我最近正在编写一些本应用于测试其他代码的代码,但我偶然发现了一个令人惊讶的整数提升案例。这是最小的测试用例:
#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节(关于等式/不等式运算符)指出
如果两个操作数均为算术或枚举类型,则通常对 两个操作数;如果指定的关系为真,则每个运算符应产生true;如果指定的关系为false,则产生false。 错误。
什么是“通常的算术转换”?在标准中它们在哪里定义?我的猜测是,对于某些运算符,它们隐式地将较小的整数提升为int
或unsigned int
(这也得到以下事实的支持:用std::uint8_t
替换unsigned int
会得到0,并且进一步因为赋值运算符缺少“通常的算术转换”子句。
答案 0 :(得分:2)
什么是“通常的算术转换”,在标准中它们在哪里定义?
许多期望算术或 枚举类型原因转换和收益结果类型类似 办法。目的是产生一个通用类型,这也是 结果。这种模式称为通常的算术转换, 定义如下:
(1.1)如果操作数中的任何一个为scoped enumeration type,则不进行任何转换 执行;如果另一个操作数的类型不同,则 表达式格式错误。
(1.2)如果一个操作数的类型为long double,则另一个应为 转换为长双。
(1.3)否则,如果任一操作数为double,则另一个应为 转换为两倍。
(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
与int
(以后针对operator+
和operator!=
),应用#1.5,uint8_t
将被提升为int
,并且operator+
的结果也是int
。
另一方面,对于unsigned int
与int
(对于operator+
),应用#1.5.3,int
将转换为unsigned int
,而operator+
的结果为unsigned int
。
答案 1 :(得分:2)
您的猜测是正确的。 C ++中许多运算符的操作数(例如,二进制算术和比较运算符)都需要进行通常的算术转换。在C ++ 17中,通常的算术转换在[expr]/11中指定。我不会在这里引用整个段落,因为它很大(您可以单击链接),但是对于整数类型,通常的算术转换归结为应用了整数提升,然后在某种意义上有效地进行了一些提升如果在初始整数提升之后两个操作数的类型不同,则较小的类型将转换为两个中较大的一个。整数提升基本上意味着小于int
的任何类型都将提升为int
或unsigned int
,二者中的任何一个都可以代表原始类型的所有可能值,主要是是导致您的示例出现此问题的原因。
您已经弄清楚自己在代码中,通常的算术转换发生在a = a + 1;
中,最明显的是在if条件下
if (a != b + 1)
…
它们将b
提升为int
的位置,从而使b + 1
的结果为int
类型,并且a
被提升到int
和!=
,因此发生在类型int
的值上,这导致条件为true而不是false…