Arduino左移未按预期工作,编译器错误
Arduino left shift not working as expected, compiler bug?
uint32_t a = 0xFF << 8;
uint32_t b = 0xFF;
uint32_t c = b << 8;
我正在为 Uno(1.0.x 和 1.5(编译,很明显a
和c
应该是相同的值,但它们不是......至少在目标上运行时不是。我在主机上编译相同的代码,没有任何问题。
右移工作正常,左移仅在我移动变量与常量时才有效。
谁能证实这一点?
我正在使用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
.
编译器只有两件奇怪的事情:
-
uint32_t a = (unsigned char)0xFF << 8;
返回 a = 0xFFFFFF00 -
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
。
这也解释了我在上一个答案末尾写的两件"奇怪"的事情。
作为参考,在注释中链接的线程中,还有一些关于编译器如何解释文字的解释。特别是在这个答案中,有一个表,其中包含编译器分配给文字的类型。在这种情况下(没有后缀,十六进制值(,编译器根据适合该值的最小类型分配此类型:
-
int
-
unsigned int
-
long int
-
unsigned long int
-
long long int
-
unsigned long long int
这导致了更多的考虑:
-
uint32_t a = 0x7FFF << 8;
这意味着文本被解释为有符号整数;提升到更大的整数会扩展符号,因此结果是0xFFFFFF00
-
uint32_t b = 0xFFFF << 8;
在这种情况下,文本被解释为无符号整数。因此,提升为 32 位整数的结果是0x0000FF00
这里最重要的是,在Arduino中int是一个16位类型。这将解释一切
-
对于
uint32_t a = 0xFF << 8
:0xFF的类型为int
1。0xFF << 8
导致 0xFF00这是 16 位 int2 中的有符号负值。再次将int
值分配给uint32_t
变量时,它在向上转换时将符号扩展为 3,因此结果变为 0xFFFFFF00U -
对于以下行
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 = 0xFF
或 int8_t b = 0xFF
则情况会有所不同,发生整数提升,结果将类似于第一行 (0xFFFFFF00U(。如果你像这样0xFF signed char
uint32_t b = (signed char)0xFF;
uint32_t c = b << 8;
然后在升级到 int 时,它将被签名扩展到 0xFFFF。同样,将其转换为 int32_t
或 uint32_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 语言中签名到无符号转换 - 它总是安全的吗?
完成,而将无符号类型转换为更宽的类型将由零扩展完成
[功劳归于Mats Petersson]
使用强制转换运算符强制编译器将0xFF视为uint32_t可以解决此问题。似乎 Arduino x编译器对常量的处理方式略有不同,因为我在轮班之前从未进行过强制转换。
谢谢!
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- 编译"运算符删除"时C++编译器如何工作?
- 虚拟基类在内部如何工作?编译器如何解析对基方法的调用?
- 检查工作正常的 CXX 编译器:/cygdrive/c/cygwin64/bin/clang++ -- 已损坏
- 交叉编译器树莓派不完整类型,而本机编译器工作
- C++编译器 g++.exe 无法编译简单的测试程序 - 确定 CXX 编译器是否工作失败
- 切换到新编译器后,SSCANF 的工作方式有所不同
- 编译器生成的默认构造函数工作 - C++
- boost binary_oarchive 对于不同的编译器,其工作方式不同
- C++ - 我使用哪个编译器来使自动 eq = [ ](..) 语法工作?
- 可视化C++工作区和编译器.你好世界测试初学者
- g++ 编译器的内部工作原理,用于傻瓜
- 编译器指令在C++中的工作
- 比较运算符重载,为什么编译器不做工作?
- MinGW编译器为pip "cannot find vcvarsall.bat"错误后,仍然无法正常工作
- 在正向声明的情况下,编译器是如何工作的,Makefile是如何发挥作用的
- g++编译器为表达式提供<<类型错误,但在Visual Studio中工作
- 编译器问题:iostream不工作
- 名称篡改如何与使用不同编译器编译的DLL和LIB一起工作
- Configure无法找到工作编译器