已签名/未签名的别名规则是否按预期工作

Has signed/unsigned aliasing rule ever worked as intended?

本文关键字:工作 是否 别名 规则      更新时间:2023-10-16

这是C++17形式的规则(〔basic.lval〕/8),但它在其他标准中看起来很相似(C++98中的"lvalue"而不是"glvalue"):

8如果程序试图通过以下类型以外的glvalue访问对象的存储值,则行为未定义:

(8.4)-一种类型,它是与对象的动态类型相对应的有符号或无符号类型

规则听起来像是"除非你做X,否则你会得到UB",但这并不意味着如果你做了X,你就不会得到UB,正如人们所期望的那样!实际上,做X是有条件的还是无条件的UB,这取决于标准的版本。

让我们看看下面的代码:

int i = -1;
unsigned j = reinterpret_cast<unsigned&>(i);

此代码的行为是什么?

C++98和C++11

[expr.relpret.cast]/10(在C++11中为/11)(重点是我的):

如果类型为"pointer"的表达式,则类型为T1的左值表达式可以强制转换为类型为"reference to T2"到T1"可以使用interpret_cast显式转换为类型"指针到T2"。也就是说引用强制转换interpret_cast(x)与转换具有相同的效果*带有内置&和*运算符结果是一个左值与源lvalue相同的对象,但具有不同的类型

因此reinterpret_cast<unsigned&>(i)左值指的是int对象i,但类型为usigned。初始化需要初始化表达式的值,这在形式上意味着左值到右值的转换应用于左值。

[conv.val]/1:

类型T的非函数、非数组的左值可以转换为右值。如果T是不完备的类型,则需要进行此转换的程序格式不正确如果左值引用的对象不是T类型的对象并且不是从T派生的类型的对象,或者如果该对象未初始化,则程序需要此转换的具有未定义的行为

我们的unsigned类型的左值不引用unsigned类型的对象,这意味着行为是未定义的。

C++14和C++17

在这些标准中,情况有点复杂,但规则略有放宽。〔expr.relinterpret.cast〕/11告诉同样的事情:

结果引用与源glvalue相同的对象,但具有指定的类型。

有关UB的冒犯性措辞已从[conv.val]/1:中删除

非函数、非数组类型T的glvalue可以转换为prvalue。如果T是一个不完全类型,那么需要进行这种转换的程序就是格式错误的如果T是非类类型,则prvalue的类型是T的cv不合格版本。否则,prvalue的类型为T.

但是L-to-R转换读取的是哪个值?[conv.val]/(2.6)(C++17中的/(3.4))回答了这个问题:

…glvalue指示的对象中包含的值是prvalue结果

unsigned左值reinterpret_cast<unsigned&>(i)表示值为-1iint对象,并且从L-to-R转换得到的prvalue具有unsigned类型。[expr]/4表示:

如果在表达式求值期间,结果没有在数学上定义,或者不在其类型的可表示值范围内,则行为是未定义的。

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

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

相同的推理适用于通过intglvalue访问unsigned对象的情况。这是C++98和C++11中的UB,以及C++14和C++17中的UB,除非对象的值在[0,INT_MAX]范围内。

因此,与人们普遍认为的这种混叠规则允许将对象重新解释为包含相应有符号/无符号类型的值相反,它不允许这样做。对于[0,INT_MAX]范围内的值,有符号和无符号类型的对象具有相同的表示形式("有符号整数类型的非负值范围是相应的无符号整数类型中的子范围,两种类型中相同值的表示形式都是相同的",C++17中的〔basic.basic〕/3说)。很难将这种访问称为"重新解释",更不用说这是C++14之前的无条件UB。

那么,规则([basic.lval]/(8.4))的目的是什么?

这是缺陷报告2214的主题,其中写道:

章节:6.9.1[基本.基础]状态:C++17提交人:Richard Smith日期:2015-12-15

【2017年2月/3月会议通过】

根据6.9.1[基本.基本]第3段,

有符号整数类型的非负值范围是相应的无符号整数类型中的一个子范围,并且每个相应的有符号/无符号类型的值表示应相同(这是C++11和C++14版本中的措辞,尽管段落编号可能不同——n.m.)

C11中的相应措辞为

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

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

拟议决议(2017年1月):

将6.9.1[基本.基本]第3段更改如下:

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

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

C和C++标准从不允许将负值重新解释为无符号,反之亦然。有几个带符号的整数表示形式(例如,一的补码、二的补码,符号和幅度),标准没有强制要求它们中的任何一个,因此它不能规定这种重新解释的效果。它们本可以实现定义,但考虑到陷阱表示的可能性,这并没有真正的好处"实现定义的结果或陷阱";与";未定义";。