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

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

本文关键字:不正确 0xff 移位 为什么      更新时间:2023-10-16

我想将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限制。

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

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

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

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

尝试

0xff << 24LL;

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

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

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

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

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

脚注澄清了符号扩展仅适用于二进制补码表示,目前在具有C++编译器的每个平台上都使用。

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

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

uint64_t temp = uint64_t(0xff) << 24