Arduino左移未按预期工作,编译器错误

Arduino left shift not working as expected, compiler bug?

本文关键字:工作 编译器 错误 左移 Arduino      更新时间:2023-10-16
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 编译会导致相同的失败。

编辑:

在目标上:

A = 0xFFFFFF00
C = 0x0000FF00

该问题与有符号/无符号隐式强制转换有关。

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

  • 0xFF被声明; 这是一个signed char;
  • 有一个<<运算,因此该变量被转换为 int。由于它是一个有符号的字符(因此它的值是 -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;也返回 a = 0xFFFFFF00。

也许是编译器中的错误转换....

编辑:

正如Phuclv所指出的,上述解释略有错误。正确的解释是,使用 uint32_t a = 0xFF << 8; ,编译器执行以下操作:

  • 0xFF被声明; 它是一个int;
  • 有一个<<运算,因此这变得0xFF00;它是一个整数,所以它是负数
  • 然后提升为 uint32_t .由于它是负数,因此1 s被预置,导致0xFFFFFF00

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

这也解释了我在上一个答案末尾写的两件"奇怪"的事情。

作为参考,在注释中链接的线程中,还有一些关于编译器如何解释文字的解释。特别是在这个答案中,有一个表,其中包含编译器分配给文字的类型。在这种情况下(没有后缀,十六进制值(,编译器根据适合该值的最小类型分配此类型:

  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

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

  1. 对于uint32_t a = 0xFF << 8:0xFF的类型为 int 10xFF << 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 << 8是一个uint32_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

如果你像在 uint32_t a = (unsigned char)0xFF << 8; 中一样投射到 unsigned char,那么(unsigned char)0xFF将使用零扩展名4 提升为 int,因此结果将与 uint32_t a = 0xFF << 8;

完全相同

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


1 默认情况下不是 int 的整数文本类型?

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

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 严格来说,像这样转换为符号位是未定义的行为

  • 1 <<31 产生错误"'<<'表达式的结果未定义">
  • 定义 (1 <<31( 还是使用 0x80000000?结果不同

3 规则是加UINT_MAX+1

否则,如果新类型

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

  • 在 C 语言中签名到无符号转换 - 它总是安全的吗?
4 如果值适合目标类型,则强制转换将始终保留输入值,因此将有符号类型转换为更宽的有符号类型将由符号扩展

完成,而将无符号类型转换为更宽的类型将由零扩展完成

[功劳归于Mats Petersson]

使用强制转换运算符强制编译器将0xFF视为uint32_t可以解决此问题。似乎 Arduino x编译器对常量的处理方式略有不同,因为我在轮班之前从未进行过强制转换。

谢谢!