当标记为 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 int
。 std::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>);
}
相关标准部分:
- expr.conv.prom-7.3.7 - 前几段。
- expr.unary.op-7.6.2.2.10 - 最后一段指出应用了“整数提升”。
- expr.arith.conv-7.4 - 仅适用于二元运算符,仍包含类似规则。
由于 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
变量的表达式做出此假设。
无论编译器是否发出警告,截断都会发生。
我在尝试翻转我的号码的所有位时遇到了一个奇怪的问题。
#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 int
。 std::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>);
}
相关标准部分:
- expr.conv.prom-7.3.7 - 前几段。
- expr.unary.op-7.6.2.2.10 - 最后一段指出应用了“整数提升”。
- expr.arith.conv-7.4 - 仅适用于二元运算符,仍包含类似规则。
由于 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
变量的表达式做出此假设。
无论编译器是否发出警告,截断都会发生。