#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)
编译器在做什么?
简短的回答:对于普通类型,“默认构造”的两种不同形式导致两种不同的初始化:
T a;
在这种情况下,对象是默认初始化的。它的值是未确定的,未定义的行为很快就会发生(这就是初始化的方式b.mem1
以及 valgrind 检测到错误的原因。)T a=T();
在这种情况下,对象被值初始化并且其整个内存被清零(这就是a.mem1
and发生的情况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中,直接由纯右值表达式初始化。T1
T(1)
T(1).mem1
b
T(1)
内部这个未确定值的评估printf
也是独立于 C++ 标准的未定义行为。这就是 valgrind 抱怨的原因以及当您更改if (true)
为if (false)
.
(1) 严格来说a
是从c++11中的值初始化对象复制构造的
(2) T1 的默认构造函数不是用户提供的,因为它在第一个声明中被定义为默认构造函数