【问题标题】:Arduino left shift not working as expected, compiler bug?Arduino 左移未按预期工作,编译器错误?
【发布时间】:2019-02-18 17:49:35
【问题描述】:
uint32_t a = 0xFF << 8;
uint32_t b = 0xFF;
uint32_t c = b << 8;

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

右移工作正常,左移仅在我移动变量与常量时才有效。

谁能证实这一点?

我在 VS2013 中使用 Visual Micro。使用 1.0.x 或 1.5 Arduino 编译会导致同样的失败。

编辑:

在目标上:

A = 0xFFFFFF00
C = 0x0000FF00

【问题讨论】:

  • 他们的价值观是什么?
  • 相关平台上的默认整数大小是多少? b 在赋值时被提升为 32 位值,因此当您稍后将其向左移动时,不会出现溢出问题。如果默认整数大小为 8 位,则对 a 的赋值将溢出。
  • 我无法确认这一点。在 Eclipse 中,a 和 c 的值都为 0xFF00
  • 您实际获得了哪些价值?第一个值是否有可能符号扩展为 0xFFFFFF00?
  • @LouisNewstrom 如果 Eclipse 使用本机 gcc,那么您的结果与我的主机匹配。这是有问题的目标。

标签: c++ arduino integer arduino-uno


【解决方案1】:

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

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

  • 0xFF 已声明;这是signed char
  • 有一个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 &lt;&lt; 8; 返回 a = 0xFFFFFF00
  2. uint32_t a = 0x000000FF &lt;&lt; 8; 也返回 a = 0xFFFFFF00。

也许是编译器中的错误类型....

编辑:

正如 phuclv 所指出的,上面的解释略有错误。正确的解释是,使用uint32_t a = 0xFF &lt;&lt; 8;,编译器会做这样的操作:

  • 0xFF 已声明;这是int
  • 有一个0xFF00;它是一个 int,所以它是负数
  • 然后将其提升为uint32_t。由于它是负数,1s 被添加到前面,导致 0xFFFFFF00

与上面的解释不同的是,如果你写uint32_t a = 0xFF &lt;&lt; 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 &lt;&lt; 8; 这意味着文字被解释为有符号整数;提升到更大的整数会扩展符号,因此结果是 0xFFFFFF00
  • uint32_t b = 0xFFFF &lt;&lt; 8; 在这种情况下,文字被解释为无符号整数。因此提升到 32 位整数的结果是 0x0000FF00

【讨论】:

  • 你指出的奇怪的事情 1 和 2 令人困惑并且出乎意料,但至少它是可行的。谢谢大家。
  • 编译器没有问题。 0xFF 是int不是signed char。整数文字必须是 int, long int or long long int。因为 int 在 Arduino 中是 16 位宽,所以 0xFF 0xFFFFFF00 而不是 0xFFFFFFFF。只有在转换为(signed char)0xFF 之后,它才会变为 0xFFFFFFFF。就连上面的“怪事”也是正确的。只是你不懂C标准,不懂编译器
  • @phuclv 我仔细检查了这个并且......是的,那部分答案是错误的;我修复了它以反映此信息。感谢您花时间修复一个 4 岁以上的答案
【解决方案2】:

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

  1. 对于uint32_t a = 0xFF &lt;&lt; 8:0xFF 的类型为int10xFF &lt;&lt; 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 &lt;&lt; 8 是一个uint32_t 表达式。它比int 更宽,所以这里没有对int 的推广

uint32_t a = (unsigned)0xFF &lt;&lt; 8 类似,输出为0x0000FF00,因为转换为unsigned int 时正数0xFF 仍然是正数。将unsigned int 向上转换为uint32_t 进行零扩展,但符号位已经为零,因此即使您执行int32_t b = 0xFF; uint32_t c = b &lt;&lt; 8,高位仍然为零。与 “奇怪” uint32_t a = 0x000000FF &lt;&lt; 8 相同。而不是 (unsigned)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 &lt;&lt; 8; 那样转换为 unsigned char,则 (unsigned char)0xFF 将使用零扩展名提升为 int4,因此结果将与 uint32_t a = 0xFF &lt;&lt; 8; 完全相同

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


1Type 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

否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。

4如果输入值适合目标类型,则转换将始终保留输入值,因此将有符号类型转换为更广泛的有符号类型将通过符号扩展完成,并转换为无符号类型到更广泛的类型将通过零扩展来完成

【讨论】:

    【解决方案3】:

    [感谢 Mats Petersson]

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

    谢谢!

    【讨论】:

    • 编译器没有问题。 C 标准规定 0xFF 将是 int 类型(在 Arduino 中为 16 位宽),并且 0xFF
    猜你喜欢
    • 1970-01-01
    • 2015-03-25
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 2014-01-09
    • 2021-08-04
    • 2019-11-07
    • 1970-01-01
    相关资源
    最近更新 更多