Before we start:
Let me point out that a lot of the confusion around this syntax comes because in both C and C++ you can use the = {0}
syntax to initialize all members of a C-style array to zero! See here: https://en.cppreference.com/w/c/language/array_initialization. So, this works:
// z has type int[3] and holds all zeroes, as: `{0, 0, 0}`
int z[3] = {0};
But, that syntax does not work the same for structs, which are entirely different animals than C-style arrays. Additionally, the various techniques allowed for the initialization of structs differ between C and C++.
Going further, one should know that technically, = {0}
only explicitly initializes the first element of an array to 0
, and the rest are implicitly automatically initialized to 0
since their values are unspecified, so they get value-initialized (zero-initialized for non-class types; see here) to 0
.
These revised examples from the latest cppreference.com community wiki (https://en.cppreference.com/w/c/language/array_initialization) make this even more clear. Notice in particular how the int y[5] = {1,2,3};
example doesn't specify the last two elements, so they are implicitly default-initialized to zero, and the int z[4] = {1};
example only explicitly initializes the first element to one, so all the rest are implicitly default-initialized to zero, and the int w[3] = {0};
example only explicitly initializes the first element to zero, so all of the rest are implicitly default-initialized to zero:
Initialization from brace-enclosed lists
When an array is initialized with a brace-enclosed list of initializers, the first initializer in the list initializes the array element at index zero (unless a designator is specified) (since C99), and each subsequent initializer without a designator (since C99) initializes the array element at index one greater than the one initialized by the previous initializer.
int x[] = {1,2,3}; // x has type int[3] and holds 1,2,3
int y[5] = {1,2,3}; // y has type int[5] and holds 1,2,3,0,0
int z[4] = {1}; // z has type int[4] and holds 1,0,0,0
int w[3] = {0}; // w has type int[3] and holds all zeroes
Again, though, the above examples are for arrays, and this answer is for structs. So, let's keep going.
See also my follow-up question I asked after writing this answer below: Why doesn't initializing a C++ struct to = {0}
set all of its members to 0?
Back to the answer:
I figured it out: to get it to compile, just delete the zero:
// does NOT work in C++ (but *does* work in C)
myStruct _m1 = {0};
// works in both C and C++!
myStruct _m1 = {};
It now compiles. However, I ran a bunch of tests to check some things in my struct_initialization.cpp file in my eRCaGuy_hello_world repo, and using = {}
does NOT initialize all elements of the struct to zero! Rather, it initializes the struct to its default values, and is called "value initialization" (see this answer here). To run my tests and see for yourself, clone my repo above and run eRCaGuy_hello_world/cpp/struct_initialization_run.sh
.
Assuming you have this struct:
typedef struct
{
int num1 = 100;
int num2 = -100;
int num3;
int num4 = 150;
} data_t;
Note: the typedef
above is a carry-over from when I was testing this stuff in C instead of C++ (although the default struct values are not allowed in C, of course). For C++, this is preferred instead:
struct data_t
{
int num1 = 100;
int num2 = -100;
int num3;
int num4 = 150;
};
So please ignore it wherever I unnecessarily use typedef
to define the structs below.
Anyway, if I declare one of the above data_t
structs, and then do this:
data_t d2 = {};
printf("d2.num1 = %i\nd2.num2 = %i\nd2.num3 = %i\nd2.num4 = %i\n\n",
d2.num1, d2.num2, d2.num3, d2.num4);
...the output will be:
d2.num1 = 100
d2.num2 = -100
d2.num3 = 0
d2.num4 = 150
And I'm not even sure if d2.num3
is zero because it was initialized to zero or because it was left uninitialized, and that memory location happened to contain zero.
What happens is that = {}
sets all values to their default values, which are 100
for member num1
, -100
for member num2
, and 150
for member num4
. num3
has no explicit default value set by us, so it gets zero-initialized as its implicit default value. Read these answers to my follow-up question:
@NathanOliver's answer here:
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
.
@rustyx's answer here:
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.
...
As explained here: https://en.cppreference.com/w/cpp/language/zero_initialization, you can also do this:
myStruct _m1{};
In the example above, this code:
data_t d2{};
printf("d2.num1 = %i\nd2.num2 = %i\nd2.num3 = %i\nd2.num4 = %i\n\n",
d2.num1, d2.num2, d2.num3, d2.num4);
...would produce output identical to what I showed above.
Even in cases where setting the struct to = {0}
DOES work, such as this:
// Does NOT do what I originally expected! Only sets the FIRST value in the
// struct to zero!
// The rest use default values, which are either 1) the explicit defaults if you
// set them with `= some_num` or whatever in the struct definition, or which are
// 2) in C++ at least, the implicit default values of `0` if you did not set
// explicit defaults.
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 still not what I originally expected, as it only sets the first value to zero! (I don't understand why; due to the hierarchy of list/aggregate initialization and value initialization (which zero initializes when explicit defaults are not set); see my question here and the several answers there):
d3.num1 = 0
d3.num2 = -100
d3.num3 = 0
d3.num4 = 150
On C-style arrays, however (NOT structs), these semantics work fine. Refer to this answer here (How to initialize all members of an array to the same value?). The following lines, therefore, both set all elements of the C-style array to zero when using C++:
uint8_t buffer[100] = {0}; // sets all elements to 0 in C OR C++
uint8_t buffer[100] = {}; // sets all elements to 0 in C++ only (won't compile in C)
So, after much experimentation, it looks like the following several ways are the ONLY ways to zero-initialize a struct, PERIOD. If you know differently, please comment and/or leave your own answer here.
The only ways possible to zero-initialize a struct in C++ are:
Be explicit:
// C-style typedef'ed struct
typedef struct
{
int num1 = 100;
int num2 = -100;
int num3;
int num4 = 150;
} data_t;
// EXPLICITLY set every value to what you want!
data_t d1 = {0, 0, 0, 0};
// OR (using gcc or C++20 only)
data_t d2 = {.num1 = 0, .num2 = 0, .num3 = 0, .num4 = 0};
Use memset()
to force all bytes to zero:
data_t d3;
memset(&d3, 0, sizeof(d3));
Set all default values to zero in the first place:
// C-style typedef'ed struct
typedef struct
{
int num1 = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
} data_t;
// Set all values to their defaults, which are zero in
// this case
data_t d4 = {};
// OR
data_t d5{}; // same thing as above in C++
// Set the FIRST value only to zero, and all the rest
// to their defaults, which are also zero in this case
data_t d6 = {0};
Write a constructor for the C++ struct
// 1. Using an initializer list
struct data
{
int num1;
int num2;
int num3;
int num4;
data() :
num1(0),
num2(0),
num3(0),
num4(0) {}
};
data d7; // all values are zero
// OR: 2. manually setting the values inside the constructor
struct data
{
int num1;
int num2;
int num3;
int num4;
data()
{
num1 = 0;
num2 = 0;
num3 = 0;
num4 = 0;
}
};
data d8; // all values are zero
Use a struct with no default values, and make your object you create from it static
typedef struct
{
int num1;
int num2;
int num3;
int num4;
} data_t;
// `static` forces a default initialization of zero for each
// value when no other default values are set
static data_t d9;
So, if you have a struct with non-zero default values, and you want to zero all values, you must do it EXPLICITLY! Here are some more ways:
// 1. Have a `constexpr` copy of the struct that you use to
// reset other struct objects. Ex:
struct data
{
int num1 = 1;
int num2 = 7;
int num3 = -10;
int num4 = 55;
};
constexpr data DATA_ALL_ZEROS = {0, 0, 0, 0};
// Now initialize d13 to all zeros using the above `constexpr` struct
// object
data d13 = DATA_ALL_ZEROS;
// OR 2. Use a `zero()` member function to zero the values:
struct data
{
int num1 = 1;
int num2 = 7;
int num3 = -10;
int num4 = 55;
zero()
{
num1 = 0;
num2 = 0;
num3 = 0;
num4 = 0;
}
};
data d14;
d14.zero();
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!
data_t d{}
sets all values to their defaults defined in the struct.
data_t d = {}
also sets all values to their defaults.
- And
data_t d = {0}
sets only the FIRST value to zero, and all other values to their defaults.
And in all cases above, if explicit default values are not set, the implicit default values are zero (0
).
So, if you want particular values to be the defaults, BE EXPLICIT!
Note that the above key take-aways I wrote seem to contradict this documentation on cppreference.com, so it has led me to ask this follow-up question listed just below, which has proven VERY helpful to my understanding!
Going further
- MOST USEFUL: Follow-up question of mine: Why doesn't initializing a C++ struct to
= {0}
set all of its members to 0?
References
- VERY USEFUL:
- https://en.cppreference.com/w/cpp/language/zero_initialization
- https://en.cppreference.com/w/cpp/language/aggregate_initialization
- https://en.cppreference.com/w/cpp/language/value_initialization
- VERY USEFUL: Initializing all members of an array (not struct) to the same value:
- How to initialize all members of an array to the same value?
- [gcc only] How to initialize all members of an array to the same value?
- https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/cpp/struct_initialization.cpp
- Clone this repo and run the code yourself with
cpp/run_struct_initialization.sh
Related
- Initializing default values in a struct
- *****[my own answer, which demonstrate this sort of struct modification/aggregate member reassignment within any function:
leds[0] = {10, 20, 30, 40, 50};
] Arduino Stack Exchange: Initializing Array of structs
Todo
[ ] I recently saw this in C: my_struct_t my_struct = {{0}};
. Investigate what that does, and if it is valid only in C, for instance, but not C++.
Update some weeks later in Jan. 2024: it appears to be an aggregate value initialization of a struct or list within a struct, in C. I think in C it would work for either a struct or list within a struct, but in C++ it would only work for a list within a struct, probably.