你为什么不能移动uint16_t

时间:2019-03-08 16:02:14

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

我正在尝试通过组合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 )

7 个答案:

答案 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 behaviorC standard的6.5.7p3节中介绍了有关按位移位运算符的内容:

  

对每个操作数执行整数提升。的   结果的类型是提升后的左操作数的类型。如果   右操作数的值是负数或大于   或等于提升的左操作数的宽度,其行为是   未定义。

您会认为这意味着uint16_t上任何大于或等于16的移位都是无效的。 但是,如上所述,<<运算符的操作数受整数提升的限制。这意味着排名低于int的任何值都将在表达式中使用之前提升为int。因此,如果您系统上的int是32位,那么您最多可以左移31位。

这就是为什么( byte3 << 24 ) + ( byte2 << 16 ) + ( byte1 << 8 ) + ( byte0 << 0 )不会产生警告的原因,即使byteuint8_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;