初始化表达式可以使用变量本身吗?

时间:2015-11-11 11:14:09

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

请考虑以下代码:

#include <iostream>

struct Data
{
    int x, y;
};

Data fill(Data& data)
{
    data.x=3;
    data.y=6;
    return data;
}

int main()
{
    Data d=fill(d);
    std::cout << "x=" << d.x << ", y=" << d.y << "\n";
}

此处dfill()的返回值进行了初始化,但fill()会在返回结果之前写入d。我关注的是d在初始化之前是非常简单的,在某些(所有?)情况下使用未初始化的变量会导致未定义的行为。

这段代码有效,还是有未定义的行为?如果它有效,一旦Data停止为POD或某些其他情况,行为是否会变为未定义?

3 个答案:

答案 0 :(得分:13)

这似乎不是有效的代码。它类似于问题中概述的情况:Is passing a C++ object into its own constructor legal?,尽管在这种情况下代码是有效的。机制并不完全相同,但基本推理至少可以让我们开始。

我们从defect report 363开始询问:

  

如果是这样,UDT的自我初始化的语义是什么?   例如

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }
     

可以编译和打印:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

并且提议的决议是:

  

3.8 [basic.life]第6段表明此处的引用是有效的。允许在它之前获取类对象的地址   已完全初始化,并允许将其作为参数传递给   参考参数,只要引用可以直接绑定。   [...]

因此虽然d未完全初始化,但我们可以将其作为参考传递。

我们开始遇到麻烦的地方就在这里:

data.x=3;

草案C ++标准部分3.8缺陷报告引用的相同部分和段落)说(强调我的):

  

同样,在对象的生命周期开始之前但在之后   对象占用的存储空间已被分配,或之后   对象的生命周期已经结束,在存储之前就已经存在了   对象占用被重用或释放,任何引用的glvalue   可以使用原始对象,但仅限于有限的方式。对于一个对象   正在建设或破坏,见12.7。否则,这样的glvalue   指分配存储(3.7.4.2),并使用的属性   不依赖于其值的glvalue是明确定义的。该程序   在以下情况下具有未定义的行为:

     
      
  • 左值到右值的转换(4.1)适用于这样的glvalue,

  •   
  • glvalue用于访问非静态数据成员或调用非静态成员函数   对象,或

  •   
  • glvalue绑定到对虚基类的引用(8.5.3),或

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

  •   

那么访问是什么意思?用defect report 1531澄清了这一点,将访问定义为:

  

访问

     
    

读取或修改对象的值

  

所以fill 访问非静态数据成员,因此我们有未定义的行为。

这也同意12.7部分的说法:

  

[...]形成指向(或   访问对象obj的直接非静态成员的值,obj的构造应该已经开始   并且它的销毁不应该完成,否则计算指针值(或访问   成员值)导致未定义的行为。

由于您无论如何都在使用副本,因此您也可以在fill内创建一个数据实例并对其进行初始化。你不必通过d

正如T.C.所指出的那样。明确引用生命周期开始时的详细信息非常重要。来自3.8部分:

  

对象的生命周期是对象的运行时属性。一个   如果对象属于类,则称其具有非平凡的初始化   或者聚合类型,它或它的一个成员由a初始化   除了一个普通的默认构造函数之外的构造函数。 [ 注意:   通过简单的复制/移动构造函数进行初始化是非常重要的   初始化。 - 尾注] T类对象的生命周期   从以下时间开始:

     
      
  • 获得具有类型T的正确对齐和大小的存储,并且

  •   
  • 如果对象具有非平凡的初始化,则其初始化完成。

  •   

由于我们通过复制构造函数进行初始化,因此初始化非常重要。

答案 1 :(得分:3)

我没有看到问题。访问未初始化的整数成员是有效的,因为您正在访问以进行编写。 阅读它们会导致UB。

答案 2 :(得分:-5)

我认为这是有效的(疯狂但有效)。

这既合法又符合逻辑:

Data d ;

d = fill( d ) ;

事实是这种形式是相同的:

Data d = fill( d ) ;

就语言的逻辑结构而言,这两个版本是等价的。

因此,该语言在法律上和逻辑上都是正确的。

但是,正如我们通常希望人们在创建变量时将变量初始化为默认值(为安全起见),这是糟糕的编程习惯

有趣的是,g ++ -Wall编译此代码时没有模糊。