Intel 移位指令的陷阱(转)

今天发现了一个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 0;
}

编译:

[[email protected] 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.

所以值肯定是零。

那么让我执行一下,看看吧。

[[email protected] 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<[email protected]>
  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<[email protected]>
  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

原文地址:https://www.cnblogs.com/charlieroro/p/8487746.html

时间: 2024-07-31 18:49:39

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

Linux内核抢占机制 - 简介

本文首发于 http://oliveryang.net,转载时请包含原文或者作者网站链接. 本文主要围绕 Linux 内核调度器 Preemption 的相关实现进行讨论.其中涉及的一般操作系统和 x86 处理器和硬件概念,可能也适用于其它操作系统. 1. 背景知识 要深入理解 Preemption 必须对操作系统的 Context Switch 做一个全面的梳理.最终可以了解 Preemption 和 Context Switch 概念上的区别与联系. 1.1 Context Switch C

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

0 系统+软件版本 系统:CentOS 6.7 x64, 内核 2.6.32-573.el6.x86_64软件:Matlab R2015b(包括威锋网和东北大学ipv6下载的资源,都测试过) 1 bug描述 1.1 未知的“陷阱” 首先,这个程序在Matlab R2013a中可以完美运行,这个“陷阱“在是新安装的R2015b上才出现的. 说它是“陷阱“,是因为脚本文件涉及到大量的数据处理,比如一个几百次的循环,它可能在执行某一句的时候就失去响应了,可能是一个循环,也可能是单句,仿佛掉进了一个未知

Intel 80X86寄存器分类介绍(转)

开始读Linux内核相关书籍时,在书店里碰到一个计算机专业科班出身的朋友,向他请教时,他认为学习Linux内核不需要汇编和计算机体系结构等相关的知识.可是结合到现在的学习经历,我却越来越觉得为了搞清楚Linux内核相关设计和运行原理,自己那点自学来的汇编知识不但不够,还大大的需要补充.本文是我今日对微处理器寄存器学习总结所得,主要是翻译自<Intel 微处理器英文第7版>,阅读的过程中我参考了网上可以下载到的该书第六版的中文版和一篇关于寄存器在Visual Stdio 编译器中惯用方法的文章&

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

缘由 最近在写一些字符串函数的优化,兴趣使然,可是写的过程中,想要实现 SSE2 128 bit / 64 bit 的按 bit 逻辑位移,遇到了一个大坑,且听我娓娓道来. 我并不想用什么马航370来博眼球,当我写下这个标题的时候,的确没有马航370这个字眼,可是当我写到一半的时候,突然就冒出了马航370这几个字,如果你认真阅读了我的文章,也许你也应该思考一下,这 128 bit / 64 bit 的位移指令到底是去哪了?石沉大海了?那不就跟马航370一样吗,是一个谜,一个非常非常大的谜....

iOS 内存陷阱

iphone开发过程中,代码中的内存泄露我们很容易用内存检测工具leaks 检测出来,并一一改之,但有些是因为ios 的缺陷和用法上的错误,leaks 检测工具并不能检测出来,你只会看到大量的内存被使用,最后收到didReceiveMemoryWarning,最终导致程序崩溃.以下是开发过程中遇到的一些问题和网上的一些资料,总结了一下: 一.[UIImage imageNamed:]只适合与UI界面中的贴图的读取,较大的资源文件应该尽量避免使用 用UIImage加载本地图像最常用的是下面三种:

安装android studio报错Failed to install Intel HAXM.

在安装android studio的过程中,安装到android的模拟器加速器(intel HAXM)这一步时,报错: HAXM是用来管理硬件加速的,估计是用了这个东西模拟器就能Eclipse的龟速吧. 解决: 原因:没有找到reg.exe; 给reg.exe配置环境变量. reg.exe一般是在C:\Windows\System32\目录下. 可以将路径C:\Windows\System32\reg.exe配置在PATH下, 也可以直接将%SystemRoot%\system32;%Syste

java笔记--笔试中极容易出错的表达式的陷阱

我相信每一个学过java的人儿们都被java表达式虐过,各种"肯定是它,我不可能错!",然后各种"尼玛,真假,怎么可能?",虽然在实际开发中很少会真的让你去使用那些知识,但熟悉表达式的陷阱对于理解java数据类型在内存中的存储和运算以及JVM工作的原理有很大的帮助,最主要的,面试题太能考这些玩意了,有些坑当时爬出来了,过几天再做又会义无反顾的跳进去,于是我整理了自己做错过的一些题,也搜集了一些充满恶意的表达式方面的小题目,放在此处,警世: 问题 结果 脱坑必备 Sy

cocos2dx-3 addImageAsync陷阱

addImageAsync异步加载未响应回调前调用unbindImageAsync撤销消息回调void TextureCache::unbindImageAsync(const std::string& filename){    _imageInfoMutex.lock();    if (_imageInfoQueue && !_imageInfoQueue->empty())    {        std::string fullpath = FileUtils::g

centos7下安装intel Media Server Studio记录

1. 硬件环境 CPU:要求为intel酷睿4代(或以上) 2. 操作系统 centos7(x64) 3. 准备SDK安装包 SDK安装包需要从intel官网获取(https://software.intel.com/en-us/intel-media-server-studio/),可是申请试用版.我使用的是essentials版. 4. 安装 4.1 创建一个video Group,并添加用户 例如: usermod -a -G video root usermod -a -G video