具有整数提升的位操作
Posted
技术标签:
【中文标题】具有整数提升的位操作【英文标题】:Bit operations with integer promotion 【发布时间】:2016-11-21 13:47:47 【问题描述】: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
然后由于整数促销,我收到警告:
警告:缩小 '(int)(((unsigned char)((int)x1.X::value)) | ((unsigned char)((int)x2.X::value)))' 来自 '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
是设置/清除操作)
【问题讨论】:
【参考方案1】:如果你使用无符号类型,一切都会好的。标准规定,对于无符号目标整数类型,窄化是完美定义的:
4.7 积分转换 [conv.integral] ... 2 如果目标类型是无符号的,则结果值是与源一致的最小无符号整数 整数(模 2n,其中 n 是用于表示无符号类型的位数)。
但如果目标类型已签名,则结果是定义的实现,根据下一段(强调我的):
3 如果目标类型是有符号的,如果可以在目标类型中表示,则值不变; 否则,该值为实现定义。
在常见的实现中一切都会好起来的,因为编译器通过只为无符号或有符号类型保留低级字节来简单地进行缩小转换更简单。但标准只要求实现定义会发生什么。当原始值无法在目标类型中表示时,实现可以记录将值缩小为有符号类型会给出0
,并且仍然符合要求。
顺便说一句,由于 C++ 和 C 经常以相同的方式处理转换,应该注意 C 标准略有不同,因为最后一种情况可能会引发信号:
6.3.1.3 [转换] 有符号和无符号整数 ...3 否则,新类型是有符号的,值不能在其中表示;无论是 结果是实现定义的或引发了实现定义的信号。
仍然确认 C 和 C++ 是不同的语言...
【讨论】:
(signed target):在这些位操作的情况下,我认为任何结果都可以表示回目标类型,从而避免实现定义的行为。我说的对吗? 在所有普通架构中你是对的。我想知道什么可能会打破这种极端情况(int8_t
是二进制补码,但普通int
不是),以及是否可以按照标准允许这样做。目前我还没有明确的答案......
@bolov:我已经深入研究了标准,并没有禁止实现支持int8_t
类型,但使用符号和幅度 具有简单的int
。在那种情况下,((int8_t) -128) | (int8_t) 1
可以给-129
它应用积分提升(转换为 int)。所以没有什么能保证按位运算在有符号类型上是稳定的(结果与操作数的类型相同)。以上是关于具有整数提升的位操作的主要内容,如果未能解决你的问题,请参考以下文章