Default Initialization of Pod Types in C++

Default initialization of POD types in C++

Local variables with automatic storage duration are not being initialized automatically. Since using uninitialized variables produces undefined behavior, it is a good practice to explicitly initialize your variables even when it's redundant.

About POD types that are being zero-initialized, C++03 standard 3.6.2 Initialization of non-local objects states:

§1 Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Objects of POD types (3.9) with static storage duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization takes place.

So it's guaranteed by standard that POD types with static storage duration (whatever their scope is) will be zero-initialized.

POD members of a class (without explicit initialization in a constructor)

This situation is described in 12.6.2 Initializing bases and members, that states (selected parts):

If a given nonstatic data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then:

— If the entity is a nonstatic data member..., and the entity class is a non-POD class, the entity is default-initialized (8.5)...

Otherwise, the entity is not initialized...

After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor’s mem-initializers, nor default-initialized, nor value-initialized, nor given a value during execution of the body of the constructor, the member has indeterminate value.

Example:

class C
{
public:
C(int x, int z) : x(x), z(z) { }
int x, y, z;
};

int main(void)
{
C* c = new C(1,3);
std::cout << c->y; // value of y is undetermined !!!
}

Default initialization of POD vs. non-POD class types

From 8.5.9 of the 2003 standard:

If no initializer is specified for an object, and the object is of
(possibly cv-qualified) non-POD class type (or array thereof), the
object shall be default-initialized; if the object is of
const-qualified type, the underlying class type shall have a
user-declared default constructor. Otherwise, if no initializer is
specified for a nonstatic object, the object and its subobjects, if
any, have an indeterminate initial value
); if the object or any of
its subobjects are of const-qualified type, the program is ill-formed.

The class you show is a POD, so the highlighted part applies, and your object will not be initialized at all (so section 8.5/5, which you quote, does not apply at all).

Edit: As per your comment, here the quote from section 8.5/5 of the final working draft of the current standard (I don't have the real standard, but the FDIS is supposedly very close):

To default-initialize an object of type T means:

— if T is a (possibly
cv-qualified) class type (Clause 9), the default constructor for T is
called (and the initialization is ill-formed if T has no accessible
default constructor);

— if T is an array type, each element is
default-initialized;

otherwise, no initialization is performed.

What is instantiated when a POD structure is declared without using a default constructor?

default-initialize an object of type T means:

... otherwise, the object is zero-initialized.

No.

Your first linked answer is correct about C++11 onwards, and this is called default-initialization. It's different to default-construction or value initialization.

The second linked answer was probably correct for C++03, but is wrong for C++11 onwards (even though it was written in 2012). I don't have a copy of the '03 standard to verify, and it was a long time ago.

The effects of default initialization are:

  • if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;

  • if T is an array type, every element of the array is default-initialized;

  • otherwise, nothing is done: the objects with automatic storage duration (and their subobjects) are initialized to indeterminate values.


Local copy of N4659 agrees with the summary above:

11.6 Initializers [dcl.init]

...

(7.3) Otherwise, no initialization is performed

The section on new-expressions even refers to 11.6 and then says

Note: If no initialization is performed, the object has an indeterminate value.— end note


Current draft has

9.3 Initializers [dcl.init]

...

(7.3) Otherwise, no initialization is performed.

Zero-initialization of POD types

As written, this is aggregate initialization. The applicable rule is (§8.5.1 [dcl.init.aggr]/p7):

If there are fewer initializer-clauses in the list than there are
members in the aggregate, then each member not explicitly initialized
shall be initialized from its brace-or-equal-initializer or, if
there is no brace-or-equal-initializer, from an empty initializer
list (8.5.4).

The relevant parts of §8.5.4 [dcl.init.list]/p3 is:

List-initialization of an object or reference of type T is defined
as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1).
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is
    value-initialized.
  • [irrelevant items omitted]
  • Otherwise, if the initializer list has no elements, the object is value-initialized.

In short, sub-aggregates are recursively aggregate-initialized from an empty initializer list. Everything else is value-initialized. So the end result is everything being value-initialized, with everything being a POD, value-initialization means zero-initialization.


If T is POD but not an aggregate, then aggregate initialization doesn't apply, so you hit the second bullet point in §8.5.4 [dcl.init.list]/p3, which results in value-initialization of the entire object instead. POD classes must have a trivial (and so not-user-provided) default constructor, so value-initialization for them means zero-initialization as well.

Class POD members default-initialization vs. zero-initialization vs. no-initialization?

  1. In default initialization, basic "C"-style types (int, double, char, bool, etc.) have indeterminate values. That is, there is no undefined behavior, but the values could be anything.

  2. If a POD member is not initialized in the constructor nor via C++11 in-class initialization, it is default-initialized.

  3. The answer is the same regardless of stack or heap.

  4. In C++98 (and not afterward), new int() was specified as performing zero initialization.

Reference: http://en.cppreference.com/w/cpp/language/default_initialization

Why is a POD in a struct zero-initialized by an implicit constructor when creating an object in the heap or a temporary object in the stack?

It's expected behaviour. There are two concepts, "default initialization" and "value initialization". If you don't mention any initializer, the object is "default initialized", while if you do mention it, even as () for default constructor, the object is "value initialized". When constructor is defined, both cases call default constructor. But for built-in types, "value initialization" zeroes the memory whereas "default initialization" does not.

So when you initialize:

Type x;

it will call default constructor if one is provided, but primitive types will be uninitialized. However when you mention an initializer, e.g.

Type x = {}; // only works for struct/class without constructor
Type x = Type();
Type x{}; // C++11 only

a primitive type (or primitive members of a structure) will be VALUE-initialized.

Similarly for:

struct X { int x; X(); };

if you define the constructor

X::X() {}

the x member will be uninitialized, but if you define the constructor

X::X() : x() {}

it will be VALUE-initialized. That applies to new as well, so

new int;

should give you uninitialized memory, but

new int();

should give you memory initialized to zero.
Unfortunately the syntax:

Type x();

is not allowed due to grammar ambiguity and

Type x = Type();

is obliged to call default constructor followed by copy-constructor if they are both specified and non-inlineable.

C++11 introduces new syntax,

Type x{};

which is usable for both cases. If you are still stuck with older standard, that's why there is Boost.ValueInitialized, so you can properly initialize instance of template argument.

More detailed discussion can be found e.g. in Boost.ValueInitialized documentation.

Meaning of default initialization changed in C++11?

The final effects are almost the same. In C++03, the use of default-initialize was restricted to non-POD class type, so the last point never applied. In C++11, the standard simplifies the wording by eliminating the condition with regards to where default-initialization was used, and changes the definition of default-initialization to cover all of the cases in a way to correspond what happened before.

When do C++ POD types get zero-initialized?

Assuming you haven't modified a before calling test(), a has a value of zero, because objects with static storage duration are zero-initialized when the program starts.

d[0] has a value of zero, because the constructor invoked by std::vector<int> d(1) has a second parameter that takes a default argument; that second argument is copied into all of the elements of the vector being constructed. The default argument is T(), so your code is equivalent to:

std::vector<int> d(1, int());

You are correct that b has an indeterminate value.

f.a and *c both have indeterminate values as well. To value initialize them (which for POD types is the same as zero initialization), you can use:

Foo f = Foo();      // You could also use Foo f((Foo()))
int* c = new int(); // Note the parentheses


Related Topics



Leave a reply



Submit