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
;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 = 0xFFFFFF00uint32_t a = 0x000000FF << 8;
也返回= 0xFFFFFF00。也许这是编译器中的错误演员....
编辑:
正如phuclv所指出的,上述解释略有不足。正确的解释是,使用uint32_t a = 0xFF << 8;
,编译器执行此操作:
0xFF
;这是一个int
;0xFF00
;它是一个int,所以它是负面的uint32_t
。因为它是负面的,所以1
s是前置的,导致0xFFFFFF00
与上述解释的不同之处在于,如果你写uint32_t a = 0xFF << 7;
,你会得到0x7F80
而不是0xFFFFFF80
。
这也解释了我在上一个答案结尾处写的两个“怪异”的东西。
作为参考,在thread linked in the comment中有一些关于编译器如何解释文字的更多解释。特别是在this answer中,有一个表格,其中包含编译器为文字指定的类型。在这种情况下(无后缀,十六进制值),编译器根据符合该值的最小类型分配此类型:
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,因此结果变为0xFFFFFF00Uuint32_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的符号扩展
如果你在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将始终保留输入值,因此将签名类型转换为更宽的签名类型将通过符号扩展来完成,并且将无符号类型转换为更宽的类型将由零扩展
[感谢Mats Petersson]
使用强制转换操作符强制编译器将0xFF视为uint32_t解决问题。看起来像Arduino xcompiler对待常量有点不同,因为我从来没有在转换之前进行过演员。
谢谢!