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

Compiler changes the type variable type from uin16_t to int when it's marked as constexpr

我在尝试翻转我的号码的所有位时遇到了一个奇怪的问题。

#include <cstdint>

constexpr uint16_t DefaultValueForPortStatus { 0xFFFF };

void f(uint16_t x)
{

}

int main()
{
  f(~(DefaultValueForPortStatus));
}

当我编译此程序(GCC 主干)时出现错误:

warning: unsigned conversion from 'int' to 'uint16_t' {aka 'short unsigned int'} changes value from '-65536' to '0' [-Woverflow]

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

这是由恕我直言,一个关于整数提升的非常不幸的 C++ 规则引起的。它基本上指出,如果 int 可以表示原始类型的所有值,则所有小于 int 的类型总是提升为 int。只有不是,才选择unsigned intstd::uint16_t 在标准 32/64 位架构上属于第一类。

int 保证至少为 16 位宽,如果是这种情况,就会选择 unsigned int 所以行为代码是实现定义的。

我不知道为什么编译器只针对 constexpr 值发出警告,很可能是因为它可以很容易地通过 ~ 传播该常量。在其他情况下,有人可能会将 DefaultValueForPortStatus 更改为某个“安全”值,该值在取反并从 int 转换回 std::uint16_t 时不会溢出。但是不管常量如何,问题都存在,你可以用这段代码测试它:

#include <type_traits>
#include <cstdint>

constexpr uint16_t DefaultValueForPortStatus { 0xFFFF };

int main()
{
  auto x = ~(DefaultValueForPortStatus);
  static_assert(std::is_same_v<decltype(x), int>);
}

相关标准部分:

由于 integer promotion,在 int 为 32 位的平台上,表达式

~(DefaultValueForPortStatus)

计算为 int,值为 -65536

具体情况如下:

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

0x0000FFFF

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

0xFFFF0000

但是,结果的数据类型为 int,而不是 unsigned int。 (我只使用 unsigned int 的等效值来说明内存表示。)因此,结果的实际值为 -65536 (因为 C++ 要求 two's complement memory representation 用于有符号整数).

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

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

编译器默认发出警告的原因可能是因为它假定此类截断不会出现在常量表达式中。它不会对基于非 const 变量的表达式做出此假设。

无论编译器是否发出警告,截断都会发生。