Value initialization: default initialization or zero initialization?
Asked Answered
C

1

20

I have templated gray_code class which is meant to store some unsigned integer whose underlying bits are stored in Gray code order. Here it is:

template<typename UnsignedInt>
struct gray_code
{
    static_assert(std::is_unsigned<UnsignedInt>::value,
                  "gray code only supports built-in unsigned integers");

    // Variable containing the gray code
    UnsignedInt value;

    // Default constructor
    constexpr gray_code()
        = default;

    // Construction from UnsignedInt
    constexpr explicit gray_code(UnsignedInt value):
        value( (value >> 1) ^ value )
    {}

    // Other methods...
};

In some generic algorithm, I wrote something like this:

template<typename UnsignedInt>
void foo( /* ... */ )
{
    gray_code<UnsignedInt> bar{};
    // Other stuff...
}

In this piece of code, I expected bar to be zero-intialized and therefore bar.value to be zero-initialized. However, after having struggled with unexpected bugs, it appears that bar.value is initialized with garbage (4606858 to be exact) instead of 0u. That surprised me, so I went to cppreference.com to see what the line above was exactly supposed to do...


From what I can read, the form T object{}; corresponds to value initialization. I found this quote interesting:

In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.

However, gray_code has a user-provided constructor. Therefore it is not an aggregate thus aggregate initialization is not performed. gray_code has no constructor taking an std::initializer_list so list initialization is not performed either. The value-initialized of gray_code should then follow the usual C++14 rules of value initialization:

1) If T is a class type with no default constructor or with a user-provided default constructor or with a deleted default constructor, the object is default-initialized.

2) If T is a class type without a user-provided or deleted default constructor (that is, it may be a class with a defaulted default constructor or with an implicitly-defined one) then the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor.

3) If T is an array type, each element of the array is value-initialized.

4) Otherwise, the object is zero-initialized.

If I read correctly, gray_code has an explicitly defaulted (not user-provided) default constructor, therefore 1) does not apply. It has a defaulted default constructor, so 2) applies: gray_code is zero-initialized. The defaulted default constructor seems to meet all the requirements of a trivial default constructor, so default initialization should not happen. Let's have a look then at how gray_code is zero-initialized:

  • If T is a scalar type, the object's initial value is the integral constant zero implicitly 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.

  • If T is a union type, the first non-static named data member is zero-initialized and all padding is initialized to zero bits.

  • If T is array type, each element is zero-initialized

  • If T is reference type, nothing is done.

gray_code is a non-union class type. Therefore, all of its non-static data members should be initialized which means that value is zero-initialized. value satisfies std::is_unsigned and is therefore a scalar type, which means that it should be initialized with "the integral constant zero implicitly converted to T".

So, if I read correctly all of that, in the function foo above, bar.value should always be initialized with 0 and it should never be initialized with garbage, am I right?

Note: the compiler I compiled my code with is MinGW_w4 GCC 4.9.1 with (POSIX threads and dwarf exceptions) in case that helps. While I sometimes get garbage on my computer, I never managed to get anything else than zero with online compilers.


Update: It seems to be a GCC bug that the error is mine and not that of my compiler. Actually, when writing this question, I assumed for the sake of simplicity that

class foo {
    foo() = default;
};

and

class foo {
    foo();
};

foo::foo() = default;

were equivalent. They are not. Here is the quote from the C++14 standard, section [dcl.fct.def.default]:

A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

In other words, when I got garbage values, my defaulted default constructor was indeed user-provided since it was not explicitly efaulted on its first declaration. Therefore, what happened was not zero initialization but default initialization. Thanks @Columbo again for pointing out the real problem.

Christoper answered 2/11, 2014 at 13:20 Comment(11)
[dcl.fct.def.default] / 5 "a function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration". Your constructor is not user-provided.Algia
You could have written UnsignedInt value{0} instead of reading pages and pages ... I know you know. ;-)Pry
a note... value initialization STILL doesn't work on Microsoft's compiler as of VS2013Querida
"gray_code has no constructor taking an std::initializer_list so list initialization is not performed either" - actually list-initialization means any time that you use a brace-enclosed list to initialize anything . gray_code<UnsignedInt> bar{}; is list-initialization.Minter
It's really icky (IMHO) that T() {} behaves differently to T() = default;Minter
@MattMcNabb You're right. I read the article about value-initialization first and the syntax matched what I wrote so I assumed that it was value-iitialization and didn't see that lisst initialization actually came "first" in the wording.Christoper
Doesn't show this question, how really ugly these initializer rules are? What especially happens from c++03 to c++11, the code which was expected to do value-initialization is no more doing the same thing in c++11?Inclose
@Inclose It only shows that = default, added in C++11, has some strange rules. The code doing value-initialization in C++03 still performs value-initialization in C++11.Christoper
@ Morween, thanks, so = default strange rules is only: A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.Inclose
@Inclose Yeah, basically what I find strange is that it causes = default to behave differently depending on whether it is or not in the class declaration.Christoper
The behaviour above is just crude, :-) hopefully the std committee has some good reasons for this strange rule. It is a totally illogical exception (at least for me)Inclose
W
10

So, if I read correctly all of that, in the function foo above, bar.value should always be initialized with 0 and it should never be initialized with garbage, am I right?

Yes. Your object is direct-list-initialized. C++14's* [dcl.init.list]/3 specifies that

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

  • [… Inapplicable bullet points…]

  • Otherwise, 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.

  • […]

Your class isn't an aggregate since it has user-provided constructors, but it does have a default constructor. [dcl.init]/7:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;

  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

[dcl.fct.def.default]/4:

A special member function is user-provided if it is user-declared and not explicitly defaulted […] on its first declaration.

So your constructor is not user-provided, therefore the object is zero-initialized. (The constructor is not called since its trivial)

And finally, in case this was not clear, to zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;

  • 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;

  • […]


Thus either

  • Your compiler is bugged

  • …or your code triggers undefined behavior at some other point.


* The answer is still yes in C++11, though the quoted sections are not equivalent.

Whimsy answered 2/11, 2014 at 14:3 Comment(7)
I looked at the generated assembly. The default constructor has one meaningful instruction, mov %ecx,-0x4(%ebp). If I understand well, it sets the address of bar but never sets value.Christoper
@Christoper Of course the constructor doesn't set value. The implicitly generated default constructor default-initializes member objects which means no initialization is done for scalars.Whimsy
You're right, it seems that I got confused between the constructor and the intialization part. That said, the defaulted default constructor is called in my case. But nothing else near this call site seems to initialize value anyway.Christoper
@Christoper Yes, that is because of said bug.Whimsy
I really want to believe that this is a bug, but I am unable to produce a minimal test case where value is default-initialized instead of zero-initialized. Thanks anyway for your help :)Christoper
Default-initialized means nothing is done for value. That said, you found a minimal test case yourself, didn't you?Whimsy
Let us continue this discussion in chat.Christoper

© 2022 - 2024 — McMap. All rights reserved.