32

What I know about C/C++ compilers is that they ignore inner braces while initializing multidimensional arrays.

So, you can't do this:

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

because the compiler will see it exactly as

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

and now it doesn't know if it is 6 * 1, 3 * 2, 2 * 3, 1 * 6, or even something else (since this can be a partial initialization list, not necessarily complete).

My question is, why does this work in many compilers?

int myArray[][2] = { { 2 }, { 4, 5 }, { 4, 1 } };

The compiler "intuitively" sees it as:

int myArray[][2] = { { 2, 0 }, { 4, 5 }, { 4, 1 } };

which means it doesn't ignore the braces. I've tried it on three different compilers till now and all worked.

I expect the answer to be "this is just compiler-dependent". I don't have access to the standard, so please provide an answer from the standard. I don't need gut feelings, I have mine.

OmarOthman
  • 1,718
  • 2
  • 19
  • 36
  • 2
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf (this is one of the two "almost published" C++11 standards - the final version contains only editoring changes AFAIK) – Spook Mar 12 '14 at 09:04
  • Only the authors of the C and/or C++ standard would know the answer to this. – Mats Petersson Mar 12 '14 at 09:07
  • 5
    @MatsPetersson: that's not true. that would render the standard almost useless. – Karoly Horvath Mar 12 '14 at 09:08
  • Eh, I misunderstood the question, I thought it was "why can't we have the compiler determine the size from what I wrote". – Mats Petersson Mar 12 '14 at 09:16
  • 1
    The reason you can't do the first example is because you can't declare a multi-dimensional array like that. Only the "left-most" dimension can have its size left out. – Lundin Mar 12 '14 at 10:08
  • @MatsPetersson It's one of those trick interview questions :) I misunderstood the question as well. I've posted a second answer (labeled Attempt 2) now that I've had more time to consider. – user3386109 Mar 12 '14 at 10:22

6 Answers6

13

The following is from section A8.7 of "The C Programming Language" by K&R, 2nd edition, pages 219,220:

An aggregate is a structure or array. If an aggregate contains members of aggregate type, the initialization rules apply recursively. Braces may be elided in the initialization as follows: if the initializer for an aggregate's member that is itself an aggregate begins with a left brace, then the succeeding comma-separated list of initializers initialize the members of the sub aggregate; it is erroneous for there to be more initializers than members. If, however, the initializer for a subaggregate does not begin with a left brace, then only enough elements from the list are taken to account of the members of the subaggregate; any remaining members are left to initialize the next member of the aggregate of which the subaggregate is a part. For example,

 int x[] = { 1, 3, 5 }; 

declares and initializes x as a 1-dimensional array with three members, since no size was specified and

there are three initializers.

Therefore, given this line

int myArray[][2] = { { 2 }, { 4, 5 }, { 4, 1 } };

the compiler will recursively initialize the array, noting that each subarray starts with a left brace and has no more than the required number of initializers, and will count the number of subarrays to determine the first dimension of the array.

The following is from section A8.7 of "The C Programming Language" by K&R, 2nd edition, page 220:

float y[4][3] = {
    { 1, 3, 5 },    
    { 2, 4, 6 },
    { 3, 5, 7 }
};

is a completely-bracketed initialization: 1,3 and 5 initialize the first row of the array y[0], namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early, and therefore the elements of y[3] are initialized with 0. Precisely the same effect could have been achieved by

float y[4][3] = {
   1, 3, 5, 2, 4, 6, 3, 5, 7 
};

Note that in both cases, the fourth row of the array will be initialized with zero, since not enough initializers were specified.

float y[4][3] = { 
    { 1 }, { 2 }, { 3 }, { 4 } 
};

initializes the first column of y and leaves the rest 0.

So the compiler doesn't ignore the inner braces. However, the inner braces are optional if you specify all of the initializers in order with no gaps. Using the inner braces gives you more control over the initialization, if you don't want to specify a full set of initializers.

Shmil The Cat
  • 4,548
  • 2
  • 28
  • 37
user3386109
  • 34,287
  • 7
  • 49
  • 68
6

The following is from A8.7 of "The C Programming Language" by K&R, 2nd edition, page 220

float y[4][3] = {
    { 1, 3, 5 },    
    { 2, 4, 6 },
    { 3, 5, 7 }
};

is equivalent to

float y[4][3] = {
   1, 3, 5, 2, 4, 6, 3, 5, 7 
};

Note that in both cases, the fourth row of the array will be initialized with zero, since not enough initializers were specified.

float y[4][3] = { 
    { 1 }, { 2 }, { 3 }, { 4 } 
};

initializes the first column of y and leaves the rest 0.

So the compiler doesn't ignore the inner braces. However, the inner braces are not required if you specify all of the initializers in order with no gaps. Using the inner braces gives you more control over the initialization if you don't want to specify a full set of initializers.

user3386109
  • 34,287
  • 7
  • 49
  • 68
2

Here are some quotes from the C Standard that can help to understand initialization of arrays.

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.

22 If an array of unknown size is initialized, its size is determined by the largest indexed element with an explicit initializer. The array type is completed at the end of its initializer list.

And here is an example from the Standard

int y[4][3] = {
    { 1, 3, 5 },
    { 2, 4, 6 },
    { 3, 5, 7 },
};

is a definition with a fully bracketed initialization: 1, 3, and 5 initialize the first row of y (the array object y[0]), namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early, so y[3] is initialized with zeros. Precisely the same effect could have been achieved by

int y[4][3] = {
    1, 3, 5, 2, 4, 6, 3, 5, 7
};

The initializer for y[0] does not begin with a left brace, so three items from the list are used. Likewise the next three are taken successively for y[1] and y[2].

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
2

ANSCI C-89 (3.5.7) says:

float y[4][3] = {
    { 1, 3, 5 },
    { 2, 4, 6 },
    { 3, 5, 7 },
};

is a definition with a fully bracketed initialization: 1, 3 and 5 initialize the first row of the array object y[0] (namely y[0][0], y[0][1] and y[0][2]). Likewise, the next two lines initialize y[1] and y[2]. The initializer ends early, so y[3] is initialized with zeroes. Precisely the same effect could have been achieved by

float y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };

The initializer for y[0] does not begin with a left brace, so three items from the list are used. Likewise, the next three are taken successively for y[1] and y[2]. Also,

float z[4][3] = {
    { 1 }, { 2 }, { 3 }, { 4 }
};

initializes the first column of z as specified and initializes the rest with zeros.

OmarOthman
  • 1,718
  • 2
  • 19
  • 36
Arjun Sreedharan
  • 11,003
  • 2
  • 26
  • 34
1

I guess, this chapter is relevant:

8.5.1 Aggregates

(...)

When initializing a multi-dimensional array, the initializer-clauses initialize the elements with the last (rightmost) index of the array varying the fastest (8.3.4). [ Example:

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

initializes x[0][0] to 3, x[0][1] to 1, x[1][0] to 4, and x[1][1] to 2. On the other hand,

float y[4][3] = {
    { 1 }, { 2 }, { 3 }, { 4 }
};

initializes the first column of y (regarded as a two-dimensional array) and leaves the rest zero. —end example ]

In a declaration of the form

T x = { a };

braces can be elided in an initializer-list as follows.105 If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [ Example:

float y[4][3] = {
    { 1, 3, 5 },
    { 2, 4, 6 },
    { 3, 5, 7 },
};

is a completely-braced initialization: 1, 3, and 5 initialize the first row of the array y[0], namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early and therefore y[3]s elements are initialized as if explicitly initialized with an expression of the form float(), that is, are initialized with 0.0. In the following example, braces in the initializer-list are elided; however the initializer-list has the same effect as the completely-braced initializer-list of the above example,

float y[4][3] = {
    1, 3, 5, 2, 4, 6, 3, 5, 7
};

The initializer for y begins with a left brace, but the one for y[0] does not, therefore three elements from the list are used. Likewise the next three are taken successively for y[1] and y[2]. —end example ]

Spook
  • 25,318
  • 18
  • 90
  • 167
1

The language specification does state that "fields that aren't given will be zero" in array initialization:

Section 8.5: paragpraph 7 of n3337 version of C++11 Standard:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list (8.5.4). [ Example:

struct S { int a; const 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. — end example ]

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227