【问题标题】:C++ Unexpected Integer PromotionC++ 意外整数提升
【发布时间】:2026-01-28 18:10:01
【问题描述】:

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

#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;
}

令人惊讶的是,这个程序返回 1。一些调试和预感显示,条件中的 b + 1 实际上返回 256,而赋值中的 a + 1 产生了预期值 0。

C++17 草案的第 8.10.6 节(关于相等/不相等运算符)指出

如果两个操作数都是算术或枚举类型,通常的算术转换是在 两个操作数;如果指定的关系为真,则每个运算符都应返回真,如果是,则返回假 假的。

什么是“通常的算术转换”,它们在标准中的什么地方定义?我的猜测是,对于某些运算符,它们隐含地将较小的整数提升为intunsigned int(这也得到了将std::uint8_t 替换为unsigned int 产生0 的事实的支持,并且进一步因为赋值运算符缺少“通常的算术转换”子句)。

【问题讨论】:

  • 我不是专家,但我相信对于任何算术运算 (b + 1),短值总是被提升为整数,并且只有在值存储在一个变量。
  • 问题已经得到解答,但作为补充,这里正在发生的事情:1) 评估 a+1a 提升为 int,因此结果为 256; 2) 赋值a = a + 1 强制将值强制转换为uint8_t,通过取值模256 使值介于0 和255 之间; 3) 在a != b+1 中,b+1 像以前一样被评估为 256,然后a 被提升为int,所以你在比较 0 和 256
  • 为 C 编写,但在 C++ 中的工作方式相同:Implicit type promotion rules
  • 另外,这正是我们不使用 C++1x 的原因:auto my_u8 = u8a + u8b;。哈,你认为你声明了uint8_t 吗?再想想。

标签: c++ language-lawyer integer-promotion


【解决方案1】:

什么是“通常的算术转换”,它们在标准中的什么地方定义?

[expr.arith.conv]/1

许多二元运算符需要算术或 枚举类型导致转换并产生类似的结果类型 方式。目的是产生一个通用类型,这也是 结果。这种模式称为通常的算术转换, 定义如下:

  • (1.1) 如果任一操作数为 scoped enumeration type,则不进行转换 执行;如果另一个操作数的类型不同,则 表达式格式不正确。

  • (1.2) 如果任一操作数为 long double 类型,则另一个应为 转换为 long double。

  • (1.3) 否则,如果任一操作数为双精度,则另一个应为 转换为双倍。

  • (1.4) 否则,如果任一操作数为浮点数,则另一个应为 转换为浮点数。

  • (1.5) 否则,积分促销 ([conv.prom]) 应为 对两个操作数都执行。59 那么以下规则应为 应用于提升的操作数:

    • (1.5.1) 如果两个操作数的类型相同,则不再进行转换 需要。

    • (1.5.2) 否则,如果两个操作数都具有有符号整数类型或两者都有 有无符号整数类型,具有较小类型的操作数 整数转换等级应转换为操作数的类型 排名更高。

    • (1.5.3) 否则,如果具有无符号整数类型的操作数具有 等级大于或等于其他类型的等级 操作数,带符号整数类型的操作数应转换为 无符号整数类型的操作数的类型。

    • (1.5.4) 否则,如果操作数的类型为有符号整数类型 可以表示操作数类型的所有值 无符号整数类型,无符号整数类型的操作数应为 转换为带符号整数类型的操作数的类型。

    • (1.5.5) 否则,两个操作数都应转换为无符号数 与带符号的操作数类型对应的整数类型 整数类型。

59) 因此,bool、char8_t、char16_t 类型的操作数, char32_t、wchar_t 或枚举类型被转换为一些 整型。

对于uint8_t vs int(对于operator+operator!=之后),应用#1.5,uint8_t将提升为intoperator+的结果是int也是。

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

【讨论】:

    【解决方案2】:

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

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

    if (a != b + 1)
        …
    

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

    【讨论】: