Multidimensional Arrays
An array of arrays gives you a grid: rows and columns for game boards, spreadsheets, images and matrices. In memory it's still one block, stored row after row.
A 2D array
#include <stdio.h> int main(void) { int seats[3][4] = { { 1, 0, 0, 1 }, { 1, 1, 0, 0 }, { 0, 0, 0, 1 }, }; seats[2][1] = 1; // book row 2, seat 1 for (int row = 0; row < 3; row++) { printf("Row %d: ", row); for (int col = 0; col < 4; col++) { printf("%c ", seats[row][col] ? 'X' : '.'); } printf("\n"); } return 0; }
Row 0: X . . X Row 1: X X . . Row 2: . X . X
int seats[3][4] is 3 rows, each an array of 4 ints. The first index picks the row, the second the column.
Row-major layout
The rows are stored one after another, so seats[1][0] comes straight after seats[0][3]. That's why looping over rows in the outer loop and columns in the inner loop is the fast order — it reads memory sequentially:
#include <stdio.h> int main(void) { int grid[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; long gap = (char *) &grid[1][0] - (char *) &grid[0][2]; printf("grid[1][0] starts %ld bytes after grid[0][2]\n", gap); printf("one row: %zu bytes, whole grid: %zu bytes\n", sizeof grid[0], sizeof grid); return 0; }
grid[1][0] starts 4 bytes after grid[0][2] one row: 12 bytes, whole grid: 24 bytes
Passing to functions
A function receiving a 2D array must know every dimension except the first, so it can work out where each row starts:
#include <stdio.h> #define COLS 3 int row_sum(const int m[][COLS], int row) { int total = 0; for (int c = 0; c < COLS; c++) total += m[row][c]; return total; } void print_matrix(int rows, const int m[][COLS]) { for (int r = 0; r < rows; r++) { for (int c = 0; c < COLS; c++) printf("%4d", m[r][c]); printf(" | sum %d\n", row_sum(m, r)); } } int main(void) { int sales[4][COLS] = { { 5, 3, 8 }, { 2, 9, 4 }, { 7, 7, 1 }, { 0, 6, 3 } }; print_matrix(4, sales); return 0; }
5 3 8 | sum 16 2 9 4 | sum 15 7 7 1 | sum 15 0 6 3 | sum 9
For grids whose size is only known at run time, a common approach is a single 1D block of rows * cols elements allocated with malloc, indexed as m[r * cols + c].
Matrix multiplication
#include <stdio.h> int main(void) { int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; int b[3][2] = { { 7, 8 }, { 9, 10 }, { 11, 12 } }; int product[2][2] = { 0 }; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 3; k++) product[i][j] += a[i][k] * b[k][j]; for (int i = 0; i < 2; i++) printf("%4d %4d\n", product[i][0], product[i][1]); return 0; }
58 64 139 154
Arrays of strings
A 2D char array holds several fixed-size strings; an array of pointers to string literals is the usual alternative when the strings never change:
#include <stdio.h> #include <string.h> int main(void) { char names[3][12] = { "Ana", "Bartholomew", "Cy" }; const char *days[] = { "Mon", "Tue", "Wed" }; strcpy(names[2], "Cyrus"); for (int i = 0; i < 3; i++) printf("%-12s %s\n", names[i], days[i]); return 0; }
Ana Mon Bartholomew Tue Cyrus Wed
Each row needs room for the longest string plus its terminating '\0' character (lesson 28): "Bartholomew" has 11 letters, so the rows are 12 characters wide. A row of 10 would leave no room for the terminator, and printing that name would run on into the next row.
Three or more dimensions
The idea extends naturally: double temps[12][31][24] could hold an hourly temperature for every day of a year. In practice more than two dimensions is rare, and a struct is often clearer.
Try it yourself
Play noughts and crosses: given a board, check every row, column and diagonal for a winner.
Winner: X