9

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?
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • 2
    Why should it? You're only giving one value, not values for all properties. – tadman Apr 30 '20 at 20:14
  • Is this a question or a question with an answer baked in? You should split the answer out into the answer section below. – tadman Apr 30 '20 at 20:14
  • @tadman, that's a quote from a different answer of mine. I don't consider it an answer by itself. – Gabriel Staples Apr 30 '20 at 20:16
  • 1
    @tadman: The latter part of the question states **what** apparently happens. It does not answer the question asked, **why**. – Eric Postpischil Apr 30 '20 at 20:17
  • 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 (https://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. – Gabriel Staples Apr 30 '20 at 20:18
  • 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... – Gabriel Staples Apr 30 '20 at 20:21
  • @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. – Nicol Bolas Apr 30 '20 at 20:24
  • 1
    your typedef in redundant, all classes and structs in c++ are types – pm100 Apr 30 '20 at 20:24
  • @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](https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/cpp/struct_initialization.cpp). – Gabriel Staples Apr 30 '20 at 20:26
  • Your struct members have initializers already. Your question sounds like it's about why *overriding* initializers doesn't work. – Nikos C. Apr 30 '20 at 20:28
  • @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++. – Gabriel Staples Apr 30 '20 at 20:28
  • @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. – NathanOliver Apr 30 '20 at 20:32
  • @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. – Gabriel Staples Apr 30 '20 at 20:33
  • Hey all, feel free to take a look at [this answer](https://stackoverflow.com/questions/61240589/how-to-initialize-a-struct-to-0-in-c/61240590#61240590) too and provide feedback to it or write your own answers there. I think it'd be helpful to complete the picture. – Gabriel Staples Apr 30 '20 at 20:34
  • I can't understand why you can't understand that `= {0}` sets member variables to zero _unless_ they have a [default member initializer](https://en.cppreference.com/w/cpp/language/data_members#Member_initialization), as of C++11. – Eljay Apr 30 '20 at 21:08
  • @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. – Gabriel Staples Apr 30 '20 at 21:16
  • 3
    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. – Eljay Apr 30 '20 at 21:24
  • 2
    @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. – Gabriel Staples Apr 30 '20 at 22:56

3 Answers3

10

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 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++](https://stackoverflow.com/a/61240590/4561887). – Gabriel Staples Apr 30 '20 at 21:07
  • 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? – Gabriel Staples May 01 '20 at 00:49
  • 1
    @GabrielStaples `data_t d;` is called [default initialization](https://en.cppreference.com/w/cpp/language/default_initialization) – NathanOliver May 01 '20 at 00:52
  • 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." – Gabriel Staples May 01 '20 at 03:52
7

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?

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • That last link ([What does {0} mean when initializing an object?](https://stackoverflow.com/q/88957/4561887)) 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._ – Gabriel Staples Apr 30 '20 at 20:55
  • 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. – Gabriel Staples Apr 30 '20 at 20:58
  • 1
    @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. – Nicol Bolas Apr 30 '20 at 21:07
  • @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](https://en.cppreference.com/w/cpp/language/value_initialization), which means setting them to their default values if they have one, or to zero if they don't._ – Gabriel Staples Apr 30 '20 at 21:08
  • `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 aggregate – M.M Apr 30 '20 at 22:56
  • 1
    @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 :) – rustyx May 01 '20 at 10:45
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.

Ron
  • 14,674
  • 4
  • 34
  • 47