在自己的初始化程序未定义的行为中访问结构的成员

时间:2016-05-24 09:32:25

标签: c++ initialization

考虑:

struct A { void do_something() {} };

struct B {
  A& a;
  B(A& a) : a{a} { a.do_something(); }
};

struct C {
  A a; B b;
  C(A& a) : b{a} {}
};

int main() {
  C c{ c.a };
}

这个可能可能会起作用,因为:

  • 即使在c初始化之前,我们也知道它的内存布局和c.a的地址
  • 我们实际上使用 c.a,直到初始化为止。

此外,我没有在几个不同的编译器下收到警告。

然而,我经历了一些非常奇怪的行为(稍后会发生),只能做一些未定义的事情,并且当我重新组织我的程序以避免这种模式时,它才会消失。

感谢。

3 个答案:

答案 0 :(得分:2)

您的程序具有未定义的行为,因为您正在访问对象外部的对象成员,并且在对象的生命周期开始之前。

  

$12.7: 1:对于具有非平凡构造函数的对象,   引用之前对象的任何非静态成员或基类   构造函数以未定义的行为开始执行结果...

至于为什么编译好:在声明之后可以使用名称...并引用C ++标准草案......(强调我的):

  

$3.3.2 : 1:名称的声明点是立即的   在完整的声明者(条款[dcl.decl])和之前之后   initializer(如果有的话)......

initalizer被定义为具有语法(部分复制):

  

初始化 ...

initializer:
    brace-or-equal-initializer
    ( expression-list )

brace-or-equal-initializer:
    = initializer-clause
    braced-init-list

initializer-clause:
    assignment-expression
    braced-init-list
. . .

上述原因与编译原因相同:

int k(k);
int m{m};
int b = b;

答案 1 :(得分:2)

除了我之前的回答,

你的代码是一个狡猾的代码...因为,尽管假设 UB在初始化之前使用了一个对象,代码的行为显然 定义良好。

如何? 在c的构造中,将发生以下事件序列:

  1. 您调用C的构造函数C(A& a) : b{a} {},该构造函数引用A类型的对象。 (引用就像一个地址,正如您正确提到的,c.a的地址在编译时是已知的)。您的电话是C c{ c.a };,编译器也可以,因为c.a是一个可访问的名称

  2. 由于C成员的声明顺序......

    struct C {
      A a; B b;
      C(A& a) : b{a} {}
    };
    

    对象ab之前初始化。

  3. 因此,a在成员初始化程序中使用之前就会生效... b{a}

  4. 但同样,你可以通过optimizers ...

    来吸烟

答案 2 :(得分:0)

C ++有一个非常明确的构造顺序。初始化第一(非虚拟)基类。接下来按照它们在类中声明的顺序初始化成员(并且忽略初始化列表的顺序),并且只有在最后一个成员被初始化之后才会输入构造函数的主体。 / p>

在这种情况下,这意味着按照A::A, B::B, C::C的顺序调用ctors。

如果C被声明为

会出现问题
struct C {
  B b; A a; 
  C(A& a) : b{a} {}
};