What is Multidimensional Array Pointer Arithmetic- C Programming For Beginners

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

PREV_LEC NEXT_LEC

Free C Programming Course · Pointers · Lecture 70

Multidimensional Array Pointer Arithmetic

See why arr, arr[0] and &arr[0][0] print the same address but behave differently, how big each pointer step really is in 2-D and 3-D arrays, and how to walk a table as one flat block. Free C tutorial for embedded systems.

2D
And 3-D layouts
5
Step sizes measured
1
Tested program

The same address can mean very different things in C. In a two-dimensional array, arr, arr[0] and &arr[0][0] all begin at the same byte, yet adding 1 to each moves you by a row, an element, or something else entirely. Understanding pointer arithmetic on multidimensional arrays in C is a core skill for embedded C programming, where lookup tables, image buffers and calibration matrices are everywhere.

This lecture of our free C programming course answers the textbook question “what do these printf calls print?” by letting the compiler tell us. Instead of guessing a base address, we measure byte differences, which are the same on every machine with the same int size.

What You Will Learn

  • Row-major memory layout for 2-D and 3-D arrays
  • The type of arr, arr[0], arr[0][0] and what each one points to
  • How sizeof behaves at each level of the array
  • How much + 1 moves each pointer
  • The correct way to get a flat int * over a 2-D array, and the caveat

Prerequisites

You should understand one-dimensional array and pointer equivalence, and the array decay rules from the previous lecture. We use int of 4 bytes in the sample output. If your toolchain uses a different size, scale the numbers accordingly.

How a 2-D Array Sits in Memory

C stores a two-dimensional array in row-major order. An array declared as int m[3][4] is, in memory, just twelve ints in a row. The first four belong to row 0, the next four to row 1, and so on. There are no hidden pointers between rows. The array-of-arrays structure exists only in the type system.

Layout of int m[3][4] in memory (row-major)
Row 0m[0][0]m[0][1]m[0][2]m[0][3]
Row 1m[1][0]m[1][1]m[1][2]m[1][3]
Row 2m[2][0]m[2][1]m[2][2]m[2][3]

Read the three rows above as one continuous strip from top to bottom. The compiler turns m[r][c] into an offset of r * 4 + c elements from the start.

Types at Every Level

ExpressionType after decayPoints tosizeof the pointed-to object+ 1 moves by
mint (*)[4]Row 0 (4 ints)16 bytes16 bytes
m[0]int *Element m[0][0]4 bytes4 bytes
&m[0][0]int *Element m[0][0]4 bytes4 bytes
&mint (*)[3][4]The entire array48 bytes48 bytes

Same address, different type

The numeric value of m, m[0] and &m[0][0] is identical. The type, and therefore the step size of pointer arithmetic, is not. The compiler uses the type to decide how many bytes + 1 adds.

Measure It: Step Sizes in 2-D and 3-D

Save this program as ep_multi_dim.c. It declares a 3 by 4 array and a 3 by 4 by 5 array, prints sizes at each level, and uses casts to char * so subtracting two pointers gives a byte count.

#include <stdio.h>

int main(void)
{
    int m[3][4];
    int c[3][4][5];

    printf("m  total/row/elem        : %zu %zu %zu\n",
           sizeof m, sizeof m[0], sizeof m[0][0]);
    printf("c  total/plane/row/elem  : %zu %zu %zu %zu\n",
           sizeof c, sizeof c[0], sizeof c[0][0], sizeof c[0][0][0]);

    printf("m+1        jumps %td bytes\n", (char *)(m + 1) - (char *)m);
    printf("m[0]+1     jumps %td bytes\n", (char *)(m[0] + 1) - (char *)m[0]);
    printf("c+1        jumps %td bytes\n", (char *)(c + 1) - (char *)c);
    printf("c[0]+1     jumps %td bytes\n", (char *)(c[0] + 1) - (char *)c[0]);
    printf("c[0][0]+1  jumps %td bytes\n", (char *)(c[0][0] + 1) - (char *)c[0][0]);

    printf("same start address? %d %d\n",
           (void *)m == (void *)m[0], (void *)m[0] == (void *)&m[0][0]);

    int k = 10;
    for (int r = 0; r < 3; r++)
        for (int q = 0; q < 4; q++)
            m[r][q] = k++;

    int *flat = &m[0][0];
    for (int i = 0; i < 12; i++)
        printf("%d ", flat[i]);
    printf("\n");
    return 0;
}

Build and Run

$ gcc -Wall -Wextra -std=c11 -o ep_multi_dim ep_multi_dim.c
$ ./ep_multi_dim
m  total/row/elem        : 48 16 4
c  total/plane/row/elem  : 240 80 20 4
m+1        jumps 16 bytes
m[0]+1     jumps 4 bytes
c+1        jumps 80 bytes
c[0]+1     jumps 20 bytes
c[0][0]+1  jumps 4 bytes
same start address? 1 1
10 11 12 13 14 15 16 17 18 19 20 21

Reading the Results

Size at each level of int c[3][4][5]
c3 planes240 bytes
c[0]4 rows80 bytes
c[0][0]5 ints20 bytes
c[0][0][0]1 int4 bytes

Each time you remove one subscript, you go one level deeper and the unit shrinks. c + 1 skips a whole 80-byte plane, c[0] + 1 skips a 20-byte row, and c[0][0] + 1 skips a single 4-byte int. The “same start address” line prints 1 1, confirming that all those names begin at the same byte.

A Flat Pointer Over a 2-D Array

Older exercises sometimes write int *p = arr; for a 2-D array and then loop over twelve elements. Modern compilers warn that the types are incompatible, because arr decays to int (*)[4], not int *. The same applies to writing int *p = &arr;, whose type is int (*)[3][4]. Both only appear to work because the address is the same.

The warning-free spelling is int *flat = &m[0][0];, as in the program, which printed the twelve values in order. Row 1 follows row 0 immediately, so a table can be walked as one list.

Standards note. Strictly speaking, the C standard only guarantees that flat[i] is valid inside the first row, because the pointer was derived from m[0][0]. Every mainstream compiler handles the flat walk correctly and it is widely used, but in safety-critical code prefer nested row and column loops, or declare the data as a one-dimensional array and compute the index yourself.

Real-World Uses in Embedded Systems

  • Calibration and lookup tables. A 2-D table of gain values indexed by temperature band and channel. Knowing the row size lets you jump to a band with one pointer addition.
  • Display frame buffers. A small monochrome display stored as uint8_t fb[8][128]. Sending one page to the controller means passing fb[page] and a length of 128.
  • Matrix maths for sensors. Rotation matrices for IMU data are small fixed-size 3 by 3 arrays.
  • Keypad scan tables. Row and column codes stored in a 2-D table, one row per scan state.

Common Mistakes and Troubleshooting

MistakeWhy it goes wrongFix
Assuming m + 1 moves one intm points to a row, so it moves one rowUse *m + 1 or m[0] + 1 for the next element
Assigning m to int *Incompatible pointer types, warningUse &m[0][0] or int (*p)[4]
Printing addresses with %uWrong format for pointers, truncation on 64-bitUse %p with a void * cast, or print byte differences
Swapping row and column sizes in a declarationTable reads wrong dataRemember the last subscript is the contiguous one
Passing int ** to a function expecting a 2-D arrayA true 2-D array is not an array of pointersDeclare the parameter as int (*m)[4]

Best Practices

  • Make the column count a named constant so the type, the loops and the function parameters all agree.
  • Use const on lookup tables so they land in flash, not RAM, on most microcontrollers.
  • Walk the last dimension in the innermost loop. It matches the memory order and is friendlier to caches.
  • Print byte distances or element indices when you need reproducible output.
  • When in doubt about a type, print sizeof of the expression. It reveals what level you are at.

Performance and Security Considerations

Row-major order means looping rows in the outer loop and columns in the inner loop reads memory sequentially, which is faster on any machine with a cache and kinder to DMA bursts. For security and correctness, index arithmetic on multidimensional arrays is a common source of off-by-one overflow, so keep each bound explicit and validated.

Suggested Images

Image ideaALT text
Rows of a 2-D array laid end to end as one memory stripRow-major layout for pointer arithmetic on multidimensional arrays in C
Nested boxes showing plane, row and element sizes3-D array sizeof levels in a free C programming course
Terminal showing 16, 4, 80, 20 byte jumpsEmbedded C programming pointer step sizes for 2D and 3D arrays

Summary and Key Takeaways

Remember

  • Multidimensional arrays are stored row-major as one contiguous block.
  • m, m[0] and &m[0][0] share an address but differ in type and step size.
  • Each subscript removed moves you one level deeper and shrinks the unit.
  • int *p = arr; on a 2-D array is a type mismatch. Use &arr[0][0].
  • Measure, do not guess. Byte differences and sizeof give you the truth.

Conclusion

Once you read every array expression as “an address plus a type”, the apparently contradictory answers in pointer exercises become predictable. The address is shared and the type decides the stride. The next lecture builds on this by comparing an array of pointers with a pointer to an array, two declarations that look nearly identical and mean very different things.

FAQ: Multidimensional Arrays and Pointers in C

Why do arr and arr[0] print the same address?

Because the first row starts at the same byte as the whole array. Their types differ, so adding 1 moves them by different amounts.

What is the difference between arr and &arr for a 2-D array?

arr decays to a pointer to the first row. &arr is a pointer to the whole array, so adding 1 skips every row.

Is a 2-D array the same as an array of pointers?

No. A 2-D array is one contiguous block with no stored row pointers. An array of pointers holds addresses that may point anywhere.

Why does my compiler warn about int *p = arr?

For a 2-D array, arr decays to a pointer to an array of ints, which is a different type from a pointer to int.

How do I pass a 2-D array to a function?

Declare the parameter with the column count, for example int m[][4] or int (*m)[4], and pass the number of rows separately.

Free Embedded C Course for Students

Keep building: next we compare arrays of pointers with pointers to arrays, a favourite interview topic.

PREV_LEC NEXT_LEC

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *