Code of the Day
BeginnerArrays and strings

Multidimensional arrays

Represent tables and matrices in C using two-dimensional arrays — declaration, indexing, nested loops, and how they are laid out in memory.

CBeginner9 min read
By the end of this lesson you will be able to:
  • Declare and initialise a two-dimensional array
  • Access elements with row and column indices
  • Use nested loops to iterate over all elements
  • Explain that 2D arrays are stored in row-major order in memory

A one-dimensional array is a sequence. A two-dimensional array is a table — rows and columns. C supports multidimensional arrays directly, and understanding how they work in memory is important for writing efficient code.

Declaring a 2D array

int matrix[3][4]; /* 3 rows, 4 columns — 12 ints total */

The first dimension is the number of rows; the second is the number of columns. With initialisation:

int matrix[2][3] = {
    {1, 2, 3},   /* row 0 */
    {4, 5, 6}    /* row 1 */
};

Accessing elements

matrix[1][2] = 99;      /* set row 1, column 2 to 99 */
printf("%d\n", matrix[0][1]); /* print row 0, column 1 -- value 2 */

matrix[row][col] is the convention. Both indices are zero-based.

Iterating with nested loops

#include <stdio.h>

int main(void) {
    int grid[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };

    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 4; col++) {
            printf("%3d", grid[row][col]);
        }
        printf("\n");
    }

    return 0;
}

Output:

  1  2  3  4
  5  6  7  8
  9 10 11 12

The outer loop traverses rows; the inner traverses columns within each row.

Row-major layout in memory

C stores two-dimensional arrays in row-major order: all elements of row 0 come first in memory, then all elements of row 1, and so on.

grid[0][0] grid[0][1] grid[0][2] grid[0][3]
grid[1][0] grid[1][1] grid[1][2] grid[1][3]
grid[2][0] grid[2][1] grid[2][2] grid[2][3]

In memory, they are laid out consecutively:

address: base+0  base+4  base+8  base+12  base+16  base+20 ...
value:   1       2       3       4        5        6       ...

This means iterating row by row (outer loop = row, inner loop = col) accesses memory sequentially — which is cache-friendly and fast. Iterating column by column (outer = col, inner = row) jumps around in memory, which is slower on modern CPUs due to cache misses.

Cache locality matters. For large matrices, the difference between row-major iteration and column-major iteration can be 5–10× in performance on real hardware. This is a preview of the memory alignment and CPU cache concepts covered in the Advanced tier.

Passing 2D arrays to functions

Passing 2D arrays requires specifying all dimensions except the first:

void print_matrix(int arr[][4], int rows) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < 4; c++) {
            printf("%4d", arr[r][c]);
        }
        printf("\n");
    }
}

The column count 4 must be known at compile time so the compiler can compute arr[r][c] as *(arr + r*4 + c). The row count rows can be a parameter.

A practical example: transpose a matrix

#include <stdio.h>

void transpose(int src[][3], int dst[][2], int rows, int cols) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            dst[c][r] = src[r][c];
        }
    }
}

int main(void) {
    int original[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int transposed[3][2];

    transpose(original, transposed, 2, 3);

    for (int r = 0; r < 3; r++) {
        printf("%d %d\n", transposed[r][0], transposed[r][1]);
    }
    return 0;
}

Output:

1 4
2 5
3 6

Where to go next

Next: C strings — how text is represented in C as null-terminated char arrays, and why this design is both powerful and dangerous.

Finished reading? Mark it complete to track your progress.

On this page