Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does delegating to the default constructor not zero initialize member variable

Tags:

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);
  }
}

When I compile this with gcc5.4, I get the following output:

g++ -std=c++11 -O3 test.cpp -o test && ./test
a._mem1=0 a._mem2=0
b._mem1=382685824 b._mem2=1

Why does the user defined constructor, which delegates to the default constructor not manage to set _mem1 to zero for b, however a which uses the default constructor is zero initialized?

Valgrind confirms this also:

==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)

If I change if(false) to if(true)

Then the output is as you would expect

a._mem1=0 a._mem2=0
b._mem1=0 b._mem2=1
c._mem1=0 c._mem2=0
d._mem1=0 d._mem2=1

What is the compiler doing?

like image 794
jonynz Avatar asked Jul 11 '18 09:07

jonynz


People also ask

Does default constructor zero initialize?

For built-in types it results in zero-initialization.

Should a constructor initialize all members?

The safest approach is to initialise every variable at the point of construction. For class members, your constructors should ensure that every variable is initialised or that it has a default constructor of its own that does the same.

Are class members initialized to zero?

If T is scalar (arithmetic, pointer, enum), it is initialized from 0 ; if it's a class type, all base classes and data members are zero-initialized; if it's an array, each element is zero-initialized.

What is a delegating constructor?

Delegating constructors. Constructors are allowed to call other constructors from the same class. This process is called delegating constructors (or constructor chaining). To have one constructor call another, simply call the constructor in the member initializer list.


1 Answers

Short answer: for trivial types, the two distinct forms of "default construction" leads to two different initializations:

  • T a; in which case the object is default-initialized. Its value is undetermined and undefined behavior will soon happen (this is how is initialized b.mem1 and why valgrind detect an error.)
  • T a=T(); in which case the object is value-initialized and its entire memory is zeroed (this is what happens to a.mem1 and a.mem2)

Long answer: Actualy, the default constructor of T1 is not the cause of zero initialization of a.mem1. a has been first zero-initialized but not b because of a singular rule of the standard that does not apply for b's initializer.

The definition volatile a=T() causes a to be value-initialized (1). struct T1 as no user-provided default constructor (2). For such a struct the entire object is zero-initialized as stated by this rule of the C++11 standard [dcl.init]/7.2:

if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.


There is a subtle difference between C++11 and C++17 that causes the definition volatile b=T(1) to be undefined behavior in C++11 but not in C++17. In C++11, b is initialized by copying an object type T1 which is initialized by the expression T(1). This copy construction evaluate T(1).mem1 which is an undetermined value. This is forbidden. In c++17, b is directly initialized by the prvalue expression T(1).

The evaluation of this undetermined value inside the printf is also undefined behavior independently of the c++ standard. This is why valgrind complains and why you see inconsistent outputs when you change if (true) to if (false).

(1) strictly speaking a is copy constructed from a value-initalized object in c++11

(2) T1's default constructor is not user provided because it is defined as defaulted on the first declaration

like image 164
Oliv Avatar answered Sep 27 '22 19:09

Oliv



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!