C++ 右移操作员的奇怪行为(1>;>;32)

C++ 右移操作员的奇怪行为(1>;>;32),c++,c,bit-manipulation,bit-shift,C++,C,Bit Manipulation,Bit Shift,我最近在使用右移运算符时遇到了一个奇怪的行为 以下节目: #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

我最近在使用右移运算符时遇到了一个奇怪的行为

以下节目:

#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()
函数会发生什么情况?我知道它与最后两行的唯一区别在于,最后两行是在编译时计算的。如果我使用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
很可能CPU实际上正在计算

a >> (b % 32)
foo
;同时,1>>32是一个常量表达式,因此编译器将在编译时折叠该常量,以某种方式给出0

因为标准(C++98§5.8/1)规定

如果右操作数为负,或大于或等于提升后的左操作数的位长度,则行为未定义

foo(1,32)
1>>32
给出不同结果并不矛盾

另一方面,在
bar
中,您提供了一个64位无符号值,因为64>32保证结果必须是1/232=0。然而,如果你写

bar(1, 64);
你仍然可以得到1


编辑:逻辑右移(SHR)的行为类似于x86/x86-64上的
a>>(b%32/64)
(英特尔253667,第4-404页):

目标操作数可以是寄存器或内存位置。计数操作数可以是立即数或CL寄存器计数被屏蔽为5位(如果在64位模式下使用REX.W,则为6位)。计数范围被限制为0到31位(如果在64位模式下使用REX.W,则为63位)。为计数为1提供特殊的操作码编码

但是,在ARM(至少是armv6&7)上,逻辑右移(LSR)实现为(ARMISA第A2-6页)

这保证了权力的正确转移≥32将产生零。例如,当此代码在iPhone上运行时,
foo(1,32)
将给出0


这些显示将32位整数移位≥32是不可移植的。

我想原因是
int
类型包含32位(对于大多数系统),但一位用于符号,因为它是有符号类型。因此,实际值仅使用31位。

确定。因此,在5.8.1中:

操作数应为整数或枚举类型,并执行整数升级。结果的类型是 提升后的左操作数的值。如果右操作数为负,或大于或等于,则行为未定义 提升后的左操作数的长度(位)


所以你有一个未定义的行为(tm)。

警告说明了一切

但公平地说,我也曾被同样的错误咬过一口

int a = 1;
cout << ( a >> 32);
inta=1;
cout>32);

完全没有定义。事实上,根据我的经验,编译器通常会给出与运行时不同的结果。我的意思是,如果编译器可以看到在运行时计算移位表达式,那么它可能会给您一个与运行时计算的表达式不同的结果。

我使用VC9编译器在32位windows上编译了它。它给了我以下信息。由于
sizeof(int)
在我的系统上是4个字节,所以编译器指示右移32位会导致未定义的行为。由于未定义,因此无法预测结果。只是为了检查,我右移了31位,所有警告都消失了,结果也和预期的一样(即0)。

foo(1,32)执行一个旋转操作,因此应该在右侧消失的位会在左侧重新出现。如果执行32次,则设置为1的单个位将返回其原始位置

条(1,32)是相同的,但该位位于64-32+1=33位,高于32位整数的可表示数字。只取最低的32位,它们都是0

1>>32由编译器执行。不知道为什么gcc在这里而不是在生成的代码中使用非旋转移位


((int)1>>(int)32)

在foo中发生的情况是移位宽度大于或等于正在移位的数据的大小。在C99标准中,会导致未定义的行为。在C++标准MS VC++中,它可能是相同的。
这样做的原因是允许编译器设计者利用任何CPU硬件支持进行移位。例如,i386体系结构有一条将32位字移位若干位的指令,但该位数是在指令中的一个5位宽的字段中定义的。最有可能的情况是,编译器通过获取位移位量并用0x1F掩蔽它来生成指令,以获得指令中的位移位。这意味着以32移位等于以0移位。

我只是想知道,为什么“不是一个真正的问题”的接近投票?!非常有趣的问题。。即使我认为旋转一个大的数字会导致0。从不知道这是UB。恨UB的人会恨它。:)这也是驾车经过投票站的唯一原因。谢谢。我一直认为移位“太多”会导致值为零,而不是UB。因此,我猜在这里使用64位值(以
32
作为右移位操作数)是正确的,并且是定义的行为?
b%32
似乎是正确的;我尝试了一个
foo(16,33)
,结果得到了
8
。侦察得好!回答得很好;解释清楚完整。再次感谢。
>
不是旋转移位,否则
1>>1
将产生
INT\u MIN
,而事实证明并非如此。这里的问题是,移位的位数与类型中的位数一样多或更多,这就是U.B。在这种情况下,它表现为相当于旋转移位的结果是巧合。符号性是语言层面上的解释问题。CPU“看到”位,而不是值或符号。未定义!=不可预知的“这可能是这个意思,但不一定如此。”内森说,但出于实际原因,不应该定义它
(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);
ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x
int a = 1;
cout << ( a >> 32);