Blank initialising an array of structs in C99 - c

This seems like a hole in my knowledge. As far as I am aware, in C99 if you initialise a single element of a struct and no others, the others are zero initialised. Does the following code zero initialise all the members of a struct though?
typedef struct
{
int foo;
int bar;
char* foos;
double dar;
} some_struct_t;
some_struct_t mystructs[100] = {};
Update: There are some comments indicating that this syntax is an extension. If that is the case, is there any way of doing this that is pure C99 compliant?

As per C11, chapter §6.7.9, Initialization syntax, (for the sake of completeness, same mentioned in chapter §6.7.8 in C99)
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designationopt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
Which implies, the brace closed initializer list should have at minimum one initializer element (object).
In your code, the empty initializer list
some_struct_t mystructs[100] = {}; //empty list
is not a valid pure C syntax; it's a compiler extension.
You need to mention a single element in the list to make it standard conforming, like
some_struct_t mystructs[100] = {0};
which meets the criteria, from paragraph 21 of same standard(s),
If there are fewer initializers in a brace-enclosed list than there are elements or members
of an aggregate, or fewer characters in a string literal used to initialize an array of known
size than there are elements in the array, the remainder of the aggregate shall be
initialized implicitly the same as objects that have static storage duration.
So, in this case, you have one explicit 0 and remaining implicit zero-initialization (or similar).

Because it's array initialisation, you would need
some_struct_t mystructs[100] = { 0 }; // ensure all array elements (struct) being zero initialisation

For structs/unions (and arrays) there is a rule saying that if it is partially initialized, the rest of the items that didn't get initialized explicitly by the programmer are set to zero.
So by typing some_struct_t mystructs[100] = {0}; you tell the compiler to explicitly set foo to zero. And then the rest of the struct members gets set to zero as well, implicitly.
This has nothing to do with C99, but works for all C standard versions. Although in C99/C11, a designated initializer {.foo=0} would have achieved the very same result.

Related

What does initializing a string to {0, } do?

I've seen code like this:
char str[1024] = {0, };
and suspect that it is similar to doing this:
char str[1024];
str[0] = '\0';
But I couldn't find anything on it so I'm not sure.
What is this (called) and what does it do?
Disclaimer: I'm aware this might have been asked and answered before, but searching for {0, } is astonishingly hard. If you can point out a duplicate, I'll happily delete this question.
No, they are not the same.
This statement
char str[1024] = {0, };
initializes the first element to the given value 0, and all other elements are to be initialized as if they have static storage, in this case, with a value 0. Syntactically this is analogous to using
char str[1024] = {0};
Quoting C11, chapter 6.7.9, p21
If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
and, from p10 (emphasis mine)
If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:
if it has pointer type, it is initialized to a null pointer;
if it has arithmetic type, it is initialized to (positive or unsigned) zero;
if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
On the other hand
char str[1024];
str[0] = '\0';
only initializes the first element, and the remaining elements remains unitialized, containing indeterminate values.
The {0, } initializer is the same as the more common {0} initializer.
The trailing comma is allowed by the syntax but it makes no difference.
6.7.9--Initialization:
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designationopt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
The semantics are such that the 0, which is syntactically required because at least one initializer-list item is syntactically required (it's a kind of a arbitrary requirement: compilers frequently support an empty {} as well) initializes the first subobject recursively (6.7.9p17):
Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union.148) In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator.
and the rest is initialized as it would be if the whole object had static storage duration (6.7.9p19,6.7.9p21). This practically means to 0 (as with memset(,0,) although with the caveat that paddings need not be initialized and that 0-initialized pointers need not necessarily be "all-bits zero".
As far as I know, compilers on usual platforms (where pointers are all-bits-zero) just mostly do what they would do with memset(,0,).
This "universal" zero initialization works because the first 0 will recursively hit a scalar type (number on pointer) which can be invariably initialized with the 0 initializer. The default "as-with-static-storage-duration" then initialization applies to the rest.
A perhaps slightly more interesting of the trailing initializer comma doing nothing would be:
int main()
{
char one[]={0,}; //<the comma doesn't introduce another member
_Static_assert(sizeof(one)==1,""); //holds
}

What happens in the background of array initialization in c?

Given that this is legal
uint8_t bytes[4] = { 1, 2, 3, 4 };
And this is not:
uint8_t bytes2[4];
bytes2 = { 1, 2, 3, 4 };
what does { 1, 2, 3, 4 } represent?
Am assuming it's neither an rvalue or an lvalue. A preprocessor code candy that expands to something?
A syntax like {1,2,3,4}; is called brace-enclosed initializer list, it's an initializer. It can be only used for initialization (for array type).
Quoting C11, chapter §6.7.9
P11
The initializer for a scalar shall be a single expression,
[an array is not a scalar type, so not applicable for us]
P14
An array of character type may be initialized by a character string literal or UTF−8 string
literal, optionally enclosed in braces.
[We are not using a string literal here, so also not applicable for us]
P16
Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed
list of initializers for the elements or named members.
[This is the case of our interest]
and, P17,
Each brace-enclosed initializer list has an associated current object. When no
designations are present, subobjects of the current object are initialized in order according
to the type of the current object: array elements in increasing subscript order, structure
members in declaration order, and the first named member of a union.[....]
So, here, the values from the brace enclosed list are not directly "assigned" to the array, they are used to initialize individual members of the array.
OTOH, an array type, is not a modifiable lvalue, so it cannot be assigned. In other words, an array variable cannot be used as the LHS of the assignment operator.
To elaborate, from C11, chapter §6.5.16
assignment operator shall have a modifiable lvalue as its left operand.
{1,2,3,4} is an initializer-list, a particular syntax token that can only be used on the line where the array is declared.
This is regulated purely by the C standard syntax. There's no particular rationale behind it, this is just how the language is defined. In the C syntax, arrays cannot be assigned to, nor copied by assignment.
You can however dodge the syntax restrictions in several ways, to overwrite all values at once. The simplest way is just to create a temporary array and memcpy:
uint8_t tmp[] = {5,6,7,8};
memcpy(bytes, tmp, sizeof bytes);
Alternatively, use a compound literal:
memcpy(bytes, (uint8_t[]){5,6,7,8}, sizeof bytes);
If it makes sense for the specific application, you can also wrap the array in a struct to bypass the syntax restrictions:
typedef struct
{
uint8_t data [4];
} array_t;
...
array_t bytes = { .data = {1,2,3,4} };
array_t tmp = { .data = {5,6,7,8} };
bytes = tmp; // works just fine, structs can be copied this way
Initialization and assignment are fundamentally different things. As for the language C, you just have to accept the fact that they are distinguished, but of course, there's a technical reason it's defined this way:
In many systems, you can have a data segment in your executable file. This segment could be read/write and given an initialized array like
uint8_t foo[] = {1, 2, 3, 4}; // assume this has static storage duration
the compiler could just decide to output this exact byte sequence directly into your executable. So there's no code at all doing an assignment, the data is already there in memory when your program starts.
OTOH, arrays cannot be assigned to (only their individual members). That's how C is defined, and it's sometimes unfortunate.
{1,2,3,4} is an initializer list. It an be used to specify the initial value of an object with at least 4 elements, be they array elements or structure members including those of nested objects.
You cannot use this syntax to assign values to an array as this:
bytes2 = {1,2,3,4};
Because the syntax is not supported, and arrays are not lvalues.
You can use intializer lists as part of a C99 syntax known as compound literals to create objects and use them as rvalues for assignment, return values or function arguments:
struct quad { int x, y, z, t; };
struct quad p;
p = (struct quad){1,2,3,4};
You still cannot use this for arrays because they are not lvalues, but you can achieve the same effect with a call to memcpy():
uint8_t bytes2[4];
memcpy(bytes2, (uint8_t[4]){1,2,3,4}, sizeof(bytes2));
This statement is compiled by clang as a single intel instruction as can be seen on Godbolt's Compiler Explorer

Is full followed by partial initialization of a subobject undefined behavior?

Consider the following struct initialization:
#include<stdio.h>
struct bar {
int b;
int a;
int r;
};
struct foo {
struct bar bar;
};
int main(int argc, char **argv) {
struct bar b = {1, 2, 3};
struct foo f = {.bar = b, .bar.a = 5 };
// should this print "1, 5, 3", "1, 5, 0", or "0, 5, 0"?
// clang on Mac prints "1, 5, 3", while gcc on Ubuntu prints "0, 5, 0"
printf("%d, %d, %d\n", f.bar.b, f.bar.a, f.bar.r);
return 0;
}
The C11 standard seems to do a quite poor job of describing what behavior should be expected here in section 6.7.9, but seems to think it's doing a reasonable job, as I don't see any warnings regarding undefined behavior in this case either.
In practice, it seems the behavior is either not standardized or the standard is violated by at least one common compiler, with clang/llvm 8.0.0 on a Mac producing "1, 5, 3", and gcc 5.4 on Ubuntu producing "0, 5, 0".
According to the C standard, should f.bar.b and f.bar.r well defined at this point, or does this initialization result in undefined or unspecified behavior?
The C11 standard seems to do a quite poor job of describing what behavior should be expected here in section 6.7.9,
Standardese can be difficult to read, but I don't think this area of the standard is worse in that respect than should be expected.
but seems to think it's doing a reasonable job, as I don't see any warnings regarding undefined behavior in this case either.
The standard is not required to explicitly declare undefined behaviors. Indeed, the standard contains a blanket statement that wherever it does not define behavior for a given piece of code, that code's behavior is undefined. Nevertheless, I do think section 6.7.9 covers this area pretty thoroughly. The main area left open is this:
The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.
(C2011, 6.7.9/23)
That doesn't present any problem for your example.
In practice, it seems the behavior is either not standardized or the standard is violated by at least one common compiler, with clang/llvm on a Mac producing "1, 5, 3", and gcc on Ubuntu producing "0, 5, 0".
I'm completely prepared to believe that one or the other of those is non-conforming in this area. However, do also pay attention to compiler versions and compilation options -- they may be compiling for different versions of the standard, with or without extensions.
According to the C standard, should f.bar.b and f.bar.r well defined at this point, or does this initialization result in undefined or unspecified behavior?
If the declaration of an object has an associated initializer then the whole object is initialized, and furthermore, the resulting initial value is well-defined by the standard, subject to caveats arising from 6.7.9/23. As for the initial values required of a conforming implementation in your example, the key provisions are these:
The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
(C2011, 6.7.9/19; emphasis added)
Each designator list begins its description with the current object associated with the closest surrounding brace pair. Each item in the designator list (in order) specifies a particular member of its current object and changes the current object for the next designator (if any) to be that member. The current object that results at the end of the designator list is the subobject to be initialized by the following initializer.
(C2011, 6.7.9/18; emphasis added)
If the aggregate or union contains elements or members that are aggregates or unions, these rules apply recursively to the subaggregates or contained unions.
(C2011, 6.7.9/20)
Thus, given f's initializer,
struct foo f = {.bar = b, .bar.a = 5 };
we first process element .bar = b, as required by 6.7.9/19. That contains a designator list designating foo.b, of type struct bar, as the object to initialize from the following initializer. This initializer exercises the option of being "a single expression that has compatible structure or union type", per 6.7.9/13, therefore the initial value of f.bar is the value of b, subject to partial or full override by subsequent initializers.
We next process the second element, .bar.a = 5. This initializes f.bar.a and only that subobject, per 6.7.9/18, overriding the initialization specified by the previous initializer per 6.7.9/19.
The result of conforming initialization thus leads to printing
1, 5, 3
GCC seems to be failing by re-initializing all of f.bar when it processes the the second initializer, instead of only f.bar.a.
In the C Standard there is written (6.7.9 Initialization)
17 Each brace-enclosed initializer list has an associated current
object. When no designations are present, subobjects of the current
object are initialized in order according to the type of the current
object: array elements in increasing subscript order, structure
members in declaration order, and the first named member of a
union.148) In contrast, a designation causes the following initializer
to begin initialization of the subobject described by the designator.
Initialization then continues forward in order, beginning with the
next subobject after that described by the designator
And
19 The initialization shall occur in initializer list order, each
initializer provided for a particular subobject overriding any
previously listed initializer for the same subobject;151) all
subobjects that are not initialized explicitly shall be initialized
implicitly the same as objects that have static storage duration.
This footnote is important
148) If the initializer list for a subaggregate or contained union
does not begin with a left brace, its subobjects are initialized as
usual, but the subaggregate or contained union does not become the
current object: current objects are associated only with
brace-enclosed initializer lists.
Thus I see neither undefined nor unspecified behavior.
In my opinion the result should look like { 1, 5, 3 }.
If to leave aside the Standard then it is reasonable at first to initialize the memory with the default initializes and then overwrite it with the explicit initializers.
The standard says…
I'm going to quote from §6.7.9 Initializers of ISO/IEC 9899:2011 (the C11 standard), the same section as Vlad from Moscow quotes in his answer:
¶16 Otherwise, the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.
¶17 Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union.148) In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator.149)
¶18 Each designator list begins its description with the current object associated with the closest surrounding brace pair. Each item in the designator list (in order) specifies a particular member of its current object and changes the current object for the next designator (if any) to be that member.150) The current object that results at the end of the designator list is the subobject to be initialized by the following initializer.
¶19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;151) all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
¶20 If the aggregate or union contains elements or members that are aggregates or unions, these rules apply recursively to the subaggregates or contained unions. If the initializer of a subaggregate or contained union begins with a left brace, the initializers enclosed by that brace and its matching right brace initialize the elements or members of the subaggregate or the contained union. Otherwise, only enough initializers from the list are taken to account for the elements or members of the subaggregate or the first member of the contained union; any remaining initializers are left to initialize the next element or member of the aggregate of which the current subaggregate or contained union is a part.
¶21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
148) If the initializer list for a subaggregate or contained union does not begin with a left brace, its subobjects are initialized as usual, but the subaggregate or contained union does not become the current object: current objects are associated only with brace-enclosed initializer lists.
149) After a union member is initialized, the next object is not the next member of the union; instead, it is the next subobject of an object containing the union.
150) Thus, a designator can only specify a strict subobject of the aggregate or union that is associated with the surrounding brace pair. Note, too, that each separate designator list is independent.
151) Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.
Interpretation
I think your code is well-formed and that GCC is handling it incorrectly and Clang is handling it correctly.
With your code modified only so that the unused argc and argv are replaced by void, running on a Mac with macOS Sierra 10.12.1, compiling with GCC 6.2.0 and with Apple's clang version 'Apple LLVM version 8.0.0 (clang-800.0.42.1)', I get the same results as you:
0, 5, 0 from GCC.
1, 5, 3 from Clang.
The key wording in the standard is:
In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator.
In your initializer, you have:
struct foo f = { .bar = b, .bar.a = 5 };
The first part of the initializer, .bar = b, clearly initializes the bar subobject. At that point, .bar.b has the value 1, .bar.a has the value 2, .bar.r has the value 3. If you omit the , .bar.a = 5 portion of the initializer, the compilers agree.
When you include the , .bar.a = 5, the designator causes the following initialize to begin intialization of the subobject described by the designator — and the designator is .bar.a so the initialization 5 initializes .bar.a. The compilers agree on this; both set .bar.a to 5. But the subobject designated by .bar was previously initialized, so the initializer for .bar.a only affects the .a element; it should not override any other element.
If the initializer is extended with with , 19, then the 19 is not a designation, but it initializes the subobject after the previous designation, which is .bar.r. Both the compilers agree with this.
This test code, a minor variant on your code, illustrates:
#include <stdio.h>
struct bar
{
int b;
int a;
int r;
};
struct foo
{
struct bar bar;
};
static inline void foobar(struct foo f)
{
printf("%d, %d, %d\n", f.bar.b, f.bar.a, f.bar.r);
}
int main(void)
{
struct bar b = {1, 2, 3};
struct foo f0 = {.bar = b, .bar.a = 5 };
struct foo f1 = {.bar = b, .bar.a = 5, 19 };
struct foo f2 = {.bar = b };
foobar(f0);
foobar(f1);
foobar(f2);
return 0;
}
The output from GCC is:
0, 5, 0
0, 5, 19
1, 2, 3
The output from Clang is:
1, 5, 3
1, 5, 19
1, 2, 3
Note that even with no warnings specifically enabled, clang gripes about this code:
$ clang -O3 -g -std=c11 so-4092-0714.c -o so-4092-0714
so-4092-0714.c:21:36: warning: subobject initialization overrides initialization of other fields within its
enclosing subobject [-Winitializer-overrides]
struct foo f0 = {.bar = b, .bar.a = 5 };
^~~~~~
so-4092-0714.c:21:29: note: previous initialization is here
struct foo f0 = {.bar = b, .bar.a = 5 };
^
so-4092-0714.c:22:36: warning: subobject initialization overrides initialization of other fields within its
enclosing subobject [-Winitializer-overrides]
struct foo f1 = {.bar = b, .bar.a = 5, 19 };
^~~~~~
so-4092-0714.c:22:29: note: previous initialization is here
struct foo f1 = {.bar = b, .bar.a = 5, 19 };
^
2 warnings generated.
$
As I said, I think Clang is initializing these structures correctly, even if it complains more than necessary while doing so.
This is not undefined behavior.
From section 6.7.9 of the C standard:
19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding
any previously listed initializer for the same subobject; all
subobjects that are not initialized explicitly shall be initialized
implicitly the same as objects that have static storage duration.
So when there is a conflict among designated initializers, the last one listed takes precedence.
In your example, you initialize .bar, then .bar.b. Both of these initialize .bar, so the second one is used. So .bar is initialized, along with its subfield .bar.b, but not .bar.a or .bar.r. And because some fields are initialized but not all, the others are initialized to 0:
21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in
a string literal used to initialize an array of known size than
there are elements in the array, the remainder of the
aggregate shall be initialized implicitly the same as objects that
have static storage duration.
This means that the correct behavior is to output "0,5,0". So gcc is conforming and the Mac compiler is not.
Since this question was posted, the C18 standard has been released, and includes some additional clarifications that make the answer completely clear.
A clarification request similar to this question had been asked of the standards body as early as 2012, with some changes to the language being briefly discussed that might make the meaning clearer...ultimately it was decided that the C11 language was already correct, but that an example should be added to clarify.
Example 12 in section 6.7.9 of the last publicly available C17 draft demonstrates the correct behavior when a subobject is fully and then partially initialized, noting that any trailing values of the larger subobject not explicitly overwritten should survive (rather than be overwritten by default values), "because implicit initialization does not override explicit initialization."
So the correct behavior is to print "1, 5, 3".

Why does this work in C for init a structure? struct x_ tmp = {{{{0}}}};

I've been hunting around on why this compiles:
struct x_ {
char a[10];
int b;
};
struct x_ tmp = {{{{0}}}};
We may have an older gcc version, so {0} may not work and {{0}} should be used, but don't see this {{{{0}}}} anywhere.
See gcc bug here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119
Thanks
Peter
I interpret the question to be asking whether GCC is correct to accept the specified initializer for an object of the specified type. To address the question I will be quoting from section 6.7.9 of the C2011 standard.
In the first place, the following applies both to the initializer for the overall struct and to the initializer within for the first member:
[...] the initializer for an object that has aggregate or union type shall be a brace-enclosed list of initializers for the elements or named members.
We then have:
Each brace-enclosed initializer list has an associated current object. [...] subobjects of the current object are initialized in order according
to the type of the current object: array elements in increasing subscript order, structure members in declaration order [...].
... and later ...
If the aggregate or union contains elements or members that are aggregates or unions, these rules apply recursively to the subaggregates or contained unions. If the initializer of a subaggregate or contained union begins with a left brace, the initializers enclosed by that brace and its matching right brace initialize the elements or members of the subaggregate or the contained union.
Among those, we have the overall {{{{0}}}} established as an initializer for the whole struct, {{{0}}} as an initializer for its first member, a, and {{0}} as an initializer for a[0]. The question comes down to whether the last pairing is a valid. If it is, then the others are valid, too.
This possibly bears on the matter:
The initializer for a scalar shall be a single expression, optionally enclosed in braces.
(emphasis added). That can be interpreted to specify that the initializer for a[0] can be enclosed in braces, i.e. as {0}, because a[0] is indeed a scalar. I am not convinced that that's the intended interpretation, but I would not be prepared at this point to fault GCC for accepting it.
I do not, however, accept {{0}} as a single expression enclosed in braces, therefore I do not accept it to be a valid initializer for a[0]. I speculate that GCC accepts it as a result of inappropriate recursive application of the rule allowing scalar initializers to be brace-enclosed.
For what it's worth, GCC 4.4.7 does emit two warnings about the construct by default:
i.c:7: warning: braces around scalar initializer
i.c:7: warning: (near initialization for ‘tmp.a[0]’)
i.c:7: warning: braces around scalar initializer
i.c:7: warning: (near initialization for ‘tmp.a[0]’)
Evidently, then, someone thought the extra braces were questionable, but the GCC documentation does not mention accepting that form among the supported language extensions. At minimum, then, there is a documentation bug.
When initializing a struct containing an array, you can either initialize the first element (with the rest set to 0 by default) or you can provide an initialization of each value. In this case using either a string initialization for a, or an array-style initialization providing a value for all members, e.g.
struct x_ tmp = { .a="" }; /* using string initialization of a */
or
struct x_ tmp = {{0},0}; /* using array initialization format */
Failing to identify a specific field, or provide an initialization for all members will generate compiler warnings for braces around a scalar initializer and missing-field-initializers, while initializing values to 0 by default.
Example with Use/Output
#include <stdio.h>
struct x_ {
char a[10];
int b;
};
int main (void) {
struct x_ tmp = { .a="" }; /* using string initialization of a */
struct x_ tmq = {{0},0}; /* using array initialization format */
printf ("\n tmp.a : '%s', tmp.b : %d\n", tmp.a, tmp.b);
printf ("\n tmq.a : '%s', tmq.b : %d\n", tmq.a, tmq.b);
return 0;
}
e.g.
$ ./bin/struct_init
tmp.a : '', tmp.b : 0
tmq.a : '', tmq.b : 0

Is an empty initializer list valid C code?

It is common to use {0} to initialize a struct or an array but consider the case when the first field isn't a scalar type. If the first field of struct Person is another struct or array, then this line will result in an error (error: missing braces around initializer).
struct Person person = {0};
At least GCC allows me to use an empty initializer list to accomplish the same thing
struct Person person = {};
But is this valid C code?
Also: Is this line guaranteed to give the same behavior, i.e. a zero-initialized struct?
struct Person person;
No, an empty initializer list is not allowed. This can also be shown by GCC when compiling with -std=c99 -pedantic:
a.c:4: warning: ISO C forbids empty initializer braces
The reason is the way the grammar is defined in §6.7.9 of the 2011 ISO C Standard:
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designation(opt) initializer
initializer-list , designation(opt) initializer
According to that definition, an initializer-list must contain at least one initializer.
Yes from C23 empty initialization is allowed. If the initializer is the empty initializer, the initial value is the same as the initialization of a static storage duration object.
struct Person person = {}; //Valid C23
If an object is initialized with an empty initializer, then:
— if it has pointer type, it is initialized to a null pointer;
— if it has decimal floating type, it is initialized to (positive or unsigned) zero, and the quantum exponent is implementation-defined166);
— if it has arithmetic type, and it does not have decimal floating type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
— if it is a union, the first-named member is initialized (recursively) according to these rules, and any padding is initialized to zero bit.
Refernece: ISO/IEC 9899:202x (E)
According to the C99 standard, array creation with an empty initializer list is forbidden. In a previous answer, you can see that grammar does not describe this case.
But what happens if you declare an array without initialization? Well, it depends on the compiler which you use. Let's take a look at this simple example: int arr[5] = {}.
GCC
By default gcc does not produce any warnings/errors when you try to compile this code. Not even -Wall, but -Wpedantic does.
warning: ISO C forbids empty initializer braces
But anyway gcc fill members of an array with 0's exactly as if you specify it explicitly int arr[5] = {0} see assembly output godbolt.
CLANG
But default not showing warnings about this case, but with option -Wgnu-empty-initializer does:
warning: use of GNU empty initializer extension
Clang generates different assembly code godbolt but behaves the same.
It depends. For ISO C standard, before ISO C23, the empty initialization of arrays, structs, or unions is not allowed; since ISO C23 (see 6.7.10 Initialization), it is allowed:
braced-initializer:
{ }
{ initializer-list }
{ initializer-list , }
An empty brace pair ({}) is called an empty initializer and is referred to as empty initialization
However, GCC provides GNU C extensions that allow empty initialization of arrays or structs. Unless -Wpedantic, -pedantic, or -pedantic-errors options are given, GCC will not throw warnings or errors for this.

Resources