Two-Dimensional Arrays
Matrices in row-major storage, nested-loop traversal, the matrix addition and multiplication programs exams expect, and how the rules generalize to three dimensions and beyond.
Grids
Tables of marks, game boards, images, matrices: a great deal of data is naturally rows and columns. C's two-dimensional array puts a grid behind one name, and last chapter's nested loops, outer rows, inner columns, are exactly its walking shape. This lesson covers declaration, the memory truth underneath, the matrix programs every exam sets, and what happens when two dimensions are not enough.
Declaring and Indexing
#define ROWS 3
#define COLS 4
int table[ROWS][COLS];
Read the type inside out: table is an array of 3 elements, each of which is an array of 4 ints, rows of columns. table[r][c] picks row r, column c, each index 0-based with the same bounds discipline as before: rows 0 to 2, columns 0 to 3, and anything outside either range is undefined behaviour.
The bracket pairs are not optional decorations: table[1, 2], a favorite exam distractor, compiles as the comma operator evaluating 1, discarding it, and indexing with 2, one index, wrong meaning. Two dimensions, two bracket pairs, always.
Row-Major: The Memory Truth
A 2D array is not a grid in memory; memory is one-dimensional. C stores it row-major: row 0's four elements first, then row 1's, then row 2's, twelve ints in one contiguous run.
table[0][0] table[0][1] table[0][2] table[0][3] table[1][0] table[1][1] ...
table[r][c] is again pure arithmetic: start plus (r times COLS plus c) elements. And walking a row (inner index varying) touches adjacent memory, which is both the natural loop order and the faster one on real machines, an observation that matters enormously at scale.
Initializing a Grid
Nested braces mirror the rows, and the inner braces are what keep a partial list honest: each one fills its own row and the rest of that row is zeroed, exactly as the single-dimension rule promised.
int grid[2][3] = {{1, 2, 3}, {4, 5}}; /* grid[1][2] is 0 */
int zeros[2][3] = {{0}}; /* all six zero */
int sparse[2][3] = {{1, 2}, {4}}; /* 1 2 0 / 4 0 0 */
Only the first dimension may be left empty, and only when the initializer is there to count it. The columns are what turn a pair of subscripts into an offset, so the compiler cannot proceed without them:
int table[][3] = {{0, 0, 0}, {1, 1, 1}}; /* fine: two rows, counted */
Leaving out any other dimension is not a style question, it is rejected:
int bad[2][] = {{1, 2, 3}, {4, 5, 6}};
error: array type has incomplete element type 'int[]'
note: declaration of 'bad' as multidimensional array must have bounds for all dimensions except the first
There is one more form, and it is the one old exam papers print. Because the elements are one contiguous run, a flat list fills the grid row by row with no inner braces at all, and the standard treats it as equivalent to the nested version:
int flat[][3] = {1, 2, 3, 4, 5, 6}; /* same object as {{1, 2, 3}, {4, 5, 6}} */
It compiles and it is correct C89, but this course's compiler settings have an opinion about it:
warning: missing braces around initializer [-Wmissing-braces]
5 | int flat[][3] = {1, 2, 3, 4, 5, 6};
| ^
| { } { }
Read the diagnostic as advice rather than an accusation: with the braces elided, nothing in the text of the declaration says where one row ends, so a miscounted list silently shifts every value into the wrong row. Recognize the flat form when a question shows it, and write the nested form yourself.
The Matrix Programs
Reading, computing, and printing a grid is three nested-loop passes. Here is the exam classic in full, row sums of a fixed 2 by 3 matrix:
Type six numbers. Two details carry the pedagogy: sum = 0; sits inside the outer loop, resetting per row, misplacing it is the standard planted bug, and the human-facing row number is r + 1 while the index stays 0-based.
Transpose is the smallest delta on this skeleton: write result[c][r] = matrix[r][c] and the grid arrives rotated. Addition and multiplication are worth writing out in full, because they are the two the examiner reaches for.
Matrix Addition
Adding two matrices means adding corresponding elements, so it needs both grids to have the same shape and one pass over a third grid to hold the answer. The inner statement is the whole idea:
11 22 33
44 55 66
One r, one c, one index pair used on all three grids: there is no cross-talk between positions at all. Subtraction is the same program with a minus sign, and scaling by a number is the same program with a[r][c] * factor.
Matrix Multiplication
Multiplication is the one that breaks the pattern, and knowing why is the exam answer. Element [r][c] of the product is not built from element [r][c] of the inputs; it is the sum of products of row r of the first against column c of the second. That summation needs a loop of its own, so three indexes are in play: r walks the product's rows, c its columns, and a third, k, walks along the row and down the column together.
The shapes have to agree: the first matrix's column count must equal the second's row count, and that shared number is exactly what k counts. A 2 by 3 times a 3 by 2 gives a 2 by 2.
58 64
139 154
Check the first element by hand, because that is what a paper asks for: 1*7 + 2*9 + 3*11 is 7 + 18 + 33, which is 58. Two details carry the marks. product[r][c] = 0; must sit outside the k loop but inside the c loop, the row-sum reset discipline one level deeper. And the subscript pattern a[r][k] * b[k][c] is what to memorize: k second on the left, first on the right, which is "across the row, down the column" written in indexes. Writing b[c][k] instead is the classic wrong answer, and on square matrices it compiles and runs while quietly computing something else.
Note also that b is declared int b[ACOLS][BCOLS]: the shared dimension is named once, so the declarations enforce the shape requirement instead of leaving it to memory.
Beyond Two Dimensions
Nothing about the design stops at two. The general form is one bracket pair per dimension:
type name[s1][s2][s3] ... [sm];
int survey[3][5][12]; /* 3 * 5 * 12 = 180 ints */
float table[5][4][5][3]; /* 5 * 4 * 5 * 3 = 300 floats */
Multiply the sizes to count the elements, which works because the storage is still one flat contiguous run. If survey holds rainfall for three years, five cities, and twelve months, then survey[2][3][10] is November of the third year in city index 3. C89 sets no limit on the number of dimensions.
The layout rule generalizes row-major exactly: the rightmost subscript varies fastest, and each subscript to its left advances only once everything to its right has wrapped. A 2 by 2 by 3 array sits in memory in this order:
000 001 002 010 011 012 100 101 102 110 111 112
Which is the same as saying a three-dimensional array is a stack of two-dimensional tables: fix the leftmost subscript and an ordinary grid remains. Filling one in nested-loop order and printing it table by table shows both facts at once:
year 0
1 2 3
4 5 6
year 1
7 8 9
10 11 12
elements = 12
The counter advanced once per element in loop order, so those numbers are the memory positions: 1 to 6 is the first table, 7 to 12 the second. The element-count idiom survives, with the divisor drilled down to a single element. Three loops for three dimensions, and the initializer rules nest the same way: one brace level per dimension, first dimension the only omittable one.
Bounds, Squared
Every bounds rule from last lesson applies per dimension, and the 2D-specific mistake is swapping them: with 2 rows and 3 columns, matrix[2][0] is out of bounds even though 2 is a fine column index. Keeping r strictly under ROWS and c strictly under COLS, names matching the declaration, makes the swap visible; single-letter i/j pairs are where it hides. AddressSanitizer aborts either way, but on the row overflow the arithmetic may land inside the neighboring row, an in-bounds-looking corruption no tool can flag, which is why the discipline is the defense.
Key Takeaways
int t[ROWS][COLS]is rows of columns; each index is 0-based with its own bound, andt[r][c]needs both bracket pairs,t[r, c]is the comma operator in disguise.- Storage is row-major and contiguous: row 0 fully, then row 1; nested braces mirror rows in initializers,
{{0}}zeroes everything. - Only the first dimension may be omitted (
int t[][3]), and only with an initializer to count it; the flat brace-less list is legal and equivalent but warns, so read it and do not write it. - The three-pass skeleton (read, compute, print) with nested loops handles row sums, addition, and transpose; addition works element by element on matching shapes.
- Multiplication needs a third index: accumulate
a[r][k] * b[k][c]overk, reset the element before thekloop, and the first matrix's column count must equal the second's row count. - Per-row accumulators reset inside the outer loop; human numbering is index plus one.
- Any number of dimensions is allowed: multiply the sizes for the element count, the rightmost subscript varies fastest, and a 3D array is a stack of 2D tables.
- Bounds apply per dimension, and swapped indexes are the 2D-specific bug; matching names (
r/ROWS,c/COLS) keep it visible.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Two-Dimensional Arrays - Quiz
Test your understanding of the lesson.
Practice Exercises
Transpose the Matrix
Read a 2 by 3 matrix (six integers, row by row) and print its 3 by 2 transpose, rows separated by newlines and values by single spaces. The transpose swaps the roles of row and column: element [r][c] of the input becomes element [c][r] of the output.
Multiply Two Matrices
Read a 2 by 3 matrix and then a 3 by 2 matrix, both row by row, and print their 2 by 2 product: one row per line, values separated by a single space. Element [r][c] of the product is the sum of a[r][k] * b[k][c] over every k, so this needs a third loop inside the row and column loops. Guard every read and print "invalid input" if any value fails to read.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!