我正在尝试通过组合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 )
答案 0 :(得分:7)
hword0
长16位,您要求32位移位。移位超过位数-未定义1。
解决方案是将组件转换为目标类型:uint64_t result = ( ((uint64_t)hword0) << 32 ) +
等。
答案 1 :(得分:4)
与问题图块相反,您可以移动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 );
答案 2 :(得分:3)
根据警告,32位大于或等于目标系统上操作数的大小。 C ++标准说:
[expr.shift]
操作数应为整数或无范围枚举类型,并执行整数提升。结果的类型为提升后的左操作数的类型。 如果右操作数为负,或者大于或等于提升后的左操作数的位长度,则行为不确定。
C标准的对应规则:
按位移位运算符
对每个操作数执行整数提升。结果的类型是提升后的左操作数的类型。 如果右操作数的值为负或大于或等于提升后的左操作数的宽度,则行为不确定。。
根据引用的规则,无论您的程序是用C还是C ++编写的,其行为都是不确定的。
您可以通过将shift的左手操作数显式转换为足够大的无符号类型来解决该问题。
P.S。在uint16_t
小于int
(非常典型)的系统上,将uint16_t
的操作数用作算术操作数时将提升为int
。因此,byte2 << 16
在此类系统上并非没有条件地†未定义。您不应该依赖此细节,但这可以解释为什么您看不到编译器关于该偏移的警告。
† byte2 << 16
仍然不确定,如果结果超出(带符号的)int
类型的可表示值的范围。如果 promoted 类型是未签名的,将会得到很好的定义。
答案 3 :(得分:3)
您可以移动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 );
答案 4 :(得分:1)
byte2 << 16
将一个8字节的值左移16个字节。那行不通。根据{{3}}:
E1 << E2的结果是E1左移E2位位置;空位用零填充。如果E1具有无符号类型,则结果的值为E1 x 2E2,比结果类型中可表示的最大值模减少1。如果E1具有带符号的类型和非负值,并且E1 x 2E2可表示为结果类型,则为结果值;否则,结果为0。否则,行为是不确定的。
由于在无符号值上使用了左移,因此得到零。
编辑
6.5.7 Bitwise shift operators, paragraph 4 of the C standard,实际上是未定义的行为:
如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为不确定。
您想要类似的东西
( ( uint64_t ) byte2 ) << 16
强制转换为64位值将确保结果不会丢失位。
答案 5 :(得分:0)
要做您想做的事情,关键思想是使用中间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;
}
答案 6 :(得分:0)
从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;