带整数提升的位运算
Bit operations with integer promotion
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
是 set/clear op)
如果你使用无符号类型,一切都会好的。标准要求对于无符号目标整数类型,窄化是完美定义的:
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++ 是不同的语言...
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
是 set/clear op)
如果你使用无符号类型,一切都会好的。标准要求对于无符号目标整数类型,窄化是完美定义的:
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++ 是不同的语言...