261

When {0} is used to initialize an object, what does it mean? I can't find any references to {0} anywhere, and because of the curly braces Google searches are not helpful.

Example code:

SHELLEXECUTEINFO sexi = {0}; // what does this do?
sexi.cbSize = sizeof(SHELLEXECUTEINFO);
sexi.hwnd = NULL;
sexi.fMask = SEE_MASK_NOCLOSEPROCESS;
sexi.lpFile = lpFile.c_str();
sexi.lpParameters = args;
sexi.nShow = nShow;

if(ShellExecuteEx(&sexi))
{
    DWORD wait = WaitForSingleObject(sexi.hProcess, INFINITE);
    if(wait == WAIT_OBJECT_0)
        GetExitCodeProcess(sexi.hProcess, &returnCode);
}

Without it, the above code will crash on runtime.

Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125

10 Answers10

314

What's happening here is called aggregate initialization. Here is the (abbreviated) definition of an aggregate from section 8.5.1 of the ISO spec:

An aggregate is an array or a class with no user-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions.

Now, using {0} to initialize an aggregate like this is basically a trick to 0 the entire thing. This is because when using aggregate initialization you don't have to specify all the members and the spec requires that all unspecified members be default initialized, which means set to 0 for simple types.

Here is the relevant quote from the spec:

If there are fewer initializers in the list than there are members in the aggregate, then each member not explicitly initialized shall be default-initialized. Example:

struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };

initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of an expression of the form int(), that is, 0.

You can find the complete spec on this topic here

Don Neufeld
  • 22,720
  • 11
  • 51
  • 50
  • 15
    Excellent response. Just wanted to add that initialising an aggregate with {0} is the same as initialising it with simply {}. Perhaps the former makes it more obvious that built-in types get zeroed. – James Hopkin Sep 18 '08 at 09:02
  • 7
    Some compilers choke on {}, that's why {0} gets used – Branan Sep 18 '08 at 16:11
  • 12
    Not quite.. In C++, if the first member cannot be zero-constructed then {0} will not work. Ex: struct A { B b; int i; char c; }; struct B { B(); B(string); }; A a = {}; // this statement cannot be rewritten as 'A a = {0}'. – Aaron Sep 23 '08 at 15:45
  • 27
    @Branan, that's because in C "{}" isn't valid. In C++ it is. @don.neufeld, this has changed with C++03 (default-initialized -> value-initialized). The quote cites the C++98 Standard, bear in mind. – Johannes Schaub - litb Jul 10 '09 at 12:36
  • 3
    So, does this replace the need to use ZeroMemory() (in VC++)? – Ray Mar 25 '14 at 19:44
  • @DebugErr Although there are a lot of cases where either would work, cases remain where Microsoft's `ZeroMemory` or the standard's `memset` cannot work (basically, for non-POD types), as well as cases where aggregate initialisation cannot work (basically, for arrays where the size is not statically known). –  Jul 30 '14 at 21:11
  • 1
    @DonNeufeld standard quotes should be accompanied by the name of which standard is being quoted; in this case it is quite relevant because you have quoted C++98, but C++03 changed *default-initialized* to *value-initialized*. C++03 also changed the meaning of the terms; *default-initialized* for primitive types in C++98 means all-bits-zero, but in C++03 it means uninitialized. The more recent standards (after you wrote your answer) follow the C++03 model. – M.M Nov 24 '15 at 02:47
  • @Aaron not sure if specifying a constructor for `B` will still work. as it may destitute the object graph from the right to be called an aggregate type. (or am I wrong?) – v.oddou May 23 '17 at 02:29
93

One thing to be aware of is that this technique will not set padding bytes to zero. For example:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Is not the same as:

foo a;
memset(&a,0,sizeof(a));

In the first case, pad bytes between c and i are uninitialized. Why would you care? Well, if you're saving this data to disk or sending it over a network or whatever, you could have a security issue.

Harold Ekstrom
  • 1,538
  • 8
  • 7
  • 13
    Of course, it's only a security issue if you 'write(f, &a, sizeof(a))', which can produce different file encoding on different processors / compilers. Well formatted output would be safe without the memset. – Aaron Sep 23 '08 at 15:42
  • 3
    Also, if you are sending stuff across the network you will always set the alignment to be packed. That way you get as few extra padding bytes as possible. – Mark Kegel Nov 08 '08 at 22:36
  • 18
    It should be noted that although the spec does not require the padding to be initialized, any sane compiler will, as it only costs time to initialize "around" it. – Tomas Jul 30 '10 at 09:15
  • 4
    I'd like to take issue with the use of the words "is not". Padding bytes are implementation defined. The compiler is free to turn foo a = {0} into memset(&a, 0, sizeof(a)) at it's own leisure. It is not required to "skip" padding bytes and *only* set foo.c and foo.i. +1 for the (potential) security bug tho – SecurityMatt Feb 16 '13 at 20:59
  • 2
    @HaroldEkstrom an update to this answer: as of both C11 and C++11, padding bits are explicitly required to be set to zero by this method. – Alex Celeste May 06 '15 at 09:27
  • @Leushenko have you got a reference for that? As far as I'm aware that is only true for objects of static storage duration. – M.M Nov 24 '15 at 02:53
  • 2
    @M.M actually you're kinda right, the explicit reference to padding is in a section about static objects; however "the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration" (C11 6.7.9). I guess technically you could twist this to mean that padding bits are skipped *iff* you provide explicit initializers for all members, but that would require the compiler vendor to go out of their way to troll you. Ambiguous text, but I think the intent is that padding is zeroed. – Alex Celeste Nov 24 '15 at 03:26
  • 3
    @Leushenko point 19 says "all subobjects that are not initialized explicitly", so I'd lean to point 21 (which you quoted) being sloppy. It would be truly bizarre if the spec allowed padding to be uninitialized up until the moment of the last initializer , after which the padding had to be zeroed, especially considering that initializers may appear out of order when designated initializers are used. – M.M Nov 24 '15 at 03:37
21

Note that an empty aggregate initializer also works:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};
dalle
  • 18,057
  • 5
  • 57
  • 81
11

In answer to why ShellExecuteEx() is crashing: your SHELLEXECUTEINFO "sexi" struct has many members and you're only initializing some of them.

For example, the member sexi.lpDirectory could be pointing anywhere, but ShellExecuteEx() will still try to use it, hence you'll get a memory access violation.

When you include the line:

SHELLEXECUTEINFO sexi = {0};

before the rest of your structure setup, you're telling the compiler to zero out all structure members before you initialize the specific ones you're interested in. ShellExecuteEx() knows that if sexi.lpDirectory is zero, it should ignore it.

Ziezi
  • 6,375
  • 3
  • 39
  • 49
snowcrash09
  • 4,694
  • 27
  • 45
  • I would say you're actually telling the compiler to zero the first member, and then default initialize all remaining, and I'd rather just using the = {}, where you're saying "default initialize all members" – stux Oct 12 '22 at 01:38
10

{0} is a valid initializer for any (complete object) type, in both C and C++. It's a common idiom used to initialize an object to zero (read on to see what that means).

For scalar types (arithmetic and pointer types), the braces are unnecessary, but they're explicitly allowed. Quoting the N1570 draft of the ISO C standard, section 6.7.9:

The initializer for a scalar shall be a single expression, optionally enclosed in braces.

It initializes the object to zero (0 for integers, 0.0 for floating-point, a null pointer for pointers).

For non-scalar types (structures, arrays, unions), {0} specifies that the first element of the object is initialized to zero. For structures containing structures, arrays of structures, and so on, this is applied recursively, so the first scalar element is set to the zero, as appropriate for the type. As in any initializer, any elements not specified are set to zero.

Intermediate braces ({, }) may be omitted; for example both these are valid and equivalent:

int arr[2][2] = { { 1, 2 }, {3, 4} };

int arr[2][2] = { 1, 2, 3, 4 };

which is why you don't have to write, for example, { { 0 } } for a type whose first element is non-scalar.

So this:

some_type obj = { 0 };

is a shorthand way of initializing obj to zero, meaning that each scalar sub-object of obj is set to 0 if it's an integer, 0.0 if it's floating-point, or a null pointer if it's a pointer.

The rules are similar for C++.

In your particular case, since you're assigning values to sexi.cbSize and so forth, it's clear that SHELLEXECUTEINFO is a struct or class type (or possibly a union, but probably not), so not all of this applies, but as I said { 0 } is a common idiom that can be used in more general situations.

This is not (necessarily) equivalent to using memset to set the object's representation to all-bits-zero. Neither floating-point 0.0 nor a null pointer is necessarily represented as all-bits-zero, and a { 0 } initializer doesn't necessarily set padding bytes to any particular value. On most systems, though, it's likely to have the same effect.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 2
    In C++, `{0}` is not a valid initializer for an object with no constructor accepting `0` ; nor for an aggregate whose first element is as such (or an aggregate with no elements) – M.M Nov 24 '15 at 02:49
7

I also use it to initialize strings eg.

char mytext[100] = {0};
Adam Pierce
  • 33,531
  • 22
  • 69
  • 89
  • 5
    Though, of course, if mytext is being used a string, char mytext[100]; mytext[0] = '\0'; would have the same effect of giving an empty string, but only cause the implementation to zero the first byte. – Chris Young Apr 08 '09 at 16:32
  • @Chris: I've often wished there was a syntax for partial object initialization. Being able to "declare and initialize" x, then do likewise with y, then z, is a lot nicer than having to declare x, y, and z, and then initialize x, y, and z, but initializing 100 bytes when only one actually needs initialization seems rather wasteful. – supercat Dec 12 '17 at 23:00
3

It's been awhile since I worked in c/c++ but IIRC, the same shortcut can be used for arrays as well.

µBio
  • 10,668
  • 6
  • 38
  • 56
2

I have always wondered, why you should use something like

struct foo bar = { 0 };

Here is a test case to explain:

check.c

struct f {
    int x;
    char a;
} my_zero_struct;

int main(void)
{
    return my_zero_struct.x;
}

I compile with gcc -O2 -o check check.c and then output the symbol table with readelf -s check | sort -k 2 (this is with gcc 4.6.3 on ubuntu 12.04.2 on a x64 system). Excerpt:

59: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
48: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
25: 0000000000601018     0 SECTION LOCAL  DEFAULT   25 
33: 0000000000601018     1 OBJECT  LOCAL  DEFAULT   25 completed.6531
34: 0000000000601020     8 OBJECT  LOCAL  DEFAULT   25 dtor_idx.6533
62: 0000000000601028     8 OBJECT  GLOBAL DEFAULT   25 my_zero_struct
57: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT  ABS _end

The important part here is, that my_zero_struct is after __bss_start. The ".bss" section in a C program is the section of memory which is set to zero before main is called see wikipedia on .bss.

If you change the code above to:

} my_zero_struct = { 0 };

Then the resulting "check" executable looks exactly the same at least with the gcc 4.6.3 compiler on ubuntu 12.04.2; the my_zero_struct is still in the .bss section and thus it will be automatically initialized to zero, before main is called.

Hints in the comments, that a memset might initialize the "full" structure is also not an improvement, because the .bss section is cleared fully which also means the "full" structure is set to zero.

It might be that the C language standard does not mention any of this, but in a real world C compiler I have never seen a different behaviour.

Ingo Blackman
  • 991
  • 8
  • 13
  • Global and static variables are always initialized by default to 0 or default ctor. But if you declare instance of f locally you may get different results. – Logman Jun 06 '18 at 11:27
0

It's syntatic sugar to initialize your entire structure to empty/zero/null values.

Long version

SHELLEXECUTEINFO sexi;
sexi.cbSize = 0;
sexi.fMask = 0;
sexi.hwnd = NULL;
sexi.lpVerb = NULL;
sexi.lpFile = NULL;
sexi.lpParameters = NULL;
sexi.lpDirectory = NULL;
sexi.nShow = nShow;
sexi.hInstApp = 0;
sexi.lpIDList = NULL;
sexi.lpClass = NULL;
sexi.hkeyClass = 0;
sexi.dwHotKey = 0;
sexi.hMonitor = 0;
sexi.hProcess = 0;

Short version

SHELLEXECUTEINFO sexi = {0};

Wasn't that much easier?

It's also nice because:

  • you don't have to hunt down every member and initialize it
  • you don't have to worry that you might not initialize new members when they're added later
  • you don't have have to call ZeroMemory
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
-5

{0} is an anonymous array containing its element as 0.

This is used to initialize one or all elements of array with 0.

e.g. int arr[8] = {0};

In this case all the elements of arr will be initialized as 0.