我试图通过组合16位和8位值来填充64位无符号变量:
uint8_t byte0 = 0x00;
uint8_t byte1 = 0xAA;
uint8_t byte2 = 0x00;
uint8_t byte3 = 0xAA;
uint16_t hword0 = 0xAA00;
uint16_t hword1 = 0xAAAA;
uint64_t result = ( hword0 << 32 ) + ( byte3 << 24 ) +
( byte2 << 16 ) + ( byte1 << 8 ) + ( byte0 << 0 );
这给了我一个警告。
left shift count >= width of type [-Wshift-count-overflow] uint64_t result = ( hword0 << 32 )
你可以换一个uint16_t
。你不能做的是将整数值移动一个大于或等于类型大小的数字。这样做会调用undefined behavior。有关按位移位运算符的C standard第6.5.7p3节中记录了这一点:
对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。
你会认为这意味着在uint16_t
上任何大于或等于16的移位都是无效的。但是,如上所述,<<
运算符的操作数受整数提升。这意味着排名低于int
的任何值在用于表达式之前都会提升为int
。因此,如果int
在您的系统上是32位,那么您可以将最多移位31位。
这就是为什么( byte3 << 24 ) + ( byte2 << 16 ) + ( byte1 << 8 ) + ( byte0 << 0 )
不会产生警告,即使byte
是uint8_t
而( hword0 << 32 )
不是。然而,由于对int
的晋升,这里仍有一个问题。由于提升的值现已签名,因此存在将1移入符号位的风险。这样做也会调用未定义的行为。
要解决此问题,任何向左移动32或更多值的值必须首先转换为uint64_t
,以便可以正确操作该值,以及可能最终将1移入符号位的任何值:
uint64_t result = ( (uint64_t)hword0 << 32 ) +
( (uint64_t)byte3 << 24 ) + ( (uint64_t)byte2 << 16 ) +
( (uint64_t)byte1 << 8 ) + ( byte0 << 0 );
hword0
是16位长,你要求32位移位。移位超过位数 - 1未定义。
解决方案是将组件转换为目标类型:uint64_t result = ( ((uint64_t)hword0) << 32 ) +
等。
与你的问题牌相反,你可以换一个uint16_t
。但你无法将它(无损)转移到宽度以上。
您的输入操作数的类型是applied to the output operand as well,因此在您的原始问题中,您有一个uint16_t << 32
为0(因为任何值向左移动32然后剪切为16位为0),因此几乎所有的uint8_t
值都是如此。
解决方案很简单:在移动之前,将您的值转换为适合移位的适当类型:
uint64_t result = ( (uint64_t)hword0 << 32 ) +
( (uint32_t)byte3 << 24 ) + ( (uint32_t)byte2 << 16 ) + ( (uint32_t)byte1 << 8 ) + ( (uint32_t)byte0 << 0 );
根据警告,32位大于或等于目标系统上操作数的大小。 C ++标准说:
[expr.shift]
操作数应为整数或无范围枚举类型,并执行整数提升。结果的类型是提升左操作数的类型。如果右操作数为负,或大于或等于提升左操作数的位长度,则行为未定义。
来自C标准的相应规则:
按位移位运算符
对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。
根据引用的规则,无论是用C还是C ++编写,程序的行为都是未定义的。
您可以通过将shift的左手操作数显式转换为足够大的无符号类型来解决问题。
附:在uint16_t
小于int
(非常典型)的系统上,当用作算术操作数时,uint16_t
oprand将被提升为int
。因此,byte2 << 16
不是无条件地在这样的系统上定义。您不应该依赖这个细节,但这解释了为什么您没有看到编译器关于该转变的警告。
†如果结果超出(签名)byte2 << 16
类型的可表示值范围,int
仍然可以是未定义的。如果提升类型未签名,则可以很好地定义。
byte2 << 16
左移8字节值16字节。那不行。每6.5.7 Bitwise shift operators, paragraph 4 of the C standard:
E1 << E2的结果是E1左移E2位位置;腾出的位用零填充。如果E1具有无符号类型,则结果的值为E1 x 2E2,比结果类型中可表示的最大值减少一个模数。如果E1具有带符号类型和非负值,并且E1 x 2E2可在结果类型中表示,那么这就是结果值;否则,行为未定义。
由于您在无符号值上使用左移,因此您得到零。
编辑
Per paragraph 3 of the same section,它实际上是未定义的行为:
如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。
你想要的东西
( ( uint64_t ) byte2 ) << 16
转换为64位值将确保结果不会丢失位。
要做你想做的事情,关键的想法是使用中间uint64_t(最终大小)来混淆比特。
以下编译没有警告:
你可以使用自动促销(并且没有演员表)
{
uint64_t b4567 = hword0; // auto promotion
uint64_t b3 = byte3;
uint64_t b2 = byte2;
uint64_t b1 = byte1;
uint64_t b0 = byte0;
uint64_t result = (
(b4567 << 32) |
(b3 << 24) |
(b2 << 16) |
(b1 << 8) |
(b0 << 0) );
}
你也可以使用静态强制转换(多次):
{
uint64_t result = (
(static_cast<uint64_t>(hword0) << 32) |
(static_cast<uint64_t>(byte3) << 24) |
(static_cast<uint64_t>(byte2) << 16) |
(static_cast<uint64_t>(byte1) << 8) |
(static_cast<uint64_t>(byte0) << 0 )
);
cout << "\n " << hex << result << endl;
}
你可以通过创建一个函数来执行这两个操作:a)执行静态转换,b)使用形式参数来使编译器自动提升。
功能看起来像:
// vvvvvvvv ---- formal parameter
uint64_t sc (uint64_t ui64) {
return static_cast<uint64_t>(ui64);
}
// using static cast function
{
uint64_t result = (
(sc(hword0) << 32) |
(sc(byte3) << 24) |
(sc(byte2) << 16) |
(sc(byte1) << 8) |
(sc(byte0) << 0)
);
cout << "\n " << hex << result << endl;
}
从C角度来看:
这里的许多讨论都省略了应用于班次(左或右)的uint8_t
首先被提升为int
,然后应用班次规则。
当uint16_t
为32位时,int
也会出现同样的情况。 (17位以上)
当int
是32位时
hword0 << 32
是UB,因为转移量太大:0到31之外。
当试图转移到符号位时,byte3 << 24
是UB。 byte3 & 0x80
是真的。
其他转变是可以的。
如果int
是64位,OP的原始代码很好 - 没有UB,包括hword0 << 32
。
如果int
是16位,所有代码的移位(除了<< 0
)都是UB或潜在的UB。
要做到这一点,不要施放(我试图避免的东西),考虑一下
// uint64_t result = (hword0 << 32) + (byte3 << 24) + (byte2 << 16) + (byte1 << 8) + byte0
// Let an optimizing compiler do its job
uint64_t result = hword0;
result <<= 8;
result += byte3;
result <<= 8;
result += byte2;
result <<= 8;
result += byte1;
result <<= 8;
result += byte0;
要么
uint64_t result = (1ull*hword0 << 32) + (1ul*byte3 << 24) + (1ul*byte2 << 16) +
(1u*byte1 << 8) + byte0;