今天发现了一个Intel逻辑左移指令shl的一个bug。

 
逻辑左移的概念是对给定的目的操作数左移COUNT次,每次移位时最高位移入标志位CF中,最低位补零. 其中OPRD1为目的操作数, 可以是通用寄存器或存储器操作数。
 
首先说明一下我的环境:Intel(R) Pentium(R) 4 CPU,操作系统是Fedora 12,gcc的版本是4.4.2。
下面请看测试程序:
#include <stdio.h>
int main()
{
#define MOVE_CONSTANT_BITS 32
unsigned int move_step=MOVE_CONSTANT_BITS;
unsigned int value1 = 1ul << MOVE_CONSTANT_BITS;
printf("value1 is 0x%X\n", value1);
unsigned int value2 = 1ul << move_step;
printf("value2 is 0x%X\n", value2);
return ;
}
编译:
[root@Lnx99 test]#gcc -g test.c -o test
test.c: In function ‘main’:
test.c:8: warning: left shift count >= width of type
 
看到这里,我想问一下大家,这两个value的值都是什么?是否相等呢?
我相信会有很大一部分人会说这两个值一样,都是0.因为根据逻辑左移的概念,这个1被移了出去,低位补了32个0.
所以值肯定是零。
 
那么让我执行一下,看看吧。
[root@Lnx99 test]#./test
value1 is 0x0
value2 is 0x1
 
有些奇怪吧,为什么这样呢。让我们看看汇编代码吧。
  1. Dump of assembler code for function main:
  2. 0x080483c4 <main+0>: push %ebp
  3. 0x080483c5 <main+1>: mov %esp,%ebp
  4. 0x080483c7 <main+3>: and $0xfffffff0,%esp
  5. 0x080483ca <main+6>: push %ebx
  6. 0x080483cb <main+7>: sub $0x2c,%esp
  7. 0x080483ce <main+10>: movl $0x20,0x14(%esp)
  8. 0x080483d6 <main+18>: movl $0x0,0x18(%esp)
  9. 0x080483de <main+26>: mov $0x80484f4,%eax
  10. 0x080483e3 <main+31>: mov 0x18(%esp),%edx
  11. 0x080483e7 <main+35>: mov %edx,0x4(%esp)
  12. 0x080483eb <main+39>: mov %eax,(%esp)
  13. 0x080483ee <main+42>: call 0x80482f4<printf@plt>
  14. 0x080483f3 <main+47>: mov 0x14(%esp),%eax
  15. 0x080483f7 <main+51>: mov $0x1,%edx
  16. 0x080483fc <main+56>: mov %edx,%ebx
  17. 0x080483fe <main+58>: mov %eax,%ecx
  18. 0x08048400 <main+60>: shl %cl,%ebx
  19. 0x08048402 <main+62>: mov %ebx,%eax
  20. 0x08048404 <main+64>: mov %eax,0x1c(%esp)
  21. 0x08048408 <main+68>: mov $0x8048504,%eax
  22. 0x0804840d <main+73>: mov 0x1c(%esp),%edx
  23. 0x08048411 <main+77>: mov %edx,0x4(%esp)
  24. 0x08048415 <main+81>: mov %eax,(%esp)
  25. 0x08048418 <main+84>: call 0x80482f4<printf@plt>
  26. 0x0804841d <main+89>: mov $0x0,%eax
  27. 0x08048422 <main+94>: add $0x2c,%esp
  28. 0x08048425 <main+97>: pop %ebx
  29. 0x08048426 <main+98>: mov %ebp,%esp
  30. 0x08048428 <main+100>: pop %ebp
  31. 0x08048429 <main+101>: ret
  32. End of assembler dump.
汇编代码中红色的代码对应于unsigned int value1 = 1ul << MOVE_CONSTANT_BITS;蓝色的代码对应于unsigned int value2 = 1ul << move_step;
从这些代码可以看出,对于第一个指令,gcc直接计算出了结果的值,然后将其赋给了value1,而第二个指令真正的执行了逻辑左移shl。
 
但是为什么逻辑左移shl运算的结果是1,而不是0呢。这个逻辑左移的结果居然与循环左移ROL的结果是一样的。到此,我有点怀疑是不是编译器的问题,在生成机器码的时候,是否错误的生成了ROL对应的机器码呢。
使用objdump -d test查看test的机器码。
对应逻辑左移的机器码是d3 e3.
 8048400:       d3 e3                   shl    %cl,%ebx
 
为了使用循环左移ROL,只能通过修改汇编代码的方式。那么首先使用gcc -S test.c 生成汇编代码test.s, 然后修改
sall    %cl, %ebx 行为roll    %cl, %ebx, 再用gcc -g test.s -o test汇编代码test.s重新生成test。
再次使用objdump -d test查看test的机器码。
对应循环左移的机器码是d3 c3。
8048400:       d3 c3                   rol    %cl,%ebx
 
到此我们可以确定编译器没有问题,使用的就是Intel提供的逻辑左移指令,那么为什么最终的结果与期望的不同呢。
难道是Intel的bug?!
 
我们不能轻易下这个结论。因为逻辑左移是一个很基础的指令,Intel会出现这么一个明显的bug吗?
让我们去看一下Intel的指令手册吧。
SAL/SAR/SHL/SHR—Shift (Continued)——32位机
Description
These instructions shift the bits in the first operand (destination operand) to the left or right by
the number of bits specified in the second operand (count operand). Bits shifted beyond the
destination operand boundary are first shifted into the CF flag, then discarded. At the end of the
shift operation, the CF flag contains the last bit shifted out of the destination operand.
The destination operand can be a register or a memory location. The count operand can be an
immediate value or register CL. The count is masked to five bits, which limits the count range
to 0 to 31. A special opcode encoding is provided for a count of 1.
 
这下真相大白了。原来在32位机器上,移位counter只有5位。那么当执行左移32位时,实际上就是左移0位。
那么这个1ul << move_step就相当于1ul<<0。那么value2自然就是1了。
 
到此,我们虽然已经知道整个儿的来龙去脉了,可是不能不说Intel的移位指令是有着陷阱的。因为在除了在Intel这个手册中说明了这个情况,在其它的汇编语言的资料中,从没有提及过这个情况。有的朋友可能说了,之前gcc已经给了一个“test.c:8: warning: left shift count >= width of type”这样的警告了啊,已经对这个情况做了提示。关于这个warning,如果代码再复杂一些,移位的个数不再是一个常量,gcc肯定是无法检测出来的。所以,当我们需要做移位处理时,一定要注意是否超出了32位(64位机则是64位)。
 
另外,对于gcc的处理,我也有一点意见。当1ul<<32时,gcc自己预处理的结果与进行运算的结果不符,虽然它更符合用户的期望。但是,当用户开始使用常量时,结果是对的,一旦换成了变量,结果就不一样了。在大型的程序中,这样会让用户很难定位到问题的。
 
注意:常数的默认类型是int,为保证可移植性,在常数计算可能导致越界的场景下,需要对常数进行类型显示处理,比如:
    printf("1ul<<40 = %llx\n",1ul<<40);

转自:http://blog.chinaunix.net/uid-23629988-id-127318.html

Intel 移位指令的陷阱(转)的更多相关文章

  1. Linux版Matlab R2015b的bug——脚本运行的陷阱(未解决)

    0 系统+软件版本 系统:CentOS 6.7 x64, 内核 2.6.32-573.el6.x86_64软件:Matlab R2015b(包括威锋网和东北大学ipv6下载的资源,都测试过) 1 脚本 ...

  2. Intel 80x86 Linux Kernel Interrupt(中断)、Interrupt Priority、Interrupt nesting、Prohibit Things Whthin CPU In The Interrupt Off State

    目录 . 引言 . Linux 中断的概念 . 中断处理流程 . Linux 中断相关的源代码分析 . Linux 硬件中断 . Linux 软中断 . 中断优先级 . CPU在关中断状态下编程要注意 ...

  3. Intel微处理器学习笔记(五) 中断

    ▼ 中断是一个由硬件激发的过程,它中断当前正在执行的任何程序. ▼ 在Intel系列微处理器中,包括INTR和NMI(Non Maskable Interrupt)两个申请中断的引脚和一个响应INTR ...

  4. Intel大坑之中的一个:丢失的SSE2 128bit/64bit 位移指令,马航MH370??

    缘由 近期在写一些字符串函数的优化,兴趣使然.但是写的过程中,想要实现 SSE2 128 bit / 64 bit 的按 bit 逻辑位移.遇到了一个大坑,且听我娓娓道来. 我并不想用什么马航370来 ...

  5. Intel大坑之一:丢失的SSE2 128bit/64bit 位移指令,马航MH370??

    缘由 最近在写一些字符串函数的优化,兴趣使然,可是写的过程中,想要实现 128bit 的按 bit 逻辑位移,遇到了一个大坑,且听我娓娓道来. 如果要追究标题,更确切的是丢失的SSE2 128 bit ...

  6. gcc中的内嵌汇编语言(Intel i386平台)

    [转]http://bbs.chinaunix.net/thread-2149855-1-1.html 一.声明  虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中还是有一部分是用汇 ...

  7. 【av68676164(p55-p58)】 Intel CPU和Linux内存管理

    7.4.1 Intel CPU物理结构 https://www.cnblogs.com/megachen/p/9768115.html x86实模式 实模式 20位:1M内存空间 地址表示方式:段地址 ...

  8. Intel汇编程序设计-整数算术指令(上)

    第七章 整数算术指令 7.1 简介 每种汇编语言都有进行操作数移位的指令,移位和循环移位指令在控制硬件设备.加密数据,以及实现高速的图形操作时特别有用.本章讲述如何进行移位和循环移位操作以及如何使用移 ...

  9. Intel Media SDK H264 encoder GOP setting

    1 I帧,P帧,B帧,IDR帧,NAL单元 I frame:帧内编码帧,又称intra picture,I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随 ...

随机推荐

  1. 聊聊JavaScript-闭包

    今天聊聊闭包,网上五花八门的定义和解释很多很多,是不是搞得你很懵逼:每次看闭包,都不同,本来自己懂,看完别人的之后就开始怀疑自己了.在我看来,闭包简单的说就是函数里面套函数,再往大了说就是我函数外面想 ...

  2. kubernetes 命令使用

    学会命令的查找和使用,而不是死记命令,记命令不如提高英文水平 1.kubernetes环境搭建完成后,kubernetes环境搭建参考http://www.cnblogs.com/sosogengdo ...

  3. javascript垃圾收集与性能问题

    一.垃圾收集 JavaScript具有自动垃圾收集功能,也就是说,执行环境会负责管理代码所占用的内存. 不同于C和类C语言,这些语言都需要手动监听内存的使用情况.JavaScript实现了自动管理内存 ...

  4. 写出一条Sql语句:取出表Customer中第31到第40记录(SQLServer,以自动增长的Id作为主键,注意:Id可能不是连续的。

    select top 10 * from (select ROW_NUMBER() over(order by Id) as rows,* from Customer) as C where C.ro ...

  5. linux ftp及C/S服务架构

    乱码转换工具使用convmv软件:windows中文字符编码为GB2312 linux中文字符编码为utf-8选项:-f:源文件中中文字符编码-t:转换成字符编码-r:代表递归--notest:不测试 ...

  6. java.sql.SQLException: Access denied for user 'sa'@'localhost' (using password: YES)

    1.错误描述 ERROR:2015-05-01 23:43:04[localhost-startStop-1] - HHH000319: Could not get database metadata ...

  7. Codeforces Round #467 (Div. 1) B. Sleepy Game

    我一开始把题目看错了 我以为是博弈.. 这题就是一个简单的判环+dfs(不简单,挺烦的一题) #include <algorithm> #include <cstdio> #i ...

  8. mariadb集群与nginx负载均衡配置--centos7版本

    这里配置得是单nginx主机..先准备4台主机,三台mariadb集群,一台nginx. ------------------------------------------------------- ...

  9. winform自动更新程序实现

    一.问题背景 本地程序在实际项目使用过程中,因为可以操作电脑本地的一些信息,并且对于串口.OPC.并口等数据可以方便的进行收发,虽然现在软件行业看着动不动都是互联网啊啥的,大有Web服务就是高大上的感 ...

  10. 元素定位-----Selenium快速入门(二)

    一.eclipse设置 工欲善其事必先利其器,在说元素定位之前,先来设置下eclipse. 首先放大一下字体,点击windows-preferences 其次,eclipse对于java的智能提示默认 ...