Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Array of Pointers Explained
An array of pointers and a pointer to an array differ by one pair of parentheses and by a lot of behaviour. Learn to read both declarations, measure them, and choose the right one for command tables and 2-D data. Free C tutorial for embedded systems.
Compare int *pa[3] with int (*p)[4]. They differ by a pair of parentheses, yet the first is an array of pointers in C holding three addresses, while the second is a single pointer that steps over whole rows of four ints. Mixing them up is one of the favourite traps in written tests and interviews for embedded C programming jobs.
In this lecture of our free C programming course we take the classic exercise that fills both from a 3 by 4 array and prints the same elements through each, rewrite it, and measure sizes and step distances to see exactly what differs.
What You Will Learn
- How to read
int *a[3]andint (*a)[4]correctly - Why both can print the same table values but occupy different memory
- Sizes and step distances measured on a real compiler
- When an array of pointers is the right tool: ragged tables and string lists
- When a pointer to an array is better: walking true 2-D data and passing it to functions
- How typedef makes the declarations readable
Prerequisites
You should be comfortable with the previous lecture on multidimensional array layout, including the fact that m for int m[3][4] decays to int (*)[4]. Familiarity with C strings helps for the command-table example.
Reading C Declarations: The Right-Left Rule
Start at the name. Read to the right until you hit a closing parenthesis or the end, then read to the left, and repeat. Brackets bind tighter than the star, which is why the parentheses matter.
| Declaration | Read as | Meaning |
|---|---|---|
int *row_ptrs[3] | row_ptrs is an array of 3 pointers to int | Three separate pointers, 24 bytes on a 64-bit system |
int (*grid_ptr)[4] | grid_ptr is a pointer to an array of 4 ints | One pointer, which steps a whole row at a time |
int *(*fn_tab[2])(void) | fn_tab is an array of 2 pointers to functions returning int pointer | Shows how far the rule scales |
Memory Picture: Pointer Array vs Pointer to Array
Run the Comparison
Save this as ep_ptr_arrays.c. The first half fills both pointer styles from the same table. The second half builds a small ragged table of modem-style command strings, a natural use for an array of pointers.
#include <stdio.h>
#include <string.h>
int main(void)
{
int grid[3][4] = {{10, 11, 12, 13}, {20, 21, 22, 23}, {30, 31, 32, 33}};
int *row_ptrs[3]; /* array of 3 pointers to int */
int (*grid_ptr)[4]; /* pointer to an array of 4 ints */
for (int i = 0; i < 3; i++)
row_ptrs[i] = grid[i];
grid_ptr = grid;
printf("sizeof row_ptrs=%zu grid_ptr=%zu *grid_ptr=%zu\n",
sizeof row_ptrs, sizeof grid_ptr, sizeof *grid_ptr);
printf("%d %d %d\n", row_ptrs[0][0], row_ptrs[0][1], row_ptrs[2][3]);
printf("%d %d %d\n", grid_ptr[0][0], grid_ptr[0][1], grid_ptr[2][3]);
printf("row_ptrs+1 steps %td bytes, grid_ptr+1 steps %td bytes\n",
(char *)(row_ptrs + 1) - (char *)row_ptrs,
(char *)(grid_ptr + 1) - (char *)grid_ptr);
const char *cmds[] = {"AT", "AT+CSQ", "AT+CREG?"};
for (size_t i = 0; i < sizeof cmds / sizeof cmds[0]; i++)
printf("cmd %zu: %-9s len=%zu\n", i, cmds[i], strlen(cmds[i]));
return 0;
}
Build and Run
$ gcc -Wall -Wextra -std=c11 -o ep_ptr_arrays ep_ptr_arrays.c
$ ./ep_ptr_arrays
sizeof row_ptrs=24 grid_ptr=8 *grid_ptr=16
10 11 33
10 11 33
row_ptrs+1 steps 8 bytes, grid_ptr+1 steps 16 bytes
cmd 0: AT len=2
cmd 1: AT+CSQ len=6
cmd 2: AT+CREG? len=8
What the Numbers Say
| Observation | Array of pointers | Pointer to array |
|---|---|---|
sizeof of the variable | 24 bytes (3 pointers) | 8 bytes (1 pointer) |
sizeof of what it points at | 4 bytes (one int) | 16 bytes (a row of four) |
Step of + 1 | 8 bytes (next pointer in the array) | 16 bytes (next row of data) |
Values printed with [i][j] | Same table values | Same table values |
Subscripting reads identically for both, which is why the two look interchangeable. The difference is what lives behind that subscript. For row_ptrs[2][3] the CPU loads a pointer from the array and then an int from wherever it points. For grid_ptr[2][3] it computes the address with arithmetic alone and loads one int.
Array of Pointers in Embedded C: Where It Shines
- Command and string tables. The
cmdsarray above holds strings of different lengths with no wasted padding. Declare it asconst char *const cmds[]so both the table and the strings sit in flash. - Ragged data. Rows of different lengths, such as per-channel calibration lists, fit naturally.
- Dispatch tables. An array of function pointers indexed by an opcode or interrupt number is the same idea with a different pointee.
- Reordering without copying. To sort big records, sort an array of pointers to them and leave the data where it is.
Pointer to Array: Where It Shines
- Walking a real 2-D table. One pointer, with no extra storage, steps row by row.
- Function parameters.
void scale_rows(int (*m)[4], size_t rows)accepts any table with four columns and lets you writem[r][c]inside. - Fixed-size frames. A buffer of equal-length packets, each 16 bytes, processed one packet per step.
Make It Readable With typedef
A typedef turns the awkward spelling into something a teammate can read at a glance:
typedef int row4_t[4]; /* one row of four ints */
void scale_rows(row4_t *m, size_t rows); /* same as int (*m)[4] */
Choosing Between Them
| Situation | Better choice | Reason |
|---|---|---|
| List of strings in flash | Array of pointers | Different lengths, no padding |
| Fixed-width numeric table | Pointer to array, or a plain 2-D array | No extra pointer storage, simple arithmetic |
| Rows of varying length | Array of pointers | Each row can be its own size |
| Passing a table to a function | Pointer to array | Compiler knows the row size |
| Sorting large records | Array of pointers | Move pointers, not data |
Common Mistakes and Troubleshooting
| Mistake | Effect | Fix |
|---|---|---|
Dropping the parentheses in int (*p)[4] | Declares an array of pointers instead | Re-read the declaration with the right-left rule |
Passing a 2-D array to int ** | Type mismatch, crash at run time | Use int (*)[cols] |
| Forgetting to initialise the pointer array | Wild pointers | Fill or NULL-initialise every entry |
| Pointing array entries at local buffers | Dangling pointers after return | Use static or caller-owned storage |
| Modifying a string literal through a table entry | Undefined behaviour, often a fault | Declare entries as const char * |
sizeof tells you about the pointer slots, not the data. Do not use it where a function expects contiguous rows.
Best Practices
- Use
const char *constfor string tables so nothing can be changed or reassigned, and the linker can place them in flash. - Compute table length once with
sizeof tab / sizeof tab[0]and never hard-code it a second time. - Use typedefs for row types and function-pointer types.
- Prefer plain 2-D arrays and pointers to arrays for numeric tables, to avoid extra indirection.
- Annotate tables with a comment saying who owns the pointed-to storage.
Performance and Security Considerations
An extra pointer load per access is a small cost but can matter in a tight loop on a core without a cache. On the security side, a table of pointers into writable memory is a tempting target. Keep dispatch and string tables const so they cannot be overwritten at run time, and bounds-check every index that comes from outside the device.
Suggested Images
| Image idea | ALT text |
|---|---|
| Three pointer slots each pointing to a row | Array of pointers in C pointing at rows of an integer table |
| A single pointer stepping across rows of a table | Pointer to an array in C stepping one row at a time |
| Flash memory holding a string table | Embedded C programming command table using an array of pointers |
Summary and Key Takeaways
Remember
int *a[3]is an array of three pointers.int (*a)[4]is one pointer to a row of four ints.- Both can be subscripted as
a[i][j], but memory use and stepping differ. - Array of pointers: ragged data, string and dispatch tables, sorting by reference.
- Pointer to array: real 2-D data and function parameters.
- Use typedef and
constto keep both readable and safe.
Conclusion
Declarations in C are not hard once you read them from the name outward. With that skill, the pair of look-alike declarations stops being a puzzle and becomes a design choice between flexibility and compactness. In the final lecture of this chapter we pull the whole pointers chapter together with a recap, a quiz and interview questions.
FAQ: Array of Pointers vs Pointer to Array
What is the difference between int *a[3] and int (*a)[3]?
The first is an array of three pointers to int. The second is a single pointer to an array of three ints.
Can I use a[i][j] with both?
Yes. The syntax is the same, but the array of pointers does an extra pointer load while the pointer to array uses arithmetic alone.
Is an array of pointers the same as a 2-D array?
No. A 2-D array is one contiguous block. An array of pointers stores addresses and the rows can be anywhere and of any length.
How do I declare a read-only table of strings?
Use const char *const names[] = {…}. The strings and the table are both read-only, and both can live in flash.
Why use a typedef for a pointer to array?
It hides the parenthesised syntax, so function signatures read like row4_t *m instead of int (*m)[4].
Learn Embedded C the Free Way
One lecture to go in this chapter: a recap quiz and interview questions to test everything about pointers.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments