tl;dr 在进行整数提升时(类型短于 int
),位操作是否安全且行为符合预期?
例如
uint8_t a, b, c;
a = b & ~c;
这是我所拥有的粗略 MCVE:
struct X { // this is actually templated
using U = unsigned; // U is actually a dependent name and can change
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {x1.value | x2.value};
else
return {x1.value & ~x2.value};
}
这很好用,但是当 U
更改为小于 int
的整数类型,例如std::uint8_t
然后由于整数促销,我收到警告:
warning: narrowing conversion of '(int)(((unsigned char)((int)x1.X::value)) | ((unsigned char)((int)x2.X::value)))' from 'int' to 'X::U {aka unsigned char}' inside { } [-Wnarrowing]
所以我添加了一个 static_cast
:
struct X {
using U = std::uint8_t;
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {static_cast<X::U>(x1.value | x2.value)};
else
return {static_cast<X::U>(x1.value & ~x2.value)};
}
问题:整数提升和缩小转换是否会扰乱预期结果(*)?特别是因为这些强制转换会前后改变符号( unsigned char
-> int
-> unsigned char
)。如果U
呢?已签名,即 std::int8_t
(它不会在我的代码中签名,但对它的行为感到好奇)。
我的常识认为代码完全没问题,但我的 C++ 偏执认为至少有机会实现定义的行为。
(*) 如果不清楚(或者我搞砸了),预期的行为是设置或清除位( x1
是值,x2
是掩码,B
是设置/清除操作)
最佳答案
如果你使用无符号类型,一切都会好的。标准要求对于无符号目标整数类型,缩小是完美定义的:
4.7 Integral conversions [conv.integral]
...
2 If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type).
但是,如果目标类型已签名,则结果是实现定义的,根据下一段(强调我的):
3 If the destination type is signed, the value is unchanged if it can be represented in the destination type; otherwise, the value is implementation-defined.
在常见的实现中,一切都会好起来的,因为编译器通过仅保留无符号或有符号类型的低级字节来简单地进行缩小转换会更简单。但该标准只要求实现定义将会发生什么。实现可以记录当原始值无法在目标类型中表示时将值缩小到有符号类型给出 0
,并且仍然符合要求。
顺便说一句,由于 C++ 和 C 通常以相同的方式处理转换,因此应该注意 C 标准略有不同,因为最后一种情况可能会引发信号:
6.3.1.3 [Conversions] Signed and unsigned integers
...
3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
仍然确认 C 和 C++ 是不同的语言...
关于c++ - 具有整数提升的位运算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40721742/