5.1.11 · D5C Programming

Question bank — Multi-dimensional arrays

1,528 words7 min readBack to topic

Reminders of the symbols used throughout (all from the parent note):

  • = number of rows, = number of columns, = bytes per element.
  • "Row-major" = stored row by row (all of row 0, then all of row 1, …).
  • means ; means .

True or false — justify

int a[3][4] reserves memory for exactly 12 ints.
True. elements, laid out as one contiguous flat block; the "3 rows of 4" is only a mental grid.
The number of rows appears in the address formula for a[i][j].
False. The formula is — only appears. That is exactly why C lets you omit the leftmost (row) dimension but never the column one.
a[i] and *(a+i) are two different things for a 2D array.
False. They are identical by definition of subscripting. a[i] is literally shorthand for *(a+i), so they compute the same row.
For int a[3][4], the pointer a and a[0] hold the same numeric address.
True in value, false in type. Both point at a[0][0], but a has type int(*)[4] (steps 16 bytes) while a[0] decays to int* (steps 4 bytes). See Pointer to Array vs Array of Pointers.
Adding 1 to a (an int(*)[4]) advances by 4 bytes.
False. It advances by one whole row = bytes, because the pointer's target type is "array of 4 ints", not int. See Pointers in C.
A 2D array and an array of pointers int *a[3] are stored the same way.
False. int a[3][4] is one flat 12-int block; int *a[3] is 3 pointers that may point to scattered, separately-allocated rows. See Pointer to Array vs Array of Pointers.
int m[2][3] = {1,2,3,4,5,6}; and int m[2][3] = {{1,2,3},{4,5,6}}; produce identical memory.
True. The flat list is filled in row-major order, so both give row 0 = {1,2,3}, row 1 = {4,5,6}.
You can change how C stores a 2D array to column-major with a keyword.
False. C is always row-major; there is no switch. Column-major is Fortran/MATLAB territory — see Row-major vs Column-major.
You can initialize int *a[3] = {1, 2, 3}; the same way you'd fill a 2D array's first row.
False. An array of pointers must be filled with pointer constants (e.g. NULL or addresses), not plain integers. {1,2,3} are int values, not addresses, so each is an invalid-pointer error/warning. See Pointer to Array vs Array of Pointers.

Spot the error

int p[][] = {{1,2},{3,4}}; — what's wrong?
The inner dimension is missing. The compiler needs for the step; only the leftmost dimension may be inferred. Write int p[][2].
a[1,2] to read row 1, column 2 — what's wrong?
1,2 is the comma operator, which evaluates to 2. So a[1,2] means a[2] — an entire row pointer, not element (1,2). Correct: a[1][2].
void f(int a[][], int rows, int cols){...} as a parameter — what's wrong?
Same trap: the inner dimension can't be blank. Write int a[][cols] — but note the C99 ordering rule below: cols must be declared before the array parameter. So the correct header is void f(int rows, int cols, int a[][cols]), or use a fixed int a[][4].
void f(int a[][cols], int rows, int cols){...} — is the ordering fine?
No. In a VLA-parameter declaration the size expression (cols) must name a parameter that already appeared earlier in the list. Here cols is used before it's declared → compile error. Put the size parameters first: void f(int rows, int cols, int a[][cols]). See Variable Length Arrays (C99).
void g(int **a){ a[1][2]=0; } called with a real int m[3][4] — what's wrong?
int** means "pointer to pointer", so a[1] is dereferenced as an address to read. But m decays to int(*)[4], whose bytes are raw ints, not pointers → garbage/crash. The correct type is int (*a)[4]. See Array Decay.
int a[3][4]; int *p = a; — what's wrong?
Type mismatch. a decays to int(*)[4], not int*. Use int (*p)[4] = a;, or int *p = &a[0][0]; if you truly want an int pointer.
Skipping to the "next row" with p++ where int *p = &a[0][0]; — what's the trap?
p++ moves one int, landing on a[0][1], not the next row. To jump a row you'd add : p += 4. Row jumps only happen automatically with an int(*)[4] pointer.

Why questions

Why is int a[3][4] called an "array of arrays" and not a "grid type"?
Because C only has true 1D arrays. int a[3][4] parses as int (a[3])[4]: an array of 3 elements, each of type int[4]. The grid is a mental model over that. See C Arrays (1D).
Why must every dimension except the first be specified when passing a 2D array?
To evaluate a[i][j] the compiler needs for the offset; it never needs . So the row count can be omitted but the column count cannot.
Why does sizeof(a) give 48 for int a[3][4] but sizeof(a) inside a function taking int a[][4] give only 8?
In the declaration, a is a real array → bytes. As a parameter it has already decayed to a pointer int(*)[4], so sizeof reports pointer size (~8). See sizeof Operator and Array Decay.
Why does a+i jump a whole row while a[i]+j jumps single ints?
a has type int(*)[4], so +1 steps one row (16 bytes). a[i] decays to int*, so +1 steps one int (4 bytes). The pointer's target type sets the stride.
Why is a[i][j] provably equal to j[a[i]] in C?
Because x[y] is defined as *(x+y), and integer addition commutes. Take the row r = a[i], which decays to an int*. Then r[j] means *(r+j), and j[r] means *(j+r); since r+j == j+r, both name the same element. So a[i][j] equals j[a[i]] — legal but horrible style.
Why doesn't the row count matter for addressing but the total memory obviously depends on it?
Addressing an existing element only needs offsets forward from base, which use . Allocating the block needs the full size — a different question that does use .

Edge cases

What is int a[0][4]; — legal?
Standard C forbids zero-length arrays (it's a GCC extension). A dimension of 0 means "no rows", so no storage — avoid it in portable code.
Does int a[3][4] = {0}; zero all 12 elements or just the first?
All 12. A partially-provided initializer zero-fills the rest, and {0} supplies one explicit 0 then zeros everything remaining.
For int a[3][4] = {{1},{2},{3}};, what is a[0][3]?
0. Each inner brace fills one row from the left; the unspecified a[0][1..3] are zero-initialized. So row 0 = {1,0,0,0}.
Is a[3] a valid expression for int a[3][4] (index equals the size)?
The expression is legal to form (it's *(a+3), a valid pointer just past the array), but dereferencing or reading through it is undefined behaviour — index 3 is out of the 0..2 range.
In a VLA int a[rows][cols] with runtime cols, is it still row-major?
Yes. VLAs only defer the sizes to runtime; the layout rule is unchanged — one flat block, row by row, addressed with the same using the runtime cols.
If cols is 0 at runtime for a VLA, is the array simply "empty"?
No — it's not allowed. A VLA bound must be a positive value; a bound of 0 (or negative) is prohibited and gives undefined behaviour, not a well-defined empty array. Always validate cols > 0 before declaring the VLA.

Recall One-line self-test

Everything on this page reduces to one sentence — say it. The sentence ::: A 2D array is one flat row-major block whose element a[i][j] lives at base + (i*C + j)*sizeof(T), which needs the column count but never the row count .


Connections

  • Multi-dimensional arrays — the parent topic these traps drill.
  • C Arrays (1D) — the "array of arrays" foundation.
  • Pointers in C — every subscript is pointer arithmetic.
  • Array Decay — why sizeof and pointer types shift inside functions.
  • Pointer to Array vs Array of Pointersint(*p)[4] vs int *p[4].
  • sizeof Operator — array vs decayed-pointer sizes.
  • Row-major vs Column-major — why the layout is fixed in C.
  • Variable Length Arrays (C99) — runtime-sized multi-D parameters.