Arduino左移不按预期工作,编译错误?

问题描述 投票:0回答:3
uint32_t a = 0xFF << 8;
uint32_t b = 0xFF;
uint32_t c = b << 8;

我正在为Uno(1.0.x和1.5)编译,显然ac应该是相同的值,但它们不是......至少在目标上运行时不会。我在主机上编译相同的代码,没有任何问题。

右移工作正常,左移仅在我将变量与常数相对应时起作用。

谁能证实这一点?

我正在使用Visual Micro和VS2013。使用1.0.x或1.5 Arduino进行编译会导致相同的故障。

EDIT:

在目标上:

A = 0xFFFFFF00
C = 0x0000FF00
c++ arduino integer arduino-uno
3个回答
1
投票

该问题与签名/无符号隐式转换有关。

你的意思是uint32_t a = 0xFF << 8;

  • 宣布0xFF;这是一个signed char;
  • 有一个<<操作,因此变量转换为int。由于它是一个带符号的char(因此其值为-1),因此用1填充,以保留符号。所以变量是0xFFFFFFFF;
  • 它被转移,所以a = 0xFFFFFF00

注意:这有点错误,请参阅下面的“更正确”版本

如果要重现相同的行为,请尝试以下代码:

uint32_t a = 0xFF << 8;
uint32_t b = (signed char)0xFF;
uint32_t c = b << 8;

Serial.println(a, HEX);
Serial.println(b, HEX);
Serial.println(c, HEX);

结果是

FFFFFF00
FFFFFFFF
FFFFFF00

或者,另一方面,如果你写

uint32_t a = (unsigned)0xFF << 8;

你得到那个a = 0x0000FF00

编译器只有两个奇怪的东西:

  1. uint32_t a = (unsigned char)0xFF << 8;返回a = 0xFFFFFF00
  2. uint32_t a = 0x000000FF << 8;也返回= 0xFFFFFF00。

也许这是编译器中的错误演员....

编辑:

正如phuclv所指出的,上述解释略有不足。正确的解释是,使用uint32_t a = 0xFF << 8;,编译器执行此操作:

  • 宣布0xFF;这是一个int;
  • 有一个<<操作,因此这成为0xFF00;它是一个int,所以它是负面的
  • 然后它被提升为uint32_t。因为它是负面的,所以1s是前置的,导致0xFFFFFF00

与上述解释的不同之处在于,如果你写uint32_t a = 0xFF << 7;,你会得到0x7F80而不是0xFFFFFF80

这也解释了我在上一个答案结尾处写的两个“怪异”的东西。

作为参考,在thread linked in the comment中有一些关于编译器如何解释文字的更多解释。特别是在this answer中,有一个表格,其中包含编译器为文字指定的类型。在这种情况下(无后缀,十六进制值),编译器根据符合该值的最小类型分配此类型:

  1. int
  2. unsigned int
  3. long int
  4. unsigned long int
  5. long long int
  6. unsigned long long int

这导致了一些更多的考虑因素:

  • uint32_t a = 0x7FFF << 8;这意味着文字被解释为有符号整数;促销到更大的整数扩展了符号,因此结果是0xFFFFFF00
  • uint32_t b = 0xFFFF << 8;在这种情况下,文字被解释为无符号整数。因此,促销到32位整数的结果是0x0000FF00

0
投票

这里最重要的是在Arduino int中是一个16位类型。这将解释一切

  1. 对于uint32_t a = 0xFF << 8:0xFF的类型为int1。 0xFF << 8导致0xFF00,这是16位int2中的带符号负值。当将int值再次分配给uint32_t变量时,它将在向上转换时进行符号扩展3,因此结果变为0xFFFFFF00U
  2. 对于以下行 uint32_t b = 0xFF; uint32_t c = b << 8; 0xFF在16位int中为正,因此b也包含0xFF。然后将其向左移8位导致0x0000FF00,因为b << 8uint32_t表达式。它比int宽,所以没有促进int发生在这里

uint32_t a = (unsigned)0xFF << 8类似,输出为0x0000FF00,因为转换为unsigned int时的正0xFF仍然为正。将unsigned int上传到uint32_t会进行零扩展,但符号位已经为零,所以即使你做int32_t b = 0xFF; uint32_t c = b << 8,高位仍为零。与“奇怪的”uint32_t a = 0x000000FF << 8相同。而不是(无符号)0xFF,你可以使用完全等效的版本(但更短)0xFFU

OTOH如果你将b声明为uint8_t b = 0xFFint8_t b = 0xFF那么事情会有所不同,会发生整数提升,结果将类似于第一行(0xFFFFFF00U)。如果你像这样将0xFF转换为signed char

uint32_t b = (signed char)0xFF;
uint32_t c = b << 8;

然后在提升到int时,它将被符号扩展为0xFFFF。类似地将它转换为int32_tuint32_t将导致从signed char到32位宽值0xFFFFFFFF的符号扩展

如果你在unsigned char中转换为uint32_t a = (unsigned char)0xFF << 8;而不是(unsigned char)0xFF将使用零extension4升级为int,因此结果将与uint32_t a = 0xFF << 8;完全相同

总结:如有疑问,请参阅标准。编译器很少对你说谎


1 Type of integer literals not int by default?

整数常量的类型是相应列表中可以表示其值的第一个。

Suffix      Decimal Constant          Octal or Hexadecimal Constant
-------------------------------------------------------------------
none        int                       int
            long int                  unsigned int
            long long int             long int
                                      unsigned long int
                                      long long int
                                      unsigned long long int

2严格来说,转移到符号位是不明确的行为

3规则是添加UINT_MAX + 1

否则,如果新类型是无符号的,则通过重复地添加或减去一个可以在新类型中表示的最大值来转换该值,直到该值在新类型的范围内。

如果值适合目标类型,4A cast将始终保留输入值,因此将签名类型转换为更宽的签名类型将通过符号扩展来完成,并且将无符号类型转换为更宽的类型将由零扩展


-1
投票

[感谢Mats Petersson]

使用强制转换操作符强制编译器将0xFF视为uint32_t解决问题。看起来像Arduino xcompiler对待常量有点不同,因为我从来没有在转换之前进行过演员。

谢谢!


推荐问答