你为什么不能移动 uint16_t
why can't you shift a uint16_t
我正在尝试通过组合 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
。但是你不能(无损地)移动它超过它的宽度。
你的输入操作数的类型是 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 );
byte2 << 16
正在将一个 8 字节的值左移 16 个字节。那行不通的。每 6.5.7 Bitwise shift operators, paragraph 4 of the C standard:
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 x 2E2 , reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 x 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
由于您对无符号值使用左移,因此得到零。
编辑
Per paragraph 3 of the same section,其实是未定义的行为:
If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
你想要这样的东西
( ( uint64_t ) byte2 ) << 16
转换为 64 位值将确保结果不会丢失位。
hword0
是 16 位长,您请求移动 32 位。移动多于位数 - 1 未定义。
解决方案是将组件转换为目标类型:uint64_t result = ( ((uint64_t)hword0) << 32 ) +
等
根据警告,32 位大于或等于目标系统上操作数的大小。 C++ 标准说:
[expr.shift]
The operands shall be of integral or unscoped enumeration type and integral promotions are performed.The type of the result is that of the promoted left operand. The behavior is undefined if the right operandis negative, or greater than or equal to the length in bits of the promoted left operand.
C 标准的相应规则:
Bitwise shift operators
The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
根据引用的规则,您的程序的行为是未定义的,无论它是用 C 还是 C++ 编写的。
您可以通过将移位的左侧操作数显式转换为足够大的无符号类型来解决该问题。
P.S。在 uint16_t
小于 int
的系统上(这很典型),uint16_t
操作数在用作算术操作数时将提升为 int
。因此,byte2 << 16
并非无条件地 † 在此类系统上未定义。您不应该依赖这个细节,但这解释了为什么您看不到编译器关于该转变的警告。
† byte2 << 16
如果结果超出(有符号)int
类型的可表示值范围,则仍然可以未定义。如果 promoted 类型是无符号的,它会被很好地定义。
你可以移动uint16_t
。您不能做的是将整数值移动大于或等于类型大小的数字。这样做会调用 undefined behavior. This is documented in section 6.5.7p3 of the C standard 关于移位运算符:
The integer promotions are performed on each of the operands. The
type of the result is that of the promoted left operand. If
the value of the right operand is negative or is greater than
or equal to the width of the promoted left operand, the behavior is
undefined.
您会认为这意味着 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
,以便可以正确操作该值,以及任何可能最终移动 a 的值1入符号位:
uint64_t result = ( (uint64_t)hword0 << 32 ) +
( (uint64_t)byte3 << 24 ) + ( (uint64_t)byte2 << 16 ) +
( (uint64_t)byte1 << 8 ) + ( byte0 << 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;
}
从C的角度来看:
此处的许多讨论都忽略了应用于移位(左或右)的 uint8_t
首先提升为 int
,然后应用移位规则。
当 int
是 32 位时,uint16_t
也会发生同样的情况。 (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;
我正在尝试通过组合 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
。但是你不能(无损地)移动它超过它的宽度。
你的输入操作数的类型是 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 );
byte2 << 16
正在将一个 8 字节的值左移 16 个字节。那行不通的。每 6.5.7 Bitwise shift operators, paragraph 4 of the C standard:
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 x 2E2 , reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 x 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
由于您对无符号值使用左移,因此得到零。
编辑
Per paragraph 3 of the same section,其实是未定义的行为:
If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
你想要这样的东西
( ( uint64_t ) byte2 ) << 16
转换为 64 位值将确保结果不会丢失位。
hword0
是 16 位长,您请求移动 32 位。移动多于位数 - 1 未定义。
解决方案是将组件转换为目标类型:uint64_t result = ( ((uint64_t)hword0) << 32 ) +
等
根据警告,32 位大于或等于目标系统上操作数的大小。 C++ 标准说:
[expr.shift]
The operands shall be of integral or unscoped enumeration type and integral promotions are performed.The type of the result is that of the promoted left operand. The behavior is undefined if the right operandis negative, or greater than or equal to the length in bits of the promoted left operand.
C 标准的相应规则:
Bitwise shift operators
The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
根据引用的规则,您的程序的行为是未定义的,无论它是用 C 还是 C++ 编写的。
您可以通过将移位的左侧操作数显式转换为足够大的无符号类型来解决该问题。
P.S。在 uint16_t
小于 int
的系统上(这很典型),uint16_t
操作数在用作算术操作数时将提升为 int
。因此,byte2 << 16
并非无条件地 † 在此类系统上未定义。您不应该依赖这个细节,但这解释了为什么您看不到编译器关于该转变的警告。
† byte2 << 16
如果结果超出(有符号)int
类型的可表示值范围,则仍然可以未定义。如果 promoted 类型是无符号的,它会被很好地定义。
你可以移动uint16_t
。您不能做的是将整数值移动大于或等于类型大小的数字。这样做会调用 undefined behavior. This is documented in section 6.5.7p3 of the C standard 关于移位运算符:
The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
您会认为这意味着 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
,以便可以正确操作该值,以及任何可能最终移动 a 的值1入符号位:
uint64_t result = ( (uint64_t)hword0 << 32 ) +
( (uint64_t)byte3 << 24 ) + ( (uint64_t)byte2 << 16 ) +
( (uint64_t)byte1 << 8 ) + ( byte0 << 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;
}
从C的角度来看:
此处的许多讨论都忽略了应用于移位(左或右)的 uint8_t
首先提升为 int
,然后应用移位规则。
当 int
是 32 位时,uint16_t
也会发生同样的情况。 (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;