挂起的引用和未定义的行为

Dangling references and undefined behavior

本文关键字:未定义 引用 挂起      更新时间:2023-10-16

假设一个悬空引用x。只写是未定义的行为吗

&x;

甚至

x;

假设x是用有效对象初始化的,然后被销毁,§3.8/6适用:

类似地,在对象的生存期开始之前,但在对象将占用的存储已被分配之后,或者在对象的生命期结束之后,在对象占用的存储被重用或释放之前,可以使用任何引用原始对象的glvalue,但只能以有限的方式使用。关于正在建造或毁坏的物体,请参见12.7。否则,这样的glvalue指的是已分配的存储(3.7.4.2),并且使用不依赖于其值的glvalue属性是定义良好的程序有未定义的行为,如果:

--将左值到右值的转换(4.1)应用于这样的glvalue,

--glvalue用于访问的非静态数据成员或调用对象或

--glvalue绑定到对虚拟基类(8.5.3)或的引用

--glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。

因此,简单地取地址是定义明确的,(指相邻的段落)甚至可以有效地用来创建一个新对象来代替旧对象。

至于而不是取地址并只写x,这真的没有任何作用,它是&x的一个适当的子表达式。所以也可以。

首先是一个非常有趣的问题。

我认为这是未定义的行为,假设"悬挂引用"意味着"被引用对象的生命周期已经结束,对象占用的存储空间已经被重用或释放。"我的推理基于以下标准规则:

3.8§3:

本国际标准中物体的特性仅适用于给定物体在其使用寿命期间。[注意:特别是在对象的生存期开始之前和结束之后如下文所述,对对象的使用有重大限制…]

"如下所述"的所有情况均指

在对象的生存期开始之前,但在对象将占用的存储空间结束之后allocated38,或者,在对象的生存期结束后,在对象占用的存储之前重复使用或释放

1.3.24:未定义的行为

本国际标准未要求的行为【注:当本国际标准省略了对行为或程序使用错误构造或错误数据时……]

我将以下思路应用于上述报价:

  1. 如果标准没有描述一种情况下的行为,那么行为是不明确的
  2. 该标准只描述了对象在其生命周期内的行为,以及在其生命期开始/结束时的一些特殊情况。这些都不适用于我们的悬空引用
  3. 因此,以任何方式使用danling参考都没有标准规定的行为,因此行为是未定义的

使用无效对象(引用、指针等)的未定义行为是左值到右值的转换(§4.1):

如果glvalue所引用的对象不是T类型的对象,也不是从T派生的对象,或者如果该对象未初始化,则需要进行此转换的程序具有未定义的行为。

假设我们没有重载operator&,一元&运算符将左值作为其操作数,因此不会发生转换。只有一个标识符(如x;)也不需要转换。只有当引用被用作表达式中的操作数时,您才会得到未定义的行为,该表达式期望该操作数是右值——大多数运算符都是这样。关键是,执行&x实际上并不需要访问x的值。Lvalue到右值的转换发生在那些需要访问其值的运算符中。

我相信您的代码定义良好。

operator&被重载时,表达式&x被转换为函数调用,并且不遵守内置运算符的规则,而是遵循函数调用的规则。对于&x,转换为函数调用会产生x.operator&()operator&(x)。在第一种情况下,当使用类成员访问运算符时,将在x上进行左值到右值的转换。在第二种情况下,operator&的自变量将使用x进行复制初始化(如在T arg = x中),其行为取决于自变量的类型。例如,在参数是左值引用的情况下,不存在未定义的行为,因为不会发生左值到右值的转换。

因此,如果operator&对于x的类型被重载,则根据对operator&函数的调用,代码可能是定义良好的,也可能不是定义良好的。

你可能会争辩说,一元&运算符依赖于至少有一些有效的存储区域,你有地址:

否则,如果表达式的类型为T,则结果的类型为"指向T的指针",并且是指定对象的地址的prvalue

一个对象被定义为一个存储区域。在所引用的对象被销毁后,该存储区域将不复存在。

我更愿意相信,只有当无效对象被实际访问时,它才会导致未定义的行为。引用仍然认为它指的是某个对象,即使它不存在,它也可以很高兴地给出它的地址。然而,这似乎是标准中一个不明确的部分。


旁白

作为未定义行为的示例,请考虑x + x。现在,我们达到了标准中另一个不明确的部分。未指定+的操作数的值类别。通常从§5/8中推断,如果未指定,则预期prvalue:

每当glvalue表达式作为期望该操作数为prvalue的运算符的操作数出现时,都会应用左值到右值(4.1)、数组到指针(4.2)或函数到指针(4.3)标准转换将表达式转换为prvalue。

现在因为x是一个左值,所以需要进行左值到右值的转换,我们得到了未定义的行为。这是有道理的,因为加法需要访问x的值,这样它才能计算出结果。