为什么将 0xff 左移 24 位会导致不正确的值?

Why does shifting 0xff left by 24 bits result in an incorrect value?

我想将 0xff 左移 3 个字节并将其存储在 uint64_t 中,它应该这样工作:

uint64_t temp = 0xff << 24;

这会产生 0xffffffffff000000 的值,这绝对不是预期的 0xff000000

但是,如果我将它移动少于 3 个字节,它就会得出正确答案。

此外,尝试将 0x01 左移 3 个字节确实有效。

这是我的输出:

0xff shifted by 0 bytes: 0xff
0x01 shifted by 0 bytes: 0x1
0xff shifted by 1 bytes: 0xff00
0x01 shifted by 1 bytes: 0x100
0xff shifted by 2 bytes: 0xff0000
0x01 shifted by 2 bytes: 0x10000
0xff shifted by 3 bytes: 0xffffffffff000000
0x01 shifted by 3 bytes: 0x1000000

通过一些实验,每个 uint64_t 左移 3 位直到 0x7f,产生 0x7f000000。 0x80 产生 0xffffffff80000000。

有人对这种奇怪的行为有解释吗? 0xff000000 肯定在 264 - 1 uint64_t 的范围内。

左移创建一个负数(32 位),然后填充为 64 位。

尝试

0xff << 24LL;

Does anyone have an explanation for this bizarre behavior?

是的,操作类型始终取决于操作数类型而不是结果类型:

double r = 1.0 / 2.0; 
    // double divided by double and result double assigned to r
    // r == 0.5

double r = 1.0 / 2; 
    // 2 converted to double, double divided by double and result double assigned to r
    // r == 0.5

double r = 1 / 2; 
    // int divided by int, result int converted to double and assigned to r
    // r == 0.0

当你理解并记住这一点时,你就不会再犯这个错误了。

我怀疑该行为取决于编译器,但我看到了同样的事情。

修复很简单。在执行转换之前,请务必将 0xff 转换为 uint64_t 类型。这样编译器就会把它当作正确的类型来处理。

uint64_t temp = uint64_t(0xff) << 24

让我们把你的问题分成两部分。第一个是移位操作,另一个是转换为uint64_t.

就左移而言,您是在 32 位(或更小)体系结构上调用未定义的行为。正如其他人提到的,操作数是 int。具有给定值的 32 位 int 将是 0x000000ff。请注意,这是一个带符号的数字,因此最左边的位是符号。根据标准,如果移位影响符号位,则结果未定义。它取决于实现的突发奇想,它随时可能发生变化,如果编译器在编译时识别它,它甚至可以完全优化掉。后者是不现实的,但实际上是允许的。虽然您永远不应该依赖这种形式的代码,但这实际上并不是让您感到困惑的行为的根源。

现在开始第二部分。左移操作的未定义结果必须转换为 uint64_t。有符号到无符号整数转换的标准状态:

If the destination type is unsigned, the resulting value is the smallest unsigned value equal to the source value modulo 2n where n is the number of bits used to represent the destination type.

That is, depending on whether the destination type is wider or narrower, signed integers are sign-extended[footnote 1] or truncated and unsigned integers are zero-extended or truncated respectively.

脚注阐明符号扩展仅适用于目前在每个带有 C++ 编译器的平台上使用的补码表示。

符号扩展意味着目标变量上符号位左侧的所有内容都将用符号位填充,这会在您的结果中产生所有 f。正如您所指出的,您可以将 0x7f 左移 3 个字节而不会发生这种情况,这是因为 0x7f=0b01111111。移位后,你得到 0x7f000000 这是最大的有符号整数,即不影响符号位的最大数字。因此,在转换中,扩展了一个0

将左操作数转换为足够大的类型可以解决此问题。

uint64_t temp = uint64_t(0xff) << 24