12
votes

In C++11, I have the following union:

union SomeData
{
    std::uint8_t Byte;
    std::uint16_t Word;
    std::uint32_t DWord;
    unsigned char String[128];
};

If I initialize the union thusly;

SomeData data {};

Is it guaranteed that the entire contents of the union will be "zero'd" out? Put another way; is an empty list-initializer of a union functionally equivalent to memset-ing the union to Zero?:

memset(&data, 0, sizeof(data));

In particular, I'm concerned about the string data. I'd like to ensure the entire length of the string contains zeros. It appears to work in my current compiler, but does the language of the spec guarantee this to always be true?

If not: is there a better way to initialize the full length of the union to zero?

2

2 Answers

7
votes

No, it is not guaranteed that the entire union will be zeroed out. Only the first declared member of the union, plus any padding, is guaranteed to be zeroed (proof below).

So to ensure the entire memory region of the union object is zeroed, you have these options:

  • Order the members such that the largest member is first and thus the one zeroed out.
  • Use std::memset or equivalent functionality. To prevent accidentally forgetting that, you can of course give SomeData a default constructor which will call this.

Quoting C++11:

8.5.4 [dcl.init.list]/3

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.

8.5 [dcl.init]/7

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.
  • ...
  • otherwise, the object is zero-initialized.

8.5 [dcl.init]/5:

To zero-initialize an object or reference of type T means:

...

  • if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero-initialized and padding is initialized to zero bits;

From these quotes, you can see that using {} to initialise data will cause the object to be value-initialized (since SomeData is a class type with a default constructor).

Value-initializing a union without a user-provided default constructor (which SomeData is) means zero-initializing it.

Finally, zero-initializing a union means zero-initializing its first non-static named data member.

6
votes

The entire union will be zeroed out. More exactly the first member of the union will be default initialized and all the remaining bytes in the union will be set to 0 as padding.

References (emphasize mine):

8.5 Initializers [dcl.init]
...

5 To zero-initialize an object or reference of type T means:
...
— if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero initialized and padding is initialized to zero bits;

That means that the first member of the union (here std::uint8_t Byte;) will be initialized to a 0 and that all other bytes in the union will be set to 0 because they are padding bytes.


But beware. As stated by Angew "padding" is wonderfully underspecified in the standard and a C compiler could interpret that the padding bytes in a union are only the bytes that follow the largest member. I would really find that weird because compatibility changes are specifically documented and previous versions (C) first initialized everything to 0 and next did specific initialization. But a new implementer could not be aware of it...

TL/DR: I really think that the intent of the standard is that all bytes in the union are set to 0 in OP's example, but for a mission critical program, I would certainly add an explicit 0 constructor...