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:
- 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.
Update:
I should clarify that my answer and my comments below assume that padding having a certain state is meaningful to observable behavior in the first place, i.e. that, absent any other modification of the object or the complete object to which it belongs, the padding of the object will remain in its given state and can be, potentially, read back with that value.
However, the standard says practically nothing about the behavior of padding and going by information I could find from WG21, the current understanding of the standard seems to be that any padding always has unspecified value in C++. Therefore it would be pointless to ask whether or not the padding should be zeroed or not, as reading it back in any form would not need to produce zero again, effectively permitting the compiler to ignore any requirements to initializing padding in any way.
Notably that is different than in C, where the standard explicitly specified conditions under which padding might take on unspecified values and when it must be stable.
See e.g. the comment on CWG 2536.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With