Hello! Welcome to our lesson on traversing multi-dimensional arrays with pointers.
In our last session, we mastered the "right-left rule" to parse complex C declarations. We can now confidently distinguish between an array of pointers like int *p[10] and a pointer to an array like int (*p)[10]. This distinction, which might have seemed academic, is the absolute key to understanding how C handles multi-dimensional arrays at a low level.
Today, we will build directly on that knowledge. Our goal is to apply pointer arithmetic to traverse multi-dimensional arrays. You'll see precisely why a type like int (*)[10] is not just a syntactic curiosity, but the essential piece of information the compiler needs to navigate a 2D array in memory. This understanding is fundamental for writing efficient and correct code, especially when dealing with the large, structured data buffers common in ADAS applications.
How Multi-Dimensional Arrays are Stored in Memory
The first thing to understand is that computer memory is a single, one-dimensional sequence of bytes. A multi-dimensional array must be "flattened" into this linear space. C, C++, and many other languages do this using row-major order. This means the first row is stored contiguously, followed by the second row, and so on.
The opposite is column-major order, used by languages like Fortran and MATLAB, where columns are stored contiguously. Given your work involves integrating with various systems and possibly analyzing data from different sources (like MATLAB simulations), being aware of these conventions is valuable.

This contiguous, row-by-row layout is the foundation for all multi-dimensional array pointer arithmetic in C.
Pointer Types and Arithmetic in 2D Arrays
Let's connect this to our previous lesson. Consider this declaration:int arr[3][4];
This is a 2D array. Conceptually, it's an array of 3 elements, where each element is an array of 4 integers.
- The name
arrrefers to the entire array. When used in an expression, it decays to a pointer to its first element. - What is the first element of
arr? It's the first row,arr[0], which is an array of 4 integers. - Therefore,
arrdecays to a pointer to an array of 4 integers. Using the syntax we learned, its type isint (*)[4].
This type is crucial because it tells the compiler the size of the object being pointed to—an entire row. This is how pointer arithmetic scales correctly.
arrpoints to the start of the first row (&arr[0]).arr + 1points to the start of the second row (&arr[1]). The address is incremented bysizeof(int[4]), which is4 * sizeof(int)bytes.*arr(which is the same asarr[0]) refers to the first row. In an expression, it decays to a pointer to its first element,&arr[0][0]. Its type isint *.*arr + 1points to the second integer in the first row (&arr[0][1]). The address is incremented by justsizeof(int).
The following video provides an excellent visualization of these concepts and the different pointer types involved.
Pointers and multidimensional arrays
To see how pointer arithmetic works with multi-dimensional arrays, it's essential to visualize their memory organization and the types of pointers involved. This video from mycodeschool offers a clear, step-by-step explanation.
Watch from the beginning (00:24) to 07:18. Focus on the distinction between the type of B (a pointer to a 1D array) and the type of *B (a pointer to an integer). Notice how adding 1 to B moves the pointer by an entire row's worth of bytes, while adding 1 to *B moves it by only a single element.
The Equivalence of Indexing and Pointer Arithmetic
With this understanding, we can now translate standard array indexing arr[i][j] into its pointer-based equivalent. The compiler effectively performs this transformation for you.
arr[i][j] is equivalent to *(*(arr + i) + j)
Let's break this down piece by piece, using our int arr[3][4] example:
arr: A pointer to the first row (typeint (*)[4]).arr + i: Pointer arithmetic. This yields a pointer to the i-th row. The address is(address of arr) + i * sizeof(int[4]). The type is stillint (*)[4].*(arr + i): Dereferencing this pointer gives us the i-th row itself (arr[i]). When used in an expression, this array name decays to a pointer to its first element,&arr[i][0]. The type is nowint *.*(arr + i) + j: More pointer arithmetic. Since the pointer type is nowint *, we addj * sizeof(int)to the address. This gives us a pointer to the j-th element in the i-th row (&arr[i][j]).*(*(arr + i) + j): The final dereference, which retrieves the actual integer value at that address.
The following document provides a superb textual analysis of this equivalence and explains why the compiler must know the array's column dimension to make this work.
A TUTORIAL ON POINTERS AND ARRAYS IN C by Ted ...
The video provided a great visual demonstration. This classic tutorial by Ted Jensen will now solidify the concepts in text, explaining how the compiler uses the array's dimensions for pointer arithmetic.
Read 'CHAPTER 6' starting from the discussion of char multi[5][10]. Focus on the breakdown of *(*(multi + row) + col). Then, read the beginning of 'CHAPTER 7', which reinforces why the compiler requires the second dimension (COLS) to perform these calculations correctly.
Test your understanding!
Given the following array:int matrix[2][3] = { {10, 20, 30}, {40, 50, 60} };
What is the value of the expression *(*(matrix + 1) + 2)? Walk through the steps to find the answer.
Show answer
matrix: Pointer to the first row,{10, 20, 30}.matrix + 1: Pointer to the second row,{40, 50, 60}.*(matrix + 1): The second row itself,matrix[1]. This decays to a pointer to its first element,40.*(matrix + 1) + 2: A pointer to the element at index 2 of the second row (the value60).*(*(matrix + 1) + 2): The value at that address, which is 60.
This is equivalent to matrix[1][2].
Generalizing to 3D and Beyond: Performance Matters
The same logic extends naturally to higher dimensions. For a 3D array C[d1][d2][d3], the name C is a pointer to a 2D array of size d2 x d3. The expression C[i][j][k] becomes *(*(*(C + i) + j) + k).
Pointers and multidimensional arrays
The pattern of dereferencing and pointer arithmetic is consistent for arrays of any dimension. Let's briefly see this in action with a 3D array.
Watch from 07:18 to the end (13:24). Note how the logic is simply extended one level deeper for the 3D array. The code example at the end is a good way to verify these concepts on your own machine.
This deep understanding of memory layout is not merely academic; it has critical performance implications. In your work with ADAS, you process large data sets (camera images, RADAR cubes, etc.) where performance is paramount. Traversing this data in the order it's stored in memory (row by row in C) is crucial for CPU cache efficiency.
When you access elements sequentially, you benefit from spatial locality. The CPU can pre-fetch subsequent memory into its fast L1/L2 caches, leading to huge speedups. Accessing elements out of order (e.g., column by column in a row-major array) can lead to constant cache misses, forcing the CPU to wait for data from slow main memory.
Memory layout of multi-dimensional arrays
This article explains the concept of memory layout in detail and uses a benchmark to demonstrate the profound performance impact of traversing arrays in their native order.
First, read the sections 'Row-major vs. column-major' and '2D row-major' to solidify the concept. Then, read 'History: Fortran vs. C' for context. Finally, and most importantly, read the 'Performance' section. This explains why access patterns aligned with memory layout are critical for cache efficiency and shows benchmarks with dramatic results—a concept highly relevant to optimizing data processing in systems like ADAS.
Conclusion
Today we've connected the abstract syntax of pointer-to-array types with the concrete reality of memory layouts and pointer arithmetic. You can now not only read but also understand and apply the pointer logic that underpins C's multi-dimensional arrays.
Key Takeaways:
- C stores multi-dimensional arrays in a contiguous block using row-major order.
- The name of a 2D array,
T arr[R][C], decays to a pointer of typeT (*)[C]—a pointer to an array. - Pointer arithmetic on this type scales by the size of a full row (
C * sizeof(T)). - The indexing expression
arr[i][j]is equivalent to the pointer expression*(*(arr + i) + j). - Traversing data in its memory-storage order (row-by-row in C) is crucial for performance due to CPU caching and spatial locality.
Preview of the Next Lesson:
We've explored a specific, powerful type: the pointer to an array. In our next lesson, we will generalize this concept and explore pointers to pointers. This will enable us to tackle a classic C problem: how to write a function that can modify a pointer variable belonging to its caller. This technique is essential for managing dynamic resources and implementing more complex data structures.