为什么在C中添加时会提升整数类型?

所以我们遇到了一个字段问题,经过几天的调试后,将问题缩小到这个特定的代码位,其中没有发生while循环中的处理:

// heavily redacted code // numberA and numberB are both of uint16_t // Important stuff happens in that while loop while ( numberA + 1 == numberB ) { // some processing } 

这个运行正常,直到我们达到了65535的uint16限制。稍后的另一堆打印语句,我们发现numberA + 1的值为65536 ,而numberB包装回0 。 检查失败,没有进行任何处理。

这让我很好奇,所以我整理了一个快速的C程序(用GCC 4.9.2编译)来检查:

 #include  #include  int main() { uint16_t numberA, numberB; numberA = 65535; numberB = numberA + 1; uint32_t numberC, numberD; numberC = 4294967295; numberD = numberC + 1; printf("numberA = %d\n", numberA + 1); printf("numberB = %d\n", numberB); printf("numberC = %d\n", numberC + 1); printf("numberD = %d\n", numberD); return 0; } 

结果是:

 numberA = 65536 numberB = 0 numberC = 0 numberD = 0 

所以似乎numberA + 1结果被提升为uint32_t。 这是C语言的意图吗? 或者这是一些编译/硬件奇怪?

所以似乎numberA + 1结果被提升为uint32_t

在添加发生之前,添加的操作数被提升为int ,并且添加的结果与有效操作数( int )的类型相同。

实际上,如果int在您的编译平台上是32位宽(意味着表示uint16_t的类型具有比int更低的“转换等级”),则numberA + 1被计算为1和作为提升的numberA之间的int加法。 C11标准中的整数提升规则6.3.1.1:2:

如果可以使用int或unsigned int,则可以在表达式中使用以下内容:[…]具有整数类型(int或unsigned int除外)的对象或表达式,其整数转换等级小于或等于等级int和unsigned int。

[…]

如果int可以表示原始类型的所有值[…],则该值将转换为int

在您的情况下, unsigned short (很可能是您平台上定义的uint16_t )的所有值都可以表示为int元素,因此unsigned shortnumberA在算术运算中被提升为int

对于诸如+算术运算符,应用通常的算术转换

对于整数,这些转换的第一步称为整数提升 ,这会将小于int任何类型的值提升为int

其他步骤不适用于您的示例,因此我将省略它们以简洁。

在表达式numberA + 1 ,应用整数提升。 1已经是一个int因此它保持不变。 numberA类型为uint16_t ,它比系统上的int窄,因此numberA会被提升为int

添加两个int的结果是另一个int ,而65535 + 1给出65536因为你有32位int

所以你的第一个printf输出这个结果。

在线:

 numberB = numberA + 1; 

上面的逻辑仍然适用于+运算符,这相当于:

 numberB = 65536; 

由于numberB有一个无符号类型,特别是uint16_t ,减少了65536(mod 65536),得到0

请注意,最后两个printf语句会导致未定义的行为; 你必须使用%u来打印unsigned int 。 要处理不同大小的int ,可以使用"%" PRIu32来获取uint32_t的格式说明符。

在开发C语言时,最好尽量减少必须处理的算术编译器的种类。 因此,大多数数学运算符(例如,加法)仅支持int + int,long + long和double + double。 虽然可以通过省略int + int来简化语言(将所有内容推广到long ),但对long值的算术通常需要2-4倍于int值的算术; 由于大多数程序都是以int类型的算术为主,因此这将是非常昂贵的。 相比之下,将float增加到double会在很多情况下保存代码,因为这意味着只需要两个函数来支持float :转换为double ,并从double转换。 所有其他浮点算术运算只需要支持一个浮点类型,并且由于浮点数学通常通过调用库例程来完成,因此调用例程来添加两个double值的成本通常与调用a的成本相同。例程添加两个float值。

不幸的是,在任何人真正弄清楚0xFFFF + 1应该是什么意思之前,C语言在各种平台上变得普遍,到那时已经有一些编译器表达式产生了65536而一些产生了零。 因此,标准的编写者已经努力以一种允许编译器继续做他们正在做的事情的方式编写它们,但是从希望编写可移植代码的任何人的角度来看,它是相当无益的。 因此,在int为32位的平台上,0xFFFF + 1将产生65536,而在int为16位的平台上,它将产生零。 如果在某个平台上int碰巧是17位,那么0xFFFF + 1会授权编译器否定时间和因果关系的法则[顺便说一句,我不知道是否有任何17位平台,但是有一些32位平台在哪里uint16_t x=0xFFFF; uint16_t y=x*x; uint16_t x=0xFFFF; uint16_t y=x*x; 将导致编译器混淆其前面的代码行为]。

int Literal 1 in,即你的int32类型,所以int32和int16的操作给出了int32的结果。

要将numberA + 1语句的结果作为uint16_t尝试显式类型转换为1 ,例如: numberA + (uint16_t)1