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

发布于 2024-09-12 18:31:26 字数 1266 浏览 5 评论 0原文

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

以下程序:

#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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

月下凄凉 2024-09-19 18:31:26

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) 的行为就像a>> (b % 32/64) 在 x86/x86-64 上(Intel #253667,第 4-404 页):

目标操作数可以是寄存器或内存位置。计数操作数可以是立即数或 CL 寄存器。 计数被掩码为 5 位(如果在 64 位模式下并且使用 REX.W,则为 6 位)。计数范围限制为 0 到 31(如果在 64 位模式下且使用 REX.W,则为 63)。使用 REX.W)。为计数 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);

其中 (ARMISA 页 AppxB-13 )

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x

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

这些表明将 32 位整数移位 ≥32 是不可移植的。

It's likely the CPU is actually computing

a >> (b % 32)

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

The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

there is no contradiction having foo(1,32) and 1>>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 write

bar(1, 64);

you 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):

The destination operand can be a register or a memory location. The count operand can be an immediate value or the CL register. The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used). A special opcode encoding is provided for a count of 1.

However, on ARM (armv6&7, at least), the logical right-shift (LSR) is implemented as (ARMISA Page 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);

where (ARMISA Page AppxB-13)

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x

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.

黯然#的苍凉 2024-09-19 18:31:26

好的。所以它在 5.8.1 中:

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

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

OK. So it's in 5.8.1:

The operands shall be of integral or enumeration type and integral promotions are performed. The type of the result is
that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to
the length in bits of the promoted left operand.

So you have an Undefined Behaviour(tm).

走野 2024-09-19 18:31:26

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.

灼痛 2024-09-19 18:31:26

我使用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).

白鸥掠海 2024-09-19 18:31:26

我想原因是 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.

不必了 2024-09-19 18:31:26

警告说明了一切!

但公平地说,我也曾犯过同样的错误。

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

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

The warning says it all!

But in fairness I got bitten by the same error once.

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

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.

踏雪无痕 2024-09-19 18:31:26

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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文