右移算子的奇怪行为(1 >> 32)

ere*_*eOn 21 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 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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

这个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
Run Code Online (Sandbox Code Playgroud)

ken*_*ytm 35

CPU可能实际上在计算

a >> (b % 32)
Run Code Online (Sandbox Code Playgroud)

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

由于标准(C++98§5.8/ 1)表明了这一点

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

没有矛盾foo(1,32)并且1>>32给出不同的结果.

 

另一方面,在bar你提供64位无符号值时,64> 32保证结果必须是 1/2 32 = 0.不过,如果你写的话

bar(1, 64);
Run Code Online (Sandbox Code Playgroud)

你可能仍然得到1.


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

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

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

(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);
Run Code Online (Sandbox Code Playgroud)

哪里(ARMISA Page AppxB-13)

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x
Run Code Online (Sandbox Code Playgroud)

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

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

  • `b%32`似乎是正确的; 我尝试了`foo(16,33)`并得到了'8`作为结果.好好的调查! (3认同)

Tad*_*pec 6

好.所以它在5.8.1中:

操作数应为整数或枚举类型,并执行整体促销.结果的类型是提升的左操作数的类型.如果右操作数为负数,或者大于或等于提升左操作数的位长度,则行为未定义.

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