Does C++ standard guarantee the initialization of padding bytes to zero for non-static aggregate objects?

2k Views Asked by At

Does C++ support a language construct that will allow us to initialize an object and all its padding fields to zero. I found some encouraging wording in cppreference.com about zero-initialization that suggests that on some conditions, the padding bytes will also be zeroed out.

Quoting from cppreference.com: zero-initialization

Zero initialization is performed in the following situations:

  1. As part of value-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.

The effects of zero initialization are:

  • If T is a scalar type, the object's initial value is the integral constant zero explicitly converted to T.
  • If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits. The constructors, if any, are ignored.
  • ...

One will find references to zero-initialization in value-initialization, aggregate-initialization and list-initialization.

I tested using fairly latest GCC and clang C++ compilers, and their behavior seems divergent.

Frankly, I tried hard to parse these rules, especially given that the divergent compiler behavior, I could not figure out how to interpret these rules correctly.

See code here (min C++11 is required). And here are the results:

Given: Foo

struct Foo
{
    char x;
    int y;
    char z;
};
Construct g++ clang++
Foo() x:[----][0x42][0x43][0x44],v: 0 x:[----][----][----][----],v: 0
y:[----][----][----][----],v: 0 y:[----][----][----][----],v: 0
z:[----][0x4A][0x4B][0x4C],v: 0 z:[----][----][----][----],v: 0
Foo{} x:[----][----][----][----],v: 0 x:[----][0x42][0x43][0x44],v: 0
y:[----][----][----][----],v: 0 y:[----][----][----][----],v: 0
z:[----][----][----][----],v: 0 z:[----][0x4A][0x4B][0x4C],v: 0

Here [----] represents a byte containing all bits 0, and [0x..] is garbage value.

As you can see the compiler outputs indicate that padding is not initialized. Both Foo() and Foo{} are value-initializations. In addition Foo{} is an aggregate-initialization, with missing initializers. Why isn't the zero-initialization rule getting triggered? Why isn't padding rule getting triggered?

I already understand that relying on padding bytes to be zero is not a good idea or may even be undefined behavior, but I think that is besides the point of this question.

  • Question 1: Does the standard provide a way to reliably initialize the padding bytes?
  • Question 2: Also see: does c initialize structure padding. Is it applicable?
  • Question 3: Are these compilers compliant with the standards?
  • Question 4: What is the explanation of the compiler's clearly divergent behavior?
1

There are 1 best solutions below

12
user17732522 On

The padding bits will be zeroed only if the class object is zero-initialized, as expressed in your quote.

For automatic and dynamic storage duration objects zero-initialization happens only if the object is value-initialized and it has a non-deleted implicit default constructor and no other user-provided default constructor. [dcl.init.general]/8.1 These conditions are fulfilled here.

Value-initialization should always happen with the () initializer. ([dcl.init.general]/16.4)

Value-initialization could also happen for {} as initializer. However, if the class is an aggregate as it is here, aggregate-initialization is preferred, which doesn't result in value-initialization. ([dcl.init.list]/3.4)

The preference of aggregate-initialization over value-initialization was changed by CWG 1301 before C++14, which may also be intended to apply to C++11. Before C++11 the rules may have been different, I haven't checked.


So I would say Clang is behaving correctly and GCC is wrong on Foo() while doing unnecessary work for Foo{} (although as noted by @PeterCordes below zeroing the whole object including the padding is actually more efficient).


Note that it is not completely clear to me whether inspecting the values of the non-zero-initialized padding bytes has well-defined behavior the way you are doing it.

For the default-initialized case reading the member has undefined behavior, because it's value will be indeterminate.

I expect that the padding is also supposed to have indeterminate values before new potentially initializes them. In that case inspecting their values if there is no zero-initialization would cause undefined behavior.