为什么委托默认构造函数不为零初始化成员变量

jon*_*ynz 7 c++11

#include <string>

struct T1 {
  int _mem1;
  int _mem2;
  T1() = default;
  T1(int mem2) : T1() { _mem2 = mem2; }
};
T1 getT1() { return T1(); }
T1 getT1(int mem2) { return T1(mem2); }
int main() {
  volatile T1 a = T1();
  std::printf("a._mem1=%d a._mem2=%d\n", a._mem1, a._mem2);
  volatile T1 b = T1(1);
  std::printf("b._mem1=%d b._mem2=%d\n", b._mem1, b._mem2);
  // Temporarily disable
  if (false) {
    volatile T1 c = getT1();
    std::printf("c._mem1=%d c._mem2=%d\n", c._mem1, c._mem2);
    volatile T1 d = getT1(1);
    std::printf("d._mem1=%d d._mem2=%d\n", d._mem1, d._mem2);
  }
}
Run Code Online (Sandbox Code Playgroud)

当我用gcc5.4编译它时,我得到以下输出:

g++ -std=c++11 -O3 test.cpp -o test && ./test
a._mem1=0 a._mem2=0
b._mem1=382685824 b._mem2=1
Run Code Online (Sandbox Code Playgroud)

为什么用户定义的构造函数(委托给默认构造函数)没有设法将_mem1设置为零b,但是a哪个使用默认构造函数是零初始化?

Valgrind也证实了这一点:

==12579== Conditional jump or move depends on uninitialised value(s)
==12579==    at 0x4E87CE2: vfprintf (vfprintf.c:1631)
==12579==    by 0x4E8F898: printf (printf.c:33)
==12579==    by 0x4005F3: main (in test)
Run Code Online (Sandbox Code Playgroud)

如果我将if(false)更改为if(true)

然后输出就像你期望的那样

a._mem1=0 a._mem2=0
b._mem1=0 b._mem2=1
c._mem1=0 c._mem2=0
d._mem1=0 d._mem2=1
Run Code Online (Sandbox Code Playgroud)

编译器在做什么?

Oli*_*liv 2

简短的回答:对于普通类型,“默认构造”的两种不同形式导致两种不同的初始化:

  • T a;在这种情况下,对象是默认初始化的。它的值是未确定的,未定义的行为很快就会发生(这就是初始化的方式b.mem1以及 valgrind 检测到错误的原因。)
  • T a=T();在这种情况下,对象被值初始化并且其整个内存被清零(这就是a.mem1and发生的情况a.mem2

长答案:实际上,默认构造函数T1并不是a.mem1初始化为零的原因。a已首先进行零初始化,但这并不是b因为标准的单一规则不适用于 的b初始化程序。

该定义volatile a=T()导致a被初始化(1)。因为没有用户提供的默认构造函数(2)。对于这样的结构体,整个对象是零初始化的,如 C++11 标准[dcl.init]/7.2的规则所述: struct T1

如果 T 是一个(可能是 cv 限定的)非联合类类型,没有用户提供的构造函数,则该对象将被零初始化,并且如果 T 的隐式声明的默认构造函数不平凡,则调用该构造函数。


C++11 和 C++17 之间存在细微差别,导致定义在 C++11 中volatile b=T(1)未定义行为,但在 C++17 中则不然。在 C++11 中,通过复制由表达式 初始化的b对象类型来初始化。该复制构造评估这是一个未确定的值。这是禁止的。在c++17中,直接由纯右值表达式初始化。T1T(1)T(1).mem1bT(1)

内部这个未确定值的评估printf也是独立于 C++ 标准的未定义行为。这就是 valgrind 抱怨的原因以及当您更改if (true)if (false).

(1) 严格来说a是从c++11中的值初始化对象复制构造的

(2) T1 的默认构造函数不是用户提供的,因为它在第一个声明中被定义为默认构造函数