Lan*_*yer 5 c++ strict-aliasing language-lawyer reinterpret-cast type-punning
这是C++ 17形式的规则([basic.lval]/8),但它在其他标准中看起来很相似("Lvalue"而不是C++ 98中的"glvalue"):
8如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
(8.4) - 对应于对象动态类型的有符号或无符号类型
规则听起来像"你会有UB,除非你做X",但这并不意味着如果你做X,你就不会得到UB,正如人们所期望的那样!事实上,X是有条件的或无条件的UB,具体取决于标准的版本.
让我们看看以下代码:
int i = -1;
unsigned j = reinterpret_cast<unsigned&>(i);
Run Code Online (Sandbox Code Playgroud)
这段代码的行为是什么?
[expr.reinterpret.cast]/10(C++ 11中的/ 11)(重点是我的):
如果可以使用reinterpret_cast将"指向T1的指针"类型的表达式显式转换为"指向T2的指针"类型,则可以将类型T1的左值表达式强制转换为"对T2的引用".也就是说,引用转换reinterpret_cast(x)与使用内置&和*运算符的转换*reinterpret_cast(&x)具有相同的效果.结果是一个左值,它引用与源左值相同的对象,但具有不同的类型.
所以reinterpret_cast<unsigned&>(i)
左值是指int
对象i
,但是带有usigned
类型.初始化需要初始化表达式的值,这正式意味着左值到右值的转换应用于左值.
[conv.lval]/1:
可以将非函数非数组类型T 的左值转换为右值.如果T是不完整类型,则需要进行此转换的程序格式不正确.如果左值引用的对象不是类型T的对象,并且不是从T派生的类型的对象,或者如果对象未初始化,则需要此转换的程序具有未定义的行为.
我们的左值unsigned
类型不是指类型的对象,unsigned
这意味着行为是未定义的.
在这些标准中,情况稍微复杂一些,但规则略有放松.[expr.reinterpret.cast]/11告诉同样的事情:
结果引用与源glvalue相同的对象,但具有指定的类型.
关于UB的违规措辞已从[conv.lval]/1中删除:
可以将非函数非数组类型T 的glvalue 转换为prvalue.如果T是不完整类型,则需要进行此转换的程序格式不正确.如果T是非类类型,则prvalue的类型是T的非限定版本.否则,prvalue的类型是T.
但是L-to-R转换的读取值是多少?[conv.lval] /(2.6)(/(3.4)in C++ 17)回答了这个问题:
... glvalue指示的对象中包含的值是prvalue结果
unsigned
lvalue reinterpret_cast<unsigned&>(i)
表示i
int
具有该值的对象,-1
并且由L-to-R转换产生的prvalue具有unsigned
类型.[expr]/4说:
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.
-1
肯定不在unsigned
prvalue表达式类型的可表示值范围内,因此行为未定义.
如果i
对象包含来自[0,INT_MAX]范围的值,则将定义该行为.
相同的推理适用于unsigned
通过int
glvalue 访问对象的情况.这是C++ 98和C++ 11中的UB以及C++ 14和C++ 17中的UB,除非[0,INT_MAX]范围内的对象值.
因此,与流行的观点相反,该别名规则允许将对象重新解释为包含相应的有符号/无符号类型的值,但它不允许它.对于[0,INT_MAX]范围内的值,有符号和无符号类型的对象具有相同的表示形式(" 有符号整数类型的非负值范围是相应无符号整数类型的子范围,其表示形式相同这两种类型中的每一种都是相同的 "在C++ 17中说[basic.fundamental]/3).很难将这种访问称为"重新解释",更不用说在C++ 14之前这是无条件的UB.
那么规则([basic.lval] /(8.4))的目的是什么?
这是缺陷报告 2214的主题,其中写道:
部分:6.9.1 [basic.fundamental] 状态:C++17 提交者:Richard Smith 日期:2015-12-15
[2017 年 2 月/3 月会议通过。]
根据 6.9.1 [basic.fundamental] 第 3 段,
有符号整数类型的非负值范围是相应无符号整数类型的子范围,并且每个相应的有符号/无符号类型的值表示应相同。(这是C++11和C++14版本中的措辞,尽管段落编号可能不同--nm)
C11 的相应措辞是,
有符号整数类型的非负值的范围是相应无符号整数类型的子范围,并且每个类型中相同值的表示是相同的。
C 措辞可以说更清晰,但它失去了 C++ 措辞的含义,即有符号类型的符号位是相应无符号类型的值表示的一部分。
拟议决议(2017 年 1 月):
将 6.9.1 [basic.fundamental] 第 3 段更改如下:
...标准和扩展无符号整数类型统称为无符号整数类型。有符号整数类型的非负值范围是对应的无符号整数类型的子范围,两种类型中相同值的表示是相同的,并且每个对应的有符号/无符号类型的值表示应是相同的。标准有符号整数类型...
所以这显然是一直以来的意图。C++17 刚刚修正了措辞。
C 和 C++ 标准从未打算允许将负值重新解释为无符号,反之亦然。存在多种有符号整数表示形式(例如,一个的补码、两个的补码、符号和数值),并且该标准不强制要求其中任何一个,因此它不能规定此类重新解释的效果。它们本来可以由实现定义,但考虑到陷阱表示的可能性,这并没有真正的好处。“实现定义的结果或陷阱”与“未定义”一样好。