2016-10-25 47 views
4

为什么下面的代码使用gcc编译,打印出“ffffffff 0”而不是“0 0”?这两位在两条指令中右移32位。它没有多大意义,因为x == 32,但还是这个奇怪的结果发生......C移位时的奇怪行为

#include <stdio.h> 

int main(void) 
{ 
    int x = 32; 
    printf("%x\n", 0xffffffff >> x); 
    printf("%x\n", 0xffffffff >> 32); 
    return 0; 
} 

编辑: enter image description here

EDIT2: 是,编译器警告我。但这不是重点。我使用0xffffffff作为掩码,用变量移位。例如,当我用8位移位时,我想要0xffffff(并且这样做)。当我用31位移位时,我想要0x1作为结果(并且这样做)。当我用32位转换时,它给了我0xffffffff(而不是0,当我有32作为文字,而不是变量时,这是beahaviour)。这很奇怪,对于我的目的来说,制作一个32位的特殊情况真的很不方便,因为它应该给0(并且它,但是只有当32是文字时)

+4

你的编译器**甚至警告你**。这是UB。 –

+1

您的文字图像[不是非常有用](// meta.unix.stackexchange.com/q/4086)。它不能被复制到编辑器中,而且它的索引也不是很好,这意味着具有相同问题的其他用户不太可能在此找到答案。请[编辑]您的文章直接合并相关文本(最好使用复制+粘贴以避免转录错误)。 –

+0

是的,它确实警告过我。但关键是第二个操作数在这两种情况下仍然是32。这是未定义的行为,我明白了,但为什么在这两种情况下未定义的行为是不同的? @TobySpeight你有文本中的源代码。该图像用于证明发生这种情况并让人们看到编译器的版本以及我如何编译它 – caution2toxic

回答

6

假设int和unsigned int的宽度是32位,那么整数常量0xffffffff的类型是unsigned int。

将整数值右移大于或等于整数的宽度将导致未定义的行为。

这种情况发生在你的例子中的两种情况。

更新:

未定义的行为意味着任何事情都可能发生。获取值0xffffffff而不是0符合此行为。

您不能移动整数类型的宽度,因为它在标准中是这样说的。如果该值大于或等于您使用的类型的宽度,则必须进行检查。
如果右操作数的值是负的或大于或等于的宽度


(:ISO/IEC 9899 201X 6.5.7按位的移位运算符3引自)提升左操作数,行为是未定义的。

+0

但是,为什么未定义的行为有所不同?如果在两种情况下它都会给出0xffffffff,那么我会很好(不是直观的真实结果,但仍然不会有差异)。这就像做同样的事情2次,并得到不同的结果... – caution2toxic

+4

@ caution2toxic这就是它的未定义。编译器可以自由地按照自己的意愿去做。 – dbush

+1

未定义的行为未定义。没有要求它甚至是确定性的。 –

-1

它取决于编译器。但在你的例子中,在第一种情况下,x是带符号的,所以移位是算术运算(最左边的位从左边重新插入)。在第二种情况下,文字32被视为无符号,因此移位变为逻辑(从左边插入0)。这解释了ffffffff 0的结果。

0

如果正在使用32位int靶向的环境中,然后通过32位或更多位移是Undefined Behaviour

如果右操作数的值是负的或者是大于或等于的数在提升的左操作数中的位,行为是未定义的。

这是您作为程序员避免调用UB的责任。

4

编辑2:是的,编译器警告我。但这不是重点。我是 使用0xffffffff作为掩码,我用变量移位。例如,对于的例子,当我用8位移位时,我想要0xffffff(并且它是这样做的)。 当我与31 bitshift我想要0x1作为结果(和它那样)。和 当我用32位移位它给了我0xffffffff(而不是0,这是 beahaviour当我有32作为一个文字,而不是一个变量)。这是 怪我的目的就真的很奇怪做出特殊情况 为32,因为它应该给0(和它,但只有当32是 文字)

是的,它是点,非常非常。当你尝试将它移动32时,就像你的编译器说的那样,你调用未定义的行为。所有的赌注都在这个时候关闭,对于该计划将做什么不再做任何假设。不打印任何东西或格式化您的驱动器是免费的。所以它不是“奇怪的”。

2

除了其他答案提到的未定义行为之外,结果的差异来自GCC优化第二个bitshift。

如果拆开使用-S标志代码,你可以看到只有一个shrl指令用于:

subq $16, %rsp 
movl $32, -4(%rbp) 
movl -4(%rbp), %eax 
movl $-1, %edx 
movl %eax, %ecx 
shrl %cl, %edx 
movl %edx, %eax 
movl %eax, %esi 
movl $.LC0, %edi 
movl $0, %eax 
call printf 
movl $0, %esi 
movl $.LC0, %edi 
movl $0, %eax 
call printf 
movl $0, %eax 

编译器看到,它可以在编译时计算第二位位移是永远不会改变,取而代之的是计算结果,这里0