根据C ++标准,该程序在哪个确切的语句中显示未定义的行为?

时间:2012-01-10 14:11:36

标签: c++ undefined-behavior

(我知道应该避免返回对函数本地变量的地址/引用,并且程序永远不应该这样做。)


在Undefined Behavior中返回对局部变量/引用结果的引用吗?或者,只有在使用返回的引用(或“取消引用”)时才会出现未定义的行为?

即。在什么确切的语句(#1#2#3)下面的代码示例调用未定义的行为? (我把每个理论都写在一起)

#include <iostream>

struct A
{ 
   int m_i;
   A():m_i(10)
   {

   } 
};  
A& foo() 
{     
    A a;
    a.m_i = 20;     
    return a; 
} 

int main()
{
   foo();                // #1 - Not UB; return value was never used
   A const &ref = foo(); // #2 - Not UB; return value still not yet used
   std::cout<<ref.m_i;   // #3 - UB: returned value is used
}

我很想知道C ++标准在这方面的规定。

我想要一个来自C ++标准的引用,它基本上会告诉我哪个确切的语句使这个代码格式不正确。

关于具体实现如何处理这一问题的讨论是受欢迎的,但正如我所说,一个理想的答案将引用C ++标准中的一个引用,这无疑澄清了这一点。

3 个答案:

答案 0 :(得分:5)

当然,当首次初始化引用时,它是有效地完成的,满足以下条件:

  

[C++11: 8.3.2/5]:不应引用引用,不引用引用数组,也不引用引用指针。引用的声明应包含初始值设定项(8.5.3),除非声明包含显式的extern说明符(7.1.1),是类定义中的类成员(9.2)声明,或者是参数的声明或返回类型(8.3.5);见3.1。 引用应初始化为引用有效的对象或函数。 [注意:特别是,空引用不能存在于定义良好的程序中,因为唯一的方法创建这样的引用将是将它绑定到通过解除引用空指针获得的“对象”,这会导致未定义的行为。如9.6中所述,引用不能直接绑定到位字段。 -end note ]

从函数返回的引用是 xvalue

  

[C++11: 3.10/1]: [..] xvalue(“eXpiring”值)也指对象,通常接近其生命周期的末尾(例如,可以移动其资源)。 xvalue是涉及rvalue引用的某些表达式的结果(8.3.2)。 [示例:调用返回类型为右值引用的函数的结果是xvalue。 -end example ] [..]

这意味着以下适用:

  

[C++11: 12.2/1]:类型的临时代码在各种上下文中创建:绑定对prvalue的引用(8.5.3),返回prvalue(6.6.3),创建的转换prvalue(4.1,5.2.9,5.2.11,5.4),抛出异常(15.1),进入处理程序(15.3),以及某些初始化(8.5)。

     

[C++11: 6.6.3/2]:既没有表达式也没有 braced-init-list 的return语句只能用于不返回值的函数,是一个返回类型为void的函数,一个构造函数(12.1)或一个析构函数(12.4)。

     

表达式为非void类型的return语句只能在返回值的函数中使用;表达式的值返回给函数的调用者。 表达式的值隐式转换为它出现的函数的返回类型。 return语句可能涉及构造和复制或移动临时对象(12.2)。 [注意:与return语句关联的复制或移动操作可能被省略或视为rvalue用于在选择构造函数时重载解析(12.8)。 -end note ]带有 braced-init-list 的return语句初始化通过copy-list-initialization(8.5.4)从函数返回的对象或引用从指定的初始化列表中。 [示例:

std::pair<std::string,int> f(const char* p, int x) {
   return {p,x};
}
     

- 示例]

此外,即使我们将以下内容解释为意味着执行了新参考“对象”的初始化,但当时裁判可能仍然活着:

  

[C++11: 8.5.3/2]:初始化后,无法更改引用以引用其他对象。请注意,引用的初始化与赋值的处理方式非常不同。 参数传递(5.2.2)和函数值返回(6.6.3)是初始化。

  • 这使#1有效。

但是,您在ref内初始化新参考main 显然违反了[C++11: 8.3.2/5]。我无法找到它的措辞,但是在执行初始化时已经退出了功能范围。

  • 这会使#2(以及因此#3)无效。

至少,标准中似乎没有任何关于此事的进一步陈述,因此如果上述推理不充分,那么我们必须得出结论,该标准在该问题上是模棱两可的。幸运的是,它在实践中没什么影响,至少在主流方面是这样。

答案 1 :(得分:2)

这是我对此事的不完整和可能不充分的看法:

关于引用的唯一特别之处是在初始化时它们必须引用有效对象。如果该对象稍后停止存在,则使用该引用为UB,因此正在初始化对现在不存在的引用的另一个引用。

以下更简单的例子提供与您的问题完全相同的困境,我认为:

std::reference_wrapper<T> r;

{
    T t;
    r = std::ref(t);
}

// #1

在#1,r内的引用不再有效,但程序正常。只是不要阅读 r

在你的例子中,第1行很好,第2行没有 - 这是因为原来的第2行用参数A::A(A const &)调用foo(),并且如上所述,这失败了使用有效引用初始化函数参数变量,编辑后的版本A const & a = foo();也是如此。

答案 2 :(得分:0)

我会说#3。单独,#2实际上并没有做任何事情,即使引用的对象已经超出范围。这不是一个与标准相关的问题,因为它是连续出现两个错误的结果:

  1. 返回对超出范围的对象的引用,后跟
  2. 使用参考文献。
  3. 要么孤立地定义了行为。标准是否有关于在其生命周期结束时使用对象的引用的说法是另一回事。