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

bie*_*000 19 c++ constexpr

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

#include <cstdint>

constexpr uint16_t DefaultValueForPortStatus { 0xFFFF };

void f(uint16_t x)
{

}

int main()
{
  f(~(DefaultValueForPortStatus));
}
Run Code Online (Sandbox Code Playgroud)

当我编译这个程序(GCC trunk)时,我收到一个错误:

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

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

Qui*_*mby 14

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

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

我不确切地知道为什么编译器仅针对constexpr值发出警告,很可能是因为它可以轻松地通过~. 在其他情况下,有人可能会更改为某些“安全”值,该值在求反并从backDefaultValueForPortStatus转换为 时不会溢出。但无论常量如何,问题都存在,您可以使用以下代码进行测试:intstd::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>);
}
Run Code Online (Sandbox Code Playgroud)

相关标准部分:

  • 是的,几乎可以肯定是因为常量传播或缺乏常量传播,这意味着 GCC 在编译时评估期间不会看到该特定值被截断。使用“static”而不是“constexpr”,您会看到同样的情况,因为这会让它看到此翻译单元中没有其他代码更改 var,因此它是一个编译时常量。如果没有任何一个,它就没有理由假设全局变量仍然具有在“main”的该部分执行时静态初始化的值。(另一个 TU 中的静态构造函数可能会更改它。) (2认同)

And*_*zel 7

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

~(DefaultValueForPortStatus)

计算结果为 an int,值为-65536

具体发生的情况如下:

在进行按位非 ( ~) 运算之前,操作数DefaultValueForPortStatus会提升为 an int,因此其内存表示形式将等同于具有unsigned int以下值的 an 的表示形式:

0x0000FFFF

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

0xFFFF0000

但是,结果的数据类型为int,而不是unsigned int。(我仅使用 的等效值unsigned int来说明内存表示形式。)因此,结果的实际值为-65536(因为 C++ 要求对有符号整数使用二进制补码内存表示形式)。

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

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

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

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