【问题标题】:Why does shifting 0xff left by 24 bits result in an incorrect value?为什么将 0xff 左移 24 位会导致值不正确?
【发布时间】:2018-02-25 01:11:44
【问题描述】:

我想将 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 肯定落在 uint64_t 的 264 - 1 限制内。

【问题讨论】:

  • 0xff 是一个int,通常是一个 32 位整数。所以0xff &lt;&lt; 24 正在对 32 位整数进行数学运算,而不是在 uint64_ts 上。所以最左边的1位被视为符号位,所以当我们将宽度扩展为64位整数时,我们将所有新位设置为1,这使得当我们转换为时所有fs一个无符号整数
  • “奇怪”如“我不明白”?这种行为的原因是所有这些常量的类型为int。要获得您正在寻找的行为,请确保它们是 64 位宽,或者通过将它们显式写入无符号 long long 值(例如,0xffULL)或强制转换为 std::uint64_t
  • 试试uint64_t temp = 0xffu &lt;&lt; 24;uint64_t temp = 0xfful &lt;&lt; 24;
  • @gurka 最好建议 uint64_t(0xff) &lt;&lt; 24long 不必是 64 位宽。
  • @gurka -- unsigned 只需 16 位宽,unsigned long 32 位宽。它们可能在您的编译器中更宽,但要确保 64 位,请使用 64 位类型,unsigned long long(至少为 64 位)或std::uint64_t,如果存在,则正好64 位宽。

标签: c++ bit-manipulation bit-shift


【解决方案1】:

有人对这种奇怪的行为有解释吗?

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

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

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

【讨论】:

    【解决方案2】:

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

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

    uint64_t temp = uint64_t(0xff) << 24
    

    【讨论】:

    【解决方案3】:

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

    试试

    0xff << 24LL;
    

    【讨论】:

    • 移位右操作数的类型不影响结果的类型;在这里使用24LL 与使用24 没有什么不同。在这方面,移位运算符不同于所有其他运算符。对于其他算术和逻辑运算符,将提升较小的类型以匹配较大的类型。对于移位运算符,唯一可能提升的参数是左侧参数,并且仅当它是 charshort 或其无符号变体时。
    【解决方案4】:

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

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

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

    如果目标类型是无符号的,则结果值是等于源值模 2n 的最小无符号值,其中 n 是用于表示目标类型的位数。

    也就是说,根据目标类型是更宽还是更窄,有符号整数被符号扩展[脚注 1] 或截断,无符号整数分别被零扩展或截断。

    脚注阐明符号扩展仅适用于二进制补码表示,目前每个平台都使用 C++ 编译器。

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

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

    uint64_t temp = uint64_t(0xff) << 24
    

    【讨论】:

    • converting one of the two arguments before the shift operation solves this 这是不正确的。移位运算符很特殊,您必须转换第一个参数,而不是像其他运算符那样转换任何一个参数
    • @phuclv 我以前从未遇到过,但看来你是对的。
    猜你喜欢
    • 2016-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-12
    • 2011-04-21
    • 2019-11-13
    • 2019-05-06
    • 1970-01-01
    相关资源
    最近更新 更多