为什么你不能转移uint16_t [重复]

问题描述 投票:2回答:7

我试图通过组合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 )
c++ c bit-manipulation bit-shift shift
7个回答
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 )不会产生警告,即使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  );

7
投票

hword0是16位长,你要求32位移位。移位超过位数 - 1未定义。

解决方案是将组件转换为目标类型:uint64_t result = ( ((uint64_t)hword0) << 32 ) +等。


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  );

3
投票

根据警告,32位大于或等于目标系统上操作数的大小。 C ++标准说:

[expr.shift]

操作数应为整数或无范围枚举类型,并执行整数提升。结果的类型是提升左操作数的类型。如果右操作数为负,或大于或等于提升左操作数的位长度,则行为未定义。

来自C标准的相应规则:

按位移位运算符

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

根据引用的规则,无论是用C还是C ++编写,程序的行为都是未定义的。

您可以通过将shift的左手操作数显式转换为足够大的无符号类型来解决问题。


附:在uint16_t小于int(非常典型)的系统上,当用作算术操作数时,uint16_t oprand将被提升为int。因此,byte2 << 16不是无条件地在这样的系统上定义。您不应该依赖这个细节,但这解释了为什么您没有看到编译器关于该转变的警告。

†如果结果超出(签名)byte2 << 16类型的可表示值范围,int仍然可以是未定义的。如果提升类型未签名,则可以很好地定义。


1
投票

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位值将确保结果不会丢失位。


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;
     }

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;
© www.soinside.com 2019 - 2024. All rights reserved.