编译器将类型变量类型从 uin16_t 更改为 int,当它标记为 constexpr 时



我在试图翻转我的数字的所有位时遇到了一个奇怪的问题。

#include <cstdint>
constexpr uint16_t DefaultValueForPortStatus { 0xFFFF };
void f(uint16_t x)
{
}
int main()
{
f(~(DefaultValueForPortStatus));
}

当我编译这个程序(GCC主干)时,我得到一个错误:

警告:从'int'到'uint16_t'{也称为'short unsigned int'}的无符号转换将值从'-65536'更改为'0' [-Woverflow]

当我从类型说明符中删除constexpr时,不会出现任何警告。为什么呢?为什么uint16_t constexpr变量被编译器改为int,而在非constexpr的情况下一切都很好?

这是由一个非常不幸的c++整数提升规则引起的。它基本上表明,如果int能够表示原始类型的所有值,则所有小于int的类型总是被提升为int。只有当不存在时,才选择unsigned int。标准32/64位体系结构上的std::uint16_t属于第一类。

int保证至少为16位宽,如果碰巧是这种情况,则会选择unsigned int因此代码的行为是实现定义的。

我不知道为什么编译器只对constexpr的值发出警告,很可能是因为它可以很容易地通过~传播该常数。在其他情况下,有人可能会将DefaultValueForPortStatus更改为某些"安全";值不会溢出当否定和从int回到std::uint16_t转换。但问题是,不管是否有constness,你都可以用下面的代码来测试它:

#include <type_traits>
#include <cstdint>
constexpr uint16_t DefaultValueForPortStatus { 0xFFFF };
int main()
{
auto x = ~(DefaultValueForPortStatus);
static_assert(std::is_same_v<decltype(x), int>);
}

相关标准章节:

  • exp . conf .prom-7.3.7 -前几段。
  • expr.unary.op-7.6.2.2.10 -最后一段,声明"整数提升";应用。
  • exp . algorithm . conf -7.4 -只适用于二进制运算符,仍然包含类似的规则。

由于整数提升,在int为32位的平台上,表达式

~(DefaultValueForPortStatus)

计算结果为int,值为-65536

实际情况如下:

在按位not (~)操作发生之前,操作数DefaultValueForPortStatus被提升为int,这样它的内存表示将等同于具有以下值的unsigned int:

0x0000FFFF

在对其应用位非(~)运算符后,其内存表示将等同于具有以下值的unsigned int的内存表示:

0xFFFF0000

结果的数据类型是int,而不是unsigned int。(我只使用unsigned int的等价值来说明内存表示。)因此,结果的实际值是-65536(因为c++要求对有符号整数使用2的补码内存表示)。

将此int转换为uint16_t以匹配函数参数的类型时,丢弃16位最高有效位,保留16位最低有效位。因此,函数参数的值为0

在使用默认设置进行编译时,如果这种截断出现在常量表达式中,gcc会提供警告。可以使用-Wno-overflow命令行选项禁用此警告。

编译器在默认情况下发出警告的原因可能是因为它假设不打算在常量表达式中发生这种截断。对于基于非const变量的表达式,它不做此假设。

无论编译器是否发出警告,截断都将以任何一种方式发生。