悬挂引用和未定义的行为

时间:2013-02-06 13:41:22

标签: c++ reference undefined-behavior language-lawyer

假设悬挂参考x。是否只是编写

是未定义的行为
&x;

甚至

x;

3 个答案:

答案 0 :(得分:4)

使用无效对象(引用,指针等)未定义行为的原因是左值到右值转换(第4.1节):

  

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

假设我们没有重载operator&,则一元&运算符将左值作为其操作数,因此不会发生转换。只有标识符,如x;中也不需要转换。当引用用作表达式中的操作数时,您将只获得未定义的行为,该表达式期望该操作数为rvalue - 大多数运算符都是这种情况。关键是,执行&x实际上并不需要访问x的值。对于需要访问其值的运算符,会发生左值到右值的转换。

我相信你的代码定义明确。

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,lvalue-to-rvalue(4.1),array-to-pointer(4.2)或function-to-pointer(4.3)应用标准转换以将表达式转换为prvalue。

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

答案 1 :(得分:4)

首先,非常有趣的问题。

我想说它是未定义的行为,假设“悬空引用”意味着“引用对象的生命周期已经结束,并且对象占用的存储已经被重用或释放。”我的理由基于以下标准裁决:

3.8§3:

  

本国际标准中归属于对象的属性仅适用于给定对象   在其一生中。 [注意:特别是在对象的生命周期开始之前和生命周期结束之后   对象的使用有很大的限制,如下所述......]

所有案例“如下所述”指的是

  

在对象的生命周期开始之前但在对象将占用的存储之后   assigned38或者,在对象的生命周期结束之后,在对象占用的存储之前   重用或发布

1.3.24: 未定义的行为

  

本国际标准没有要求的行为   [注意:当本国际标准忽略任何明确的定义时,可能会出现未定义的行为   行为或程序使用错误的构造或错误数据时。 ...]

我将以下思路应用于上述引文:

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

答案 2 :(得分:3)

假设使用有效对象初始化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的正确子表达式。所以它也没关系。