int arr[][3] = {{2,4,6}, {8,1,7}, {2}};
sets arr[2][0]
to 2 explicitly. arr[2][1]
and arr[2][2]
are automatically set to zero—when an object is explicitly initialized but some initializers are omitted, the corresponding parts of the object are initialized as if zero had been given (null pointers for pointer types, zero for arithmetic types, and recursively so for aggregates).
In int arr[][3]
, the first dimension may be omitted because it is determined by counting the initializers. It is permitted to have an incomplete type temporarily, because nothing else depends on it while the compiler examines the initializers.
In int arr[3][]
, the second dimension may not be omitted because forming the larger array, arr[3]
, requires that its element type be complete. One reason for this is that it would be more work for the compiler to support an incomplete type, as explained below.
In justification of these requirements, observe that, to process the initializers of int arr[][3]
, all the compiler has to do is put each element of the subarray in a known place. When the compiler sees an initializer for, say, arr[i][j]
, where i
and j
are known by counting as the initializers are processed or by explicit subscript desigators (as in int arr[][3] = { [2] = { 1, 2, 3 }};
, the compiler writes the initializer value to location i*3+j
in the buffer where it is building the array values. Sometimes, when i
increases, the compiler may need to reallocate space and copy everything to the larger space (as with realloc
), but that is the only rearrangement it has to do while processing all the initializers, no matter what they are.
In contrast, consider how the compiler would have to process int arr[3][] = { … };
. The compiler could read each { … }
within the outer { … }
and figure out which one has the greatest number of elements and make that the dimension for the array. But then it has to do one of two things:
- it has to read all the initializers first to figure out the count, then go back and read them all again to put their values into the buffer being used to build the array, or
- as it is building the buffer, when it discovers a subarray initialized with
{ … }
with more elements than seen in a previous subarray, it has to rearrange most of the values in its buffer to use a new subarray size.
This is more work than we want the compiler to have to do.
Additionally, the initializers can contain expressions that cannot be evaluated if the complete type is not known, as in this atrocious code:
void *a[][3] = { {0, 0, 0}, {&a[0][1], &a[0][2], &a[0][0]} };