Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Subscripting a Pointer to Array
Learn how a pointer to an array in C walks a 2D array row by row, and why you can write p[i][j] on it just like on the array itself.
A pointer to an array in C is a variable that holds the address of a whole row, not of a single element. Once you store such a pointer, you can subscript it twice and reach any element of a 2D array. This lecture of our free C programming course shows the declaration, the pointer arithmetic, the equivalent expressions and a complete tested program. These ideas are everyday tools in embedded C programming, whenever a driver or filter receives a table of samples.
What You Will Learn
- How to declare a pointer to an array, and why the parentheses matter
- The difference between int (*p)[4] and int *p[4]
- How p + i moves a whole row at a time
- Three equal ways to write the same element access
- How to write a function that accepts a 2D array
- Why you can increment p but not the array name
Prerequisites
Finish the previous lecture on pointer addresses in 2D arrays first. You should know that readings[i] means *(readings+i) and that a row of 4 ints occupies 4 times sizeof(int) bytes. Any gcc or clang compiler is enough for this free C tutorial.
Declaring a Pointer to an Array in C
Suppose we have the same sensor table as before.
int readings[3][4] = {
{10, 11, 12, 13},
{20, 21, 22, 23},
{30, 31, 32, 33}
};
int (*p)[4] = readings; /* pointer to an array of 4 ints */
Read the declaration from the name outwards. p is a pointer (*p), and what it points at is an array of 4 ints ([4] applied to the parenthesised part). The assignment needs no cast because readings decays to exactly that type, int (*)[4].
Why the parentheses matter
The subscript operator binds more tightly than the star. Without the parentheses you declare something completely different.
| Declaration | Meaning | sizeof on a 64-bit PC |
|---|---|---|
| int (*p)[4] | One pointer to an array of 4 ints | sizeof p is 8; sizeof *p is 16 |
| int *q[4] | An array of 4 pointers to int | sizeof q is 32 |
How a Pointer to an Array in C Walks the Rows
Because p points at a row of 4 ints, adding 1 moves it by the size of one row. After p = readings, the expression p + 1 points at row 1, and p + 2 at row 2. The pointer never needs to know how many rows exist; it only knows how wide a row is.
Three Equal Ways to Read an Element
To reach the value in row 1, column 2 you can write any of these. They all produce 22.
| Form | How to read it |
|---|---|
| p[1][2] | Subscript the pointer twice, exactly as you would the array |
| *(*(p+1)+2) | Move to row 1, follow it to the row, move to column 2, follow it to the int |
| (*(p+1))[2] | Move to row 1, follow it, then subscript the row |
The first form works because p[1] means *(p+1), which is a row, and subscripting a row with [2] gives its third element. In other words, a pointer to an array can be used with the same double subscript as the array itself. That is why you will see both in real firmware.
Demo: A Pointer to an Array in C in Action
This program shows the sizes, the row steps, the three access forms, a pointer increment, and a function that takes a pointer to array as its parameter. Save it as ep_ptr_to_row.c.
#include <stdio.h>
#define SENSORS 3
#define SAMPLES 4
/* A parameter written as int (*m)[SAMPLES] is the same as int m[][SAMPLES]. */
static int column_sum(int (*m)[SAMPLES], size_t rows, size_t col)
{
int total = 0;
for (size_t r = 0; r < rows; r++)
total += m[r][col];
return total;
}
int main(void)
{
int readings[SENSORS][SAMPLES] = {
{10, 11, 12, 13},
{20, 21, 22, 23},
{30, 31, 32, 33}
};
int (*p)[SAMPLES] = readings; /* pointer to an array of 4 ints */
int *q[SAMPLES]; /* array of 4 pointers to int */
const char *base = (const char *)readings;
printf("sizeof(*p) = %zu bytes (one whole row)\n", sizeof *p);
printf("sizeof(q) = %zu bytes (4 pointers)\n", sizeof q);
for (int i = 0; i < SENSORS; i++)
printf("p+%d points at +%td\n", i, (const char *)(p + i) - base);
printf("p[1][2] = %d\n", p[1][2]);
printf("*(*(p+1)+2) = %d\n", *(*(p + 1) + 2));
printf("(*(p+1))[2] = %d\n", (*(p + 1))[2]);
p++; /* moves one whole row, not one int */
printf("after p++ : p[0][0] = %d, moved +%td bytes\n",
p[0][0], (const char *)p - base);
p = readings;
for (size_t c = 0; c < SAMPLES; c++)
printf("sum of sample %zu across sensors = %d\n",
c, column_sum(readings, SENSORS, c));
return 0;
}
gcc -std=c11 -Wall -Wextra -pedantic -o ep_ptr_to_row ep_ptr_to_row.c
./ep_ptr_to_row
Expected output on a 64-bit machine with 4-byte ints (the sizeof(q) line is 16 on a 32-bit MCU, because there each pointer is 4 bytes):
sizeof(*p) = 16 bytes (one whole row)
sizeof(q) = 32 bytes (4 pointers)
p+0 points at +0
p+1 points at +16
p+2 points at +32
p[1][2] = 22
*(*(p+1)+2) = 22
(*(p+1))[2] = 22
after p++ : p[0][0] = 20, moved +16 bytes
sum of sample 0 across sensors = 60
sum of sample 1 across sensors = 63
sum of sample 2 across sensors = 66
sum of sample 3 across sensors = 69
Reading the output
- sizeof *p is 16: the pointer remembers that its target is a row of 4 ints.
- The q array is 32 bytes: a completely different object made of 4 separate pointers, which is why the parentheses matter.
- p++ jumps 16 bytes: after the increment, p[0][0] is the first element of the old row 1.
- The column sums add across rows: 10+20+30 is 60, and so on.
Passing a 2D Array to a Function
A function parameter is where a pointer to an array in C appears most often. The function column_sum shows the standard way to accept a 2D array. The parameter int (*m)[SAMPLES] is a pointer to a row, and the compiler treats int m[][SAMPLES] as exactly the same thing. The column count is part of the type, so it must be known when the function is compiled. The compiler needs it to compute how far a row step is. The number of rows is not part of the type, which is why we pass it as a separate argument.
C99 also lets you write the column count as a runtime parameter, for example size_t cols, int m[][cols]. Many embedded coding standards, including MISRA C, forbid variable-length array types, so fixed column counts with named constants remain the safer habit.
Array Name vs a Pointer to an Array in C
| Property | readings (array) | p (pointer to row) |
|---|---|---|
| Can be incremented | No, it is not a modifiable lvalue | Yes, p++ moves one row |
| sizeof gives | Whole array, 48 bytes | Size of the pointer itself |
| Can be reassigned | No | Yes, p can point at another table |
| Double subscript works | Yes | Yes |
This difference is useful in practice. You can keep a pointer to the current calibration table and switch it to another table at run time, while the tables themselves stay fixed in memory.
Real-World Uses in Embedded Systems
- A pointer to a row of a const lookup table, such as a font bitmap or a CRC table, kept in flash.
- A driver that receives a block of ADC frames and walks them with one pointer, one frame per step.
- A signal-processing filter that is called once per channel with a pointer to that channel’s row.
- A menu or state table where each row describes one state and the pointer holds the current one.
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| int *p = readings; | Wrong type: int * is not int (*)[4]; the compiler warns and indexing breaks | Declare int (*p)[4] |
| Passing an int ** to a function expecting int (*)[4] | An array of arrays is not an array of pointers | Match the parameter type to the array |
| Using sizeof(p) to count rows | It returns the pointer size, not the table size | Pass the row count, or compute it where the array is declared |
| Casting to hide a type warning | The cast hides a real mismatch | Fix the declaration instead |
Best Practices for a Pointer to an Array in C
- Use a #define or enum for the column count and use it in every declaration.
- Declare the pointer const when you only read: const int (*p)[SAMPLES].
- Choose clear names such as row or frame for pointers to rows, and keep indexes named i, j.
- Performance: stepping a pointer by rows compiles to a single add of a constant, which is as cheap as it gets on small cores.
- Safety: the pointer carries no length. Always check the row count before walking.
Summary and Key Takeaways
- int (*p)[N] is a pointer to a row of N ints; int *q[N] is an array of N pointers.
- p + i moves i rows, and p[i][j] works exactly like the array subscript.
- p[i][j], *(*(p+i)+j) and (*(p+i))[j] are equal.
- A function that takes a 2D array needs the column count in the parameter type.
- You can increment the pointer, but never the array name.
You now know how a pointer to an array in C is declared, stepped and subscripted. Next we extend the same idea one level deeper with three-dimensional arrays. Continue through this free embedded C course to see how the pattern repeats.
Frequently Asked Questions
What is a pointer to an array in C?
It is a pointer whose target type is a whole array, for example int (*p)[4]. Adding 1 to it moves past the entire array, not a single element.
What is the difference between int (*p)[4] and int *p[4]?
The first is a single pointer to an array of 4 ints. The second is an array of 4 pointers to int. The parentheses change the meaning completely.
Can I subscript a pointer to an array twice?
Yes. p[i][j] is valid because p[i] is a row and the second subscript picks an element from that row.
Why does my function need the number of columns?
The column count is part of the row type. Without it the compiler cannot compute where each row starts.
Why can I do p++ but not readings++?
An array name is not a modifiable lvalue; it always refers to the same block of memory. A pointer is an ordinary variable, so it can be changed.
Is this useful for embedded C programming?
Yes. Lookup tables, sample buffers and state tables are commonly walked with pointers to rows, especially in const data stored in flash.
Continue the Free C Tutorial
You are working through a free C programming course built for embedded systems students. Move on to three-dimensional arrays next, or return to the course index to pick any topic.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments