2

I was wondering if someone could provide a detailed, simple explanation of the differences between the two of the following pieces of code. Given the following definition:

typedef struct {
    stuff;
    stuff_2;
} variable_t;

What is the difference between:

  • variable_t my_variable;
  • variable_t my_variable = {};

And if I do the first one, and then never fully initialize it, why does the compiler not throw an error?

Note: I am compiling with gcc -std=gnu99, so the second is valid and wound up being the solution to a problem that I had. I was wondering as to why.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278

2 Answers2

2

It depends a little bit on where you place the respective variable definition, and it also seems depends on the compiler in use.

Automatic storage duration

Let's discuss the difference when the variables have automatic storage duration (which is the case if you place it in function or block scope and there without static keyword):

void someFunction() {
   variable_t my_variable;       // (1)
   variable_t my_variable = {};  // (2)
}

(1) denotes a variable definition without an explicit initialization. And according this online C standard draft, it's value is indeterminate:

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.

(2) is a variable definition with explicit initialization through an initializer list without designators, i.e. without associating values to members through their names but only through the order of values (cf. 6.7.9 p17..21).

The interesting paragraph is 6.7.9 p21, which states that if the initializer list has fewer entries than the number of struct members, the members are initialized according to the initialization rule of static storage duration (i.e. to 0 or NULL as explained later):

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, ... , the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

So it seems that if you write variable_t my_variable = {}, then all members are initialized to 0 or NULL.

However, as mentioned by aschepler in the comments, C initialization list grammar states that initializer lists must not be empty (cf. also cppreference.com):

... the initializer must be a non-empty, brace-enclosed, comma-separated list of initializers for the members

So according to the standard an initializer list in C needs at least one entry; When testing it in my XCode8.3 environment with -std=gnu99, an empty initialization list seems to be supported, but I am aware that this is not a valid reference. So to be safe and not to depend on particular compiler extensions, you should actually write:

   variable_t my_variable = {0};  

Static storage duration

At file scope, your variable definitions will have static storage duration, and then other rules apply (cf. 6.7.9 (10)):

(10) ... 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;

...

(21) If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, ... the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

So if you write...

#include <stdio.h>
variable_t my_variable;       // (1)
variable_t my_variable = {};  // (2)

then (1) and (2) actually yield the same result because for the not explicitly initialized variable (1), paragraph (10) applies, and for the explicitly but empty initialized variable (2), according to paragraph (21), every member falls back to the initialization rule of (10).

Again, compilers may not support empty initalization lists as discussed above.

Hope it helps (because it has been a lot of typing :-) )

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • 2
    `{}` is illegal because of the grammar definition at the start of 6.7.9: An _initializer_ is either an _assignment-expression_, the sequence `{` _initializer-list_ `}`, or the sequence `{` _initializer-list_ `,` `}`, and _initializer-list_ has no empty productions. – aschepler Jul 10 '17 at 22:42
  • `{0}` always produces the same result, regardless of the type of the first member. It should also be noted that zeroing of padding bits is only required since C11. – nwellnhof Jul 10 '17 at 23:53
-2

When you declare:

variable_t my_variable;           // a variable_t that is uninitialized

variable_t my_variable = {};      // a variable_t initialized with zeroes.

Note that for variables declared at file-scope, this doesn't really matter since the data is - normally- zeroed out before program start.

Used on the stack, the second line efficiently fills my_variables with zeroes. Same as if there was a call to:

memset(&variable, 0, sizeof(variable));

This works because, in C, you can copy a structusing =.

Here's a little game the computer is sure to win.

struct A { /*...*/ };

void main() 
{
   A a;  // random
   A b = {};
   if (memcmp(&a, &b, sizeof(A)) == 0)
   {
       printf("You win\n");
       return;
   }

   a = b;
   if (memcmp(&a, &b, sizeof(A)) == 0)
   {
       printf("I win\n");
   }
}
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • 1
    Note that the first might initialized to all zeros if it is defined at file scope, but not if it is defined in block scope. The second is not legal in strict standard C — it is a C++ notation (which some C compilers might allow even though the standard does not). – Jonathan Leffler Jul 10 '17 at 19:24
  • Certainly using uninitialized variable like `A a` (that is not an `unsigned char`) in the first `memcmp(&a,...` is UB. – chux - Reinstate Monica Jul 10 '17 at 19:40
  • It's address is initialized A is on the stack. I can read it. – Michaël Roy Jul 10 '17 at 19:42
  • 2
    @DougSkinner: OK — I've seen. My comment is still accurate: the `= {};` initializer is still not standard C, but it is accepted by some C compilers, and specifically by GCC and probably by Clang emulating GCC. It isn't a bad notation for default initializing a variable; it is just not standard C. – Jonathan Leffler Jul 10 '17 at 20:56
  • 1
    Using `memset` to zero is not guaranteed to give the same values as a `{0}` initializer, although this is very common behavior. The C standard guarantees that sequences of zero bytes give zero values for all integer types, but says nothing about the representation of null pointer values or zero floating type values. – aschepler Jul 10 '17 at 23:04
  • This is C., there's no type safety implied, or wanted, for that matter. It just _has to_ work, with no bugs if possible. :) – Michaël Roy Jul 11 '17 at 00:15