为什么在 C 中的加法过程中提升整数类型?

Why are integer types promoted during addition in 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 <stdio.h>
#include <stdint.h>

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 语言的意图吗?或者这是编译器/硬件的一些奇怪之处?

int 中的文字 1,即在您的情况下是 int32 类型,因此使用 int32 和 int16 的操作会给出 int32 的结果。

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

So it appears that the result of numberA + 1 was promoted to uint32_t

加法的操作数在加法发生前被提升为int,加法结果与有效操作数类型相同( int).

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

The following may be used in an expression wherever an int or unsigned int may be used: […] An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.

[…]

If an int can represent all values of the original type […], the value is converted to an 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 值的成本通常与调用例程以添加两个 float 值的成本。

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