Why doesn't initializing a C++ struct to `= {0}` set all of its members to 0?
Asked Answered
V

3

10

After doing a ton of testing and writing this answer on how to initialize a struct to zero in C++ (note: the downvote on it was before my total rewrite of it), I can't understand why = {0} doesn't set all members of the struct to zero!

If you do this:

struct data_t
{
    int num1 = 100;
    int num2 = -100;
    int num3;
    int num4 = 150;
};

data_t d3 = {0};
printf("d3.num1 = %i\nd3.num2 = %i\nd3.num3 = %i\nd3.num4 = %i\n\n",
       d3.num1, d3.num2, d3.num3, d3.num4);

...the output is:

d3.num1 = 0
d3.num2 = -100
d3.num3 = 0
d3.num4 = 150

...although I expected the output to be this:

d3.num1 = 0
d3.num2 = 0
d3.num3 = 0
d3.num4 = 0

...which means that only the FIRST member was set to zero, and all the rest were set to their defaults.

I was always under the impression that initializing a struct in any of these 3 ways would zero-initialize it, but obviously I'm wrong!

  1. data_t d{}
  2. data_t d = {}
  3. data_t d = {0}

My key takeaway from this answer is therefore this:

The big take-away here is that NONE of these: data_t d{}, data_t d = {}, and data_t d = {0}, actually set all members of a struct to zero!

  1. data_t d{} sets all values to their defaults defined in the struct.
  2. data_t d = {} also sets all values to their defaults.
  3. And data_t d = {0} sets only the FIRST value to zero, and all other values to their defaults.

So, why doesn't initializing a C++ struct to = {0} set all of its members to 0?

Note that my key take-aways above actually contradict this rather official-looking documentation I've been using for years (https://en.cppreference.com/w/cpp/language/zero_initialization), which says that T t = {} ; and T {} ; are both zero initializers, when in fact, according to my tests and take-away above, they are NOT.

References:

  1. How to initialize a struct to 0 in C++
  2. Update: I was just pointed to this reference too: What does {0} mean when initializing an object?
Vittorio answered 30/4, 2020 at 20:12 Comment(18)
Why should it? You're only giving one value, not values for all properties.Bred
Is this a question or a question with an answer baked in? You should split the answer out into the answer section below.Bred
@tadman, that's a quote from a different answer of mine. I don't consider it an answer by itself.Vittorio
@tadman: The latter part of the question states what apparently happens. It does not answer the question asked, why.Fanatic
Also, I've been told before that = {0} does zero-initialize all members of a struct, and have believed it for years now, mistakenly, apparently. Also, this rather official-looking documentation I've been using for years (en.cppreference.com/w/cpp/language/zero_initialization) says that T t = {} ; and T {} ; are both zero initializers, when in fact, according to my tests and take-away above, they are NOT.Vittorio
It's like the wool just fell off of my eyes and I'm running around in circles now not sure if what I'm seeing is real...Vittorio
@GabrielStaples: "this rather official-looking documentation [...] says that T t = {} ; and T {} ; are both zero initializers" No, it doesn't. It says that these syntaxes can perform zero initialization, but that doesn't mean they always do.Assiduity
your typedef in redundant, all classes and structs in c++ are typesTanguay
@pm100, agreed. It's intentional. I originally wrote the code to be tested in both C and C++, and that's what my project does here.Vittorio
Your struct members have initializers already. Your question sounds like it's about why overriding initializers doesn't work.Hewe
@NicolBolas, you are right. So, also agreed. I copy-and-pasted my C code to compile it in C++, and ensured I did not have default values for the code to compile in C. I guess what I'm saying is: I started with C styles then started splitting into C and C++, leaving some of the C syntax as carry-over into the C++.Vittorio
@GabrielStaples Your life will become easier if you think of C and C++ like C# and Java. They may look similar, but they are two different languages and don't work the same. This only gets more true as the years go one and C and C++ diverge even more.Crackerjack
@pm100, @NicolBolas, I can see this is causing too much distraction, so I've updated the question to not unnecessarily use typedef on the struct definition since this is C++, even though I originally started this whole struct initialization stuff in C then ported it to C++ slowly while I was testing.Vittorio
Hey all, feel free to take a look at this answer too and provide feedback to it or write your own answers there. I think it'd be helpful to complete the picture.Vittorio
I can't understand why you can't understand that = {0} sets member variables to zero unless they have a default member initializer, as of C++11.Endanger
@Eljay, because that's not what I was taught. I asked a senior developer to me a few years back and I got an answer that "= {0} zero-initializes the entire object". That led me to believe for years that myStruct data default-initialized, according to any defaults set in the struct definition, and (perhaps--wasn't sure) zero-initialized just the members that didn't have defaults, and myStruct data = {0} zero-initialized all members, period, no exceptions. I was wrong. I'm glad I understand now.Vittorio
The senior developer was correct for C and for before C++11. Since C++11, the rules have changed slightly to accommodate default member initializer.Endanger
@Eljay, at the time the more senior developer told me these things, I was essentially a C programmer working in a large C++ code base. The C++ code was most likely C++14 or C++17, and at the absolute oldest was C++11, so unfortunately, I don't think he understood the full truth about what = {0} was really doing at the time either, hence my statement that many veteran developers probably misunderstand the details of what it's doing. I've had other discussions with people since then and not once has someone said what I learned here today. Literally, I just went from confusion to enlightenment.Vittorio
C
11

So, why doesn't initializing a C++ struct to = {0} set all of its members to 0?

Because you are only providing one value, while the class has more than one member.

When you have T t{}; or T t = {} what you are doing is called value initialization. In value initialization, if the object/member does not have a default constructor, or a default member initializer, then the compiler falls back to zero initializing the objec/member. So with

data_t d{}

the value of the members in order would be 100, -100, 0 ,150 and that 0 for num3 happens because it has no default and you did not provide a value in the {} so the compiler falls back to zero initializing num3. This is the same with data_t d = {}. With data_t d = {0} you provide the first element, so num1 is 0, but then like the first two, all of the other members are initialized with their default value if they have one, or zero initialized if they don't, giving you 0, -100, 0, 150 for the member values.

This was a change that happened when C++11 was released and allowed for default member initializers.


If your data_t was defined like

typedef struct
{
    int num1;
    int num2;
    int num3;
    int num4;
} data_t;

then data_t d{}, data_t d = {}, data_t d = {0} would all leave you with a zero initialized class since there are no default member initializers and the only value you provide in you braced-init-list (the technical name for {...}) is zero so all members become zero.

Crackerjack answered 30/4, 2020 at 20:24 Comment(4)
Perhaps it was classic Stack Overflow "strategic downvoting" :(. Thanks for this answer. It's by far the most clear, so I'm marking it as the solution. See my comments under the other answer too, as they summarize pretty much what you just said. This was very enlightening, I had no idea there were multiple layers of subtleties going on.I think struct initialization is an extremely misunderstood topic, even by veteran programmers. I hope you take a look at my other Q & A too, as you have some great insights: How to initialize a struct to 0 in C++.Vittorio
If you just call data_t d; does value initialization take place? Default values still get set, as part of the default constructor, but I suppose any members without default values will NOT get initialized, correct? They will just have whatever data was sitting in the RAM at that address. This is opposed to data_t d{}; or data_t d = {};, or I believe also data_t d = data_t();, where members with no default value will get set to zero. Can you confirm my understanding is correct?Vittorio
@GabrielStaples data_t d; is called default initializationCrackerjack
Thanks. This documentation is very hard to read though, but the salient points I picked out for default initialization, to answer my own question, are 1) the defaults for each struct member are used, 2) if no defaults are available, "objects with automatic storage duration (and their subobjects) are initialized to indeterminate values", meaning the struct member values with no defaults are left uninitialized and could be anything, except in the case of 3) "static and thread-local objects [structs in this case]", which "get zero initialized."Vittorio
S
8

data_t d3 = {0} is list-initialization syntax which, with aggregates such as data_t, performs aggregate initialization: the provided 0 value is used to initialized the first member and the remaining members are initialized using their corresponding defaults, and if none exist, are value-initialized (emphasis mine, edited for C++14):

If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized by their default member initializers, if provided in the class definition, and otherwise by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.

value-initialization means zero-initialization for non-class types. That is why the member num3, which has no default value, gets the value 0.

Note: this is not to be confused with default-initialization, which does not initialize non-class types at all. data_t d3; would be default-initialization, and the member num3 would be left in an indeterminate state.

The important point to keep an eye out for is whether or not the object being initialized is an aggregate because the initialization rules are different for aggregates vs. classes with a constructor. In case of a constructor, non-class members without a default value will be default-initialized (i.e. left in an indeterminate state).

Some examples:

struct A { // an aggregate
    int num1 = 100;
    int num2 = -100;
    int num3;
};

struct B { // not an aggregate
    int num1 = 100;
    int num2 = -100;
    int num3;
    B() {}
    B(int) {}
};

int main() {
    A a1; // default-initialization: a1 is {100, -100, ???}
    A a2 = {}; // aggregate initialization: a2 is {100, -100, 0}
    A a3 = { 1 }; // aggregate initialization: a3 is {1, -100, 0}
    A a4 = { 1,2,3 }; // aggregate initialization: a4 is {1, 2, 3}
    B b1; // default-initialization: b1 is {100, -100, ???}
    B b2 = {}; // copy-list-initialization invoking B::B(): b2 is {100, -100, ???}
    B b3 = { 1 }; // copy-list-initialization invoking B::B(int): b3 is {100, -100, ???}
    B b4 = { 1,2,3 }; // error: no B constructor taking (int,int,int)
}

Note also that aggregate initialization rules predate C++11. See for example this related pre-C++11 question: What does {0} mean when initializing an object?

Sourdine answered 30/4, 2020 at 20:24 Comment(6)
That last link (What does {0} mean when initializing an object?) is very helpful. So, I've discovered the missing link in my understanding. I'll bold the part that's new to me: Initializing a struct with = {0} zero-initializes the first member of the struct, and default-initializes all other members of the struct. This means that other members of the struct are initialized to their default values, NOT to zero, unless they have no explicit default values, in which case they are initialized to zero.Vittorio
So, for the last couple years when I thought = {0} was zero-initializing, for cases where the struct has NO DEFAULT VALUES, I was NOT wrong, as it was zero-initializing all members since no members had default values. However, if a struct member has a default value, it takes precedence, and gets set instead, except for the first member, which is explicitly set to zero. Ok, got it. There's lots of subtleties going on there that I didn't understand before.Vittorio
@GabrielStaples: No, none of these are subtleties. You simply learned how list initialization works incorrectly. The list of values in the braced-init-list is used to initialize the object; that's how it works. If the list contains just one zero, then that means one zero is applied (via list-initialization rules) to the object. It's that simple.Assiduity
@NicolBolas, fair enough. At least I finally get it now. "If the list contains just one zero, then that means one zero is applied (via list-initialization rules) to the object"...and I'd add: and all other members are value-initialized, which means setting them to their default values if they have one, or to zero if they don't.Vittorio
data_t d3 = {0} is list-initialization syntax. Whether or not it is aggregate initialization is a semantic issue that depends on whether the class definition of data_t meets the requirements for being an aggregateAngeles
@GabrielStaples beware "default-initializes all other members of the struct" means something else in C++ actually - for non-class types it means no initialization at all. I've expanded my answer to cover that as well, in case someone stumbles upon this in the future :)Sourdine
G
3

Short answer: because you have in-class initializers.

Longer answer: because you are compiling for the C++14 or higher, have in-class initializers and you are using aggregate initialization. The reference provides the explanation:

If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).

To zero-initialize all data members, provide only data members declarations, without the in-class initializers and then use the = {0} or ={} syntax:

struct data_t
{
    int num1;
    int num2;
    int num3;
    int num4;
};

int main()
{
    data_t d3 = { 0 };
    printf("d3.num1 = %i\nd3.num2 = %i\nd3.num3 = %i\nd3.num4 = %i\n\n",
        d3.num1, d3.num2, d3.num3, d3.num4);
}

Now, all your data members are initialized to 0.

Galah answered 30/4, 2020 at 20:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.