Ch5.9: Multi-dimensional C-style Arrays

Overview

A multi-dimensional C-style array is simply an array whose elements are themselves C-style arrays. The most common form is the two-dimensional array, which you can think of as a table of rows and columns.

In this chapter, you will learn:

1. Declaring a multi-dimensional C-style array

A two-dimensional array is declared by specifying both dimensions:


int a[2][3]{
    {1, 2, 3},
    {4, 5, 6}
};

This means:

2. Memory layout: row-major order

C++ stores multi-dimensional arrays in row-major order. This means the entire first row is stored first, then the second row, and so on.


int a[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

Memory (addresses vary):

Address:   1000 1004 1008 1012 1016 1020
Memory:    [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
              ↑    ↑    ↑    ↑    ↑    ↑
             a[0][0] ...        ...  a[1][2]

Even though the array looks like a grid, it is stored as one contiguous block.

3. Indexing multi-dimensional arrays

You access elements using multiple [] operators:


int x = a[0][1];   // 2
int y = a[1][2];   // 6

The expression a[i] refers to the i-th row, which is itself an array of 3 int.


int b[3] = a[0];   // ❌ error: cannot copy arrays

But you can take its address:


int (*row0)[3] = ::std::addressof(a[0]);

The type of a[0] is int[3]. The type of &a[0] is int (*)[3].

4. Decay of multi-dimensional arrays

Just like one-dimensional arrays, multi-dimensional arrays decay in most expressions — but only the outermost dimension decays.


int a[2][3];

int (*p)[3] = a;   // decay: a → pointer to array of 3 int

Important:

This is why:


int *p0 = a[0];   // decay: a[0] → &a[0][0]

But:


int (*p1)[3] = a;   // pointer to row

5. Pointer arithmetic with multi-dimensional arrays

Pointer arithmetic on int (*)[3] moves by entire rows.


int (*p)[3] = a;

int (*p_next)[3] = p + 1;   // moves to a[1]

Meanwhile, pointer arithmetic on int* moves by individual elements.


int *q = a[0];     // points to a[0][0]
int *q2 = q + 4;   // points to a[1][1]

This works because the entire 2×3 array is contiguous.

6. Using ::std::ranges::begin and ::std::ranges::end

You can obtain iterators for the outer dimension:


#include 

int a[2][3]{{1,2,3},{4,5,6}};

int (*first)[3] = ::std::ranges::begin(a);
int (*last)[3]  = ::std::ranges::end(a);

These compute:

They do not search for '\0' because this is not a string.

7. Higher-dimensional arrays

You can extend this to any number of dimensions:


int a[2][3][4];

This is an array of 2 elements, each of which is an array of 3 elements, each of which is an array of 4 int.

The memory is still one contiguous block.

Key takeaways