Why is a member not getting zero-initialized in this example?

  • A+
Category:Languages

This is specifically regarding C++11:

#include <iostream> struct A {     A(){}     int i; }; struct B : public A {     int j; }; int main() {     B b = {};     std::cout << b.i << b.j << std::endl; } 

Compiling with g++ 8.2.1:

$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp a.cpp: In function ‘int main()’: a.cpp:25:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]      std::cout << b.i << " " << b.j << std::endl 

gcc is detecting b.i as uninitialized, but I would think it should be getting zero-initialized along with b.j.

What I believe is happening (C++11 specifically, from the ISO/IEC working draft N3337, emphasis mine):

  • B is not an aggregate because it has a base class. Public base classes were only allowed in aggregates in C++17.
  • A is not an aggregate because it has a user-provided constructor

Section 8.5.1

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

  • b is getting list initialized with an empty braced-init-list

Section 8.5.4

List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).

  • This means b gets value-initialized
  • B has an implicitly-defined default constructor, so b value-initialization invokes zero-initialization
  • b.B::A gets zero-initialized, which zero-initalizes b.B::A.i, and then b.B::j gets zero-initialized.

Section 8.5

To zero-initialize an object or reference of type T means:
...
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;

...

To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
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.

However, it looks like gcc is saying only b.B::j is going to get zero-initialized. Why is this?

One reason I can think of is if B is being treated as an aggregate, which would initialize b.B::A with an empty list. B is certainly not an aggregate, though, because gcc rightly errors if we try to use aggregate initialization.

// ... as in the above example int main() {     B b = {A{}, 1};     std::cout << b.i << " " << b.j << std::endl; } 

Compiling with C++11

$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp a.cpp: In function ‘int main()’: a.cpp:10:18: error: could not convert ‘{A(), 1}’ from ‘<brace-enclosed initializer list>’ to ‘B’      B b = {A{}, 1}; 

Compiling with C++17

g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp a.cpp: In function ‘int main()’: a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]      std::cout << b.i << " " << b.j << std::endl; 

And we can see that b.i is uninitialized because B is an aggregate, and b.B::A is getting initialized by an expression that itself leaves A::i uninitialized.

So it's not an aggregate. Another reason is if b.B::j is getting zero-initialized, and b.B::A is getting value-initialized, but I don't see that anywhere in the specs.

The last reason is if an older version of the standard was getting invoked. From cppreference:

2) if T is a non-union class type without any user-provided constructors, every non-static data member and base-class component of T is value-initialized; (until C++11)

In this case, both b.B::i and b.B::A would be value-initialized, which would cause this behavior, but that is marked as "(until C++11)".

 


For any class, if there is a single user-defined constructor it must be used, and A(){} does not initialise i.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: