C++ 意外的整数提升

C++ Unexpected Integer Promotion

我最近在写一些实际上应该测试其他代码的代码,我偶然发现了一个令人惊讶的整数提升案例。这是最小的测试用例:

#include <cstdint>
#include <limits>

int main()
{
    std::uint8_t a, b;
    a = std::numeric_limits<std::uint8_t>::max();
    b = a;

    a = a + 1;

    if (a != b + 1)
        return 1;
    else
        return 0;
}

令人惊讶的是这个程序 returns 1. 一些调试和直觉表明条件语句中的 b + 1 实际上返回了 256,而赋值语句中的 a + 1 产生了期望值 0。

C++17 草案第 8.10.6 节(关于 equality/ineuqlity 运算符)指出

If both operands are of arithmetic or enumeration type, the usual arithmetic conversions are performed on both operands; each of the operators shall yield true if the specified relationship is true and false if it is false.

什么是 "the usual arithmetic conversions",它们在标准中的何处定义?我的猜测是,对于某些运算符,它们隐式地将较小的整数提升为 intunsigned int(将 std::uint8_t 替换为 unsigned int 得到 0 的事实也支持这一点,并且进一步因为赋值运算符缺少 "usual arithmetic conversions" 子句)。

What are "the usual arithmetic conversions", and where are they defined in the standard?

[expr.arith.conv]/1

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

  • (1.1) If either operand is of scoped enumeration type, no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.

  • (1.2) If either operand is of type long double, the other shall be converted to long double.

  • (1.3) Otherwise, if either operand is double, the other shall be converted to double.

  • (1.4) Otherwise, if either operand is float, the other shall be converted to float.

  • (1.5) Otherwise, the integral promotions ([conv.prom]) shall be performed on both operands.59 Then the following rules shall be applied to the promoted operands:

    • (1.5.1) If both operands have the same type, no further conversion is needed.

    • (1.5.2) Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank.

    • (1.5.3) Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

    • (1.5.4) Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.

    • (1.5.5) Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

59) As a consequence, operands of type bool, char8_­t, char16_­t, char32_­t, wchar_­t, or an enumerated type are converted to some integral type.

For uint8_t vs int (for operator+ and operator!= later), #1.5 is applied, uint8_t will promote to intoperator+的结果也是int

另一方面,对于unsigned int vs int(对于operator+),应用#1.5.3,int将被转换为unsigned intoperator+的结果为unsigned int.

你猜对了。 C++ 中许多运算符的操作数(例如,二进制算术和比较运算符)都需要进行通常的算术转换。在 C++17 中,通常的算术转换在 [expr]/11 中指定。我不打算在这里引用整个段落,因为它相当大(你可以点击 link),但对于整数类型,通常的算术转换归结为应用整数提升,然后有效地进行更多从某种意义上说,如果初始整数提升后两个操作数的类型不同,则较小的类型将转换为两者中较大的类型。整数提升基本上是指任何小于int的类型都会被提升为intunsigned int,两者中的任何一个都可以代表原始类型的所有可能值,这主要是什么导致您的示例中的行为。

正如您自己已经弄清楚的那样,在您的代码中,通常的算术转换发生在 a = a + 1; 中,最明显的是,在您的 if

条件下
if (a != b + 1)
    …

它们导致 b 被提升为 int,使 b + 1 的结果成为 int 类型,以及 a被提升为 int!=,因此,发生在 int 类型的值上,这导致条件为真而不是假...