签名/无符号别名规则是否按预期工作?

问题描述 投票:5回答:1

这是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);

这段代码的行为是什么?

C++98 and C++11

[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类型的对象,这意味着行为未定义。

C++14 and C++17

在这些标准中,情况稍微复杂一些,但规则略有放松。 [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)表示具有值iint -1对象,并且由L-to-R转换产生的prvalue具有unsigned类型。 [expr] / 4说:

如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义。

-1绝对不在prvalue表达式的unsigned类型的可表示值范围内,因此行为未定义。

如果i对象包含[0,INT_MAX]范围内的值,则将定义该行为。

同样的推理适用于通过unsigned glvalue访问int对象的情况。这是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))的目的是什么?

c++ language-lawyer strict-aliasing reinterpret-cast type-punning
1个回答
1
投票

这是defect report 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版本中的措辞,但段落编号可能不同 - n.m.)

C11的相应措辞是,

有符号整数类型的非负值范围是相应无符号整数类型的子范围,并且每种类型中相同值的表示相同。

C语言可以说更清楚,但它失去了C ++措辞的含义,即有符号类型的符号位是相应无符号类型的值表示的一部分。

拟议决议(2017年1月):

更改6.9.1 [basic.fundamental]第3段如下:

...标准和扩展无符号整数类型统称为无符号整数类型。有符号整数类型的非负值范围是相应无符号整数类型的子范围,两种类型中每一种类型中相同值的表示相同,并且每个对应的有符号/无符号类型的值表示应为是相同的。标准有符号整数类型......

所以这显然是一直以来的意图。 C ++ 17刚刚修改了措辞。

C和C ++标准从未打算允许将负值重新解释为无符号,反之亦然。野外有几个带符号的整数表示(例如一个补码,两个补码,符号和幅度),标准不强制要求它们,所以它不能规定这种重新解释的效果。它们可能已经被实现定义,但考虑到陷阱表示的可能性,那里没有真正的好处。 “实现定义的结果或陷阱”与“未定义”一样好。

© www.soinside.com 2019 - 2024. All rights reserved.