我最近使用右移操作符遇到了一种奇怪的行为。
以下计划:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stdint.h>
int foo(int a, int b)
{
return a >> b;
}
int bar(uint64_t a, int b)
{
return a >> b;
}
int main(int argc, char** argv)
{
std::cout << "foo(1, 32): " << foo(1, 32) << std::endl;
std::cout << "bar(1, 32): " << bar(1, 32) << std::endl;
std::cout << "1 >> 32: " << (1 >> 32) << std::endl; //warning here
std::cout << "(int)1 >> (int)32: " << ((int)1 >> (int)32) << std::endl; //warning here
return EXIT_SUCCESS;
}
输出:
foo(1, 32): 1 // Should be 0 (but I guess I'm missing something)
bar(1, 32): 0
1 >> 32: 0
(int)1 >> (int)32: 0
foo()
功能会怎样?我知道它的作用和最后两行之间的唯一区别是在编译时评估最后两行。如果我使用64位整数,为什么它“有用”?
任何有关此事的灯光都将非常感谢!
肯定是相关的,这是g++
给出的:
> g++ -o test test.cpp
test.cpp: In function 'int main(int, char**)':
test.cpp:20:36: warning: right shift count >= width of type
test.cpp:21:56: warning: right shift count >= width of type
答案 0 :(得分:35)
CPU 可能正在计算
a >> (b % 32)
<{1>}中的;同时,1>&gt; 32是一个常量表达式,因此编译器将在编译时折叠常量,它以某种方式给出0。
由于标准(C ++98§5.8/ 1)声明
如果右操作数为负数,或者大于或等于提升左操作数的位长度,则行为未定义。
foo
和foo(1,32)
给出的结果并不矛盾。
另一方面,在1>>32
中,您提供了64位无符号值,因为64&gt; 32保证结果必须 1/2 32 = 0.不过,如果你写
bar
你可能仍然得到1。
编辑:逻辑右移(SHR)在x86 / x86-64(英特尔#253667,第4-404页)上的行为类似于bar(1, 64);
:
目标操作数可以是寄存器或内存位置。计数操作数可以是立即值或CL寄存器。 计数被屏蔽为5位(如果在64位模式下使用REX.W则为6位)。计数范围限制为0到31(如果是64位模式,则为63)使用REX.W)。计数为1的特殊操作码编码。
然而,在ARM(至少是armv6&amp; 7)上,逻辑右移(LSR)实现为(ARMISA Page A2-6)
a >> (b % 32/64)
其中(ARMISA Page AppxB-13)
(bits(N), bit) LSR_C(bits(N) x, integer shift)
assert shift > 0;
extended_x = ZeroExtend(x, shift+N);
result = extended_x<shift+N-1:shift>;
carry_out = extended_x<shift-1>;
return (result, carry_out);
这保证了≥32的右移将产生零。例如,当在iPhone上运行此代码时,ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x
将给出0。
这些显示将32位整数移位≥32是不可移植的。
答案 1 :(得分:6)
行。所以它在5.8.1:
操作数应为整数或枚举类型,并执行整体促销。结果的类型是 升级的左操作数。如果右操作数为负数或大于或等于,则行为未定义 提升左操作数的位长度。
所以你有一个未定义的行为(tm)。
答案 2 :(得分:3)
foo中发生的是移位宽度大于或等于被移位数据的大小。在C99标准中导致未定义的行为。无论在什么样的C ++标准MS VC ++中,它都可能是相同的。
这样做的原因是允许编译器设计人员利用任何CPU硬件支持进行轮班。例如,i386架构具有将32位字移位多位的指令,但位数在指令中的5位宽的字段中定义。很可能,您的编译器通过获取位移量并使用0x1F对其进行屏蔽来生成指令,以获得指令中的位移。这意味着移位32与移位0相同。
答案 3 :(得分:1)
我使用VC9编译器在32位窗口上编译它。它给了我以下warning。由于sizeof(int)
是4个字节,因此我的系统编译器指示右移32位会导致未定义的行为。由于未定义,因此无法预测结果。只是为了检查我右移31位并且所有警告都消失了,结果也如预期的那样(即0)。
答案 4 :(得分:0)
我认为原因是int
类型保留32位(对于大多数系统而言),但由于它是有符号类型,因此一位用于符号。因此,只有31位用于实际值。
答案 5 :(得分:0)
警告说明了一切!
但公平地说,我曾被同样的错误咬过一次。
int a = 1;
cout << ( a >> 32);
完全未定义。事实上,根据我的经验,编译器通常会给出与运行时不同的结果。我的意思是,如果编译器可以看到在运行时评估shift表达式,它可能会给运行时计算的表达式提供不同的结果。
答案 6 :(得分:-5)
foo(1,32)执行旋转屎,因此右侧消失的位重新出现在左侧。如果这样做32次,则设置为1的单个位将返回其原始位置。
bar(1,32)是相同的,但该位在64-32 + 1 =第33位,高于32位int的可表示数字。只取32个最低位,它们都是0。
1&gt;&gt; 32由编译器执行。不知道为什么gcc在这里使用非旋转移位而不是在生成的代码中。
((int)1&gt;&gt;(int)32)
的相同内容