右移运算符的奇怪行为 (1 >> 32)
我最近在使用右移运算符时遇到了奇怪的行为。
以下程序:
#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
I recently faced a strange behavior using the right-shift operator.
The following program:
#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;
}
Outputs:
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
What happens with the foo()
function ? I understand that the only difference between what it does and the last 2 lines, is that the last two lines are evaluated at compile time. And why does it "work" if I use a 64 bits integer ?
Any lights regarding this will be greatly appreciated !
Surely related, here is what g++
gives:
> 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
CPU 很可能实际上是
在
foo
中计算;同时, 1>> 32 是一个常量表达式,因此编译器会在编译时折叠该常量,这会以某种方式给出 0。由于标准 (C++98 §5.8/1) 规定
foo(1,32)
和1>>32
给出不同的结果并不矛盾。另一方面,在
bar
中,您提供了一个 64 位无符号值,如 64 > 。 32 保证结果必须是 1 / 232 = 0。不过,如果你写,你可能仍然得到 1。
编辑:逻辑右移 (SHR) 的行为就像
a>> (b % 32/64)
在 x86/x86-64 上(Intel #253667,第 4-404 页):但是,在 ARM(至少是 armv6&7)上,逻辑右移 (LSR) 实现为 (ARMISA 页 A2-6),
其中 (ARMISA 页 AppxB-13 )
这保证了 ≥32 的右移将产生零。例如,当此代码在 iPhone 上运行时,
foo(1,32)
将给出 0。这些表明将 32 位整数移位 ≥32 是不可移植的。
It's likely the CPU is actually computing
in
foo
; meanwhile, the 1 >> 32 is a constant expression, so the compiler will fold the constant at compile-time, which somehow gives 0.Since the standard (C++98 §5.8/1) states that
there is no contradiction having
foo(1,32)
and1>>32
giving different results.On the other hand, in
bar
you provided a 64-bit unsigned value, as 64 > 32 it is guaranteed the result must be 1 / 232 = 0. Nevertheless, if you writeyou may still get 1.
Edit: The logical right shift (SHR) behaves like
a >> (b % 32/64)
on x86/x86-64 (Intel #253667, Page 4-404):However, on ARM (armv6&7, at least), the logical right-shift (LSR) is implemented as (ARMISA Page A2-6)
where (ARMISA Page AppxB-13)
This guarantees a right shift of ≥32 will produce zero. For example, when this code is run on the iPhone,
foo(1,32)
will give 0.These shows shifting a 32-bit integer by ≥32 is non-portable.
好的。所以它在 5.8.1 中:
所以你有一个未定义的行为(tm)。
OK. So it's in 5.8.1:
So you have an Undefined Behaviour(tm).
foo 中发生的情况是移位宽度大于或等于要移位的数据的大小。在 C99 标准中,这会导致未定义的行为。无论 MS VC++ 构建的 C++ 标准是什么,它都可能是相同的。
这样做的原因是为了允许编译器设计者利用任何 CPU 硬件对移位的支持。例如,i386 架构有一条指令将 32 位字移位多个位,但位数是在指令中的 5 位宽字段中定义的。最有可能的是,您的编译器通过获取位移量并用 0x1F 对其进行掩码来生成指令,以获取指令中的位移量。这意味着移位 32 与移位 0 相同。
What happens in foo is that the shift width is greater than or equal to the size of the data being shifted. In the C99 standard that results in undefined behaviour. It's probably the same in whatever C++ standard MS VC++ is built to.
The reason for this is to allow compiler designers to take advantage of any CPU hardware support for shifts. For example, the i386 architecture has an instruction to shift a 32 bit word by a number of bits, but the number of bits is defined in a field in the instruction that is 5 bits wide. Most likely, your compiler is generating the instruction by taking your bit shift amount and masking it with 0x1F to get the bit shift in the instruction. This means that shifting by 32 is the same as shifting by 0.
我使用VC9编译器在32位Windows上编译它。它给了我以下警告。由于
sizeof(int)
在我的系统上是 4 个字节,编译器表明右移 32 位会导致未定义的行为。由于它是未定义的,因此您无法预测结果。只是为了检查,我右移了 31 位,所有警告都消失了,结果也符合预期(即 0)。I compiled it on 32 bit windows using VC9 compiler. It gave me the following warning. Since
sizeof(int)
is 4 bytes on my system compiler is indicating that right shifting by 32 bits results in undefined behavior. Since it is undefined, you can not predict the result. Just for checking I right shifted with 31 bits and all the warnings disappeared and the result was also as expected (i.e. 0).我想原因是
int
类型保留 32 位(对于大多数系统),但一位用于符号,因为它是有符号类型。因此只有 31 位用于实际值。I suppose the reason is that
int
type holds 32-bits (for most systems), but one bit is used for sign as it is signed type. So only 31 bits are used for actual value.警告说明了一切!
但公平地说,我也曾犯过同样的错误。
是完全未定义的。事实上,根据我的经验,编译器通常会给出与运行时不同的结果。我的意思是,如果编译器可以在运行时计算移位表达式,它可能会给出与运行时计算的表达式不同的结果。
The warning says it all!
But in fairness I got bitten by the same error once.
is completely undefined. In fact the compiler generally gives a different results than the runtime in my experience. What I mean by this is if the compiler can see to evaluate the shift expression at run time it may give you a different result to the expression evaluated at runtime.
foo(1,32) 执行旋转操作,因此应该在右侧消失的位会重新出现在左侧。如果执行 32 次,设置为 1 的单个位将返回到其原始位置。
bar(1,32) 是相同的,但该位位于第 64-32+1=33 位,高于 32 位 int 的可表示数字。只取最低32位,全为0。
1>> 32由编译器执行。不知道为什么 gcc 在这里使用非循环移位而不是在生成的代码中使用。
((int)1 >> (int)32) 也是如此
foo(1,32) performs a rotate-shit, so bits that should disappear on the right reappear on the left. If you do it 32 times, the single bit set to 1 is back to its original position.
bar(1,32) is the same, but the bit is in the 64-32+1=33th bit, which is above the representable numbers for a 32-bit int. Only the 32 lowest bit are taken, and they are all 0's.
1 >> 32 is performed by the compiler. No idea why gcc uses a non-rotating shift here and not in the generated code.
Same thing for ((int)1 >> (int)32)