Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Array of Pointers in C
Chapter 8 · Pointers · Store many addresses in one array and use them to reach, share and reorder data without copying it
Topics covered in this free C tutorial
Welcome back to our free C programming course. In this lecture we study the array of pointers in C: an ordinary array whose every element is itself a pointer. It sounds like a small trick, yet it sits behind argv in main(), behind menu and message tables, and behind most lookup tables you will meet in embedded C programming. By the end of this free C tutorial you will be able to declare one, fill it, read through it, write through it and even sort data using nothing but pointer swaps.
What You Will Learn
- How to declare an array of pointers in C and read the declaration correctly
- What each element stores, and how that differs from an array of plain values
- How to make the pointers aim at separate variables
- How to make the pointers aim at the elements of another array
- How to reorder data virtually by sorting the pointers instead of the data
- Where this pattern shows up in C programming for embedded systems
- The mistakes beginners make, and how to avoid them
Prerequisites
Before you start
You should already be comfortable with the address-of operator &, the dereference operator *, declaring a pointer variable, and basic one-dimensional arrays. If you followed the earlier lectures of this free embedded C course, including the previous lecture that compared an array of pointers with a pointer to an array, you are ready.
What Is an Array of Pointers in C?
An ordinary array such as int data[4] holds four integers sitting next to each other in memory. An array of pointers in C holds four addresses instead. Each slot says “the value you want is over there”, and the data itself can live anywhere: in separate variables, inside another array, in a structure, or in memory you obtained at run time.
Reading the declaration
Consider this declaration:
int *reading[3];
The square brackets bind more tightly than the star, so read it from the name outward: reading is an array of 3 elements, and each element is a int *, a pointer to int. The type of one element is therefore “pointer to int”, and the array as a whole holds three of them.
| Declaration | Meaning | Memory it owns |
|---|---|---|
int *p; | one pointer to int | one address |
int *p[3]; | array of 3 pointers to int | three addresses |
int (*p)[3]; | one pointer to an array of 3 ints | one address |
int p[3]; | array of 3 ints | three integers |
Watch the brackets. int *p[3] and int (*p)[3] look almost identical but are different types. The first owns three pointers. The second owns a single pointer that happens to aim at a whole block of three integers.
Array of Pointers in C: How It Looks in Memory
The array of pointers is a small table of addresses. The actual values stay where they were declared. Reading *reading[i] follows the stored address and fetches the value there.
reading[3] pointing at three separate variables
Because every element is a pointer, every element has the same size no matter what it points to. On a 64-bit Linux PC that is 8 bytes. On a typical 32-bit ARM Cortex-M microcontroller it is 4 bytes. This is why sizeof of the array of pointers tells you about the table, not about the data.
| Expression | What it gives you |
|---|---|
reading | address of the first element of the pointer table |
reading[i] | the i-th stored address (a int *) |
*reading[i] | the int value found at that stored address |
&reading[i] | where the i-th pointer itself lives (a int **) |
sizeof(reading) | number of elements times pointer size |
Example 1: Array of Pointers to Separate Variables
The first program gathers three unrelated sensor values behind one name. We fill the table with addresses, read through it in a loop, and then write through one element to prove the pointers aim at the real variables, not at copies.
#include <stdio.h>
int main(void)
{
int temperature = 31;
int humidity = 64;
int pressure = 1013;
int *reading[3]; /* array of 3 pointers to int */
reading[0] = &temperature;
reading[1] = &humidity;
reading[2] = &pressure;
for (int i = 0; i < 3; i++) {
printf("reading[%d] -> value %d\n", i, *reading[i]);
}
*reading[1] = 70; /* write through the second pointer */
printf("humidity is now %d\n", humidity);
printf("sizeof(reading) = %zu bytes\n", sizeof(reading));
printf("sizeof(reading[0]) = %zu bytes\n", sizeof(reading[0]));
return 0;
}
Build and run it with strict warnings switched on, which is a good habit for any C programming for embedded systems project:
gcc -std=c11 -Wall -Wextra -pedantic -o ptrarr_sensors ep_ptrarr_sensors.c
./ptrarr_sensors
Expected output on a 64-bit Linux machine:
reading[0] -> value 31
reading[1] -> value 64
reading[2] -> value 1013
humidity is now 70
sizeof(reading) = 24 bytes
sizeof(reading[0]) = 8 bytes
Three things deserve attention. First, the loop uses *reading[i], so the compiler reads the address at slot i and then the int at that address. Second, the statement *reading[1] = 70; changed humidity itself, which the following printf confirms. Third, sizeof(reading) is 24 bytes because three 8-byte pointers were stored. On a 32-bit microcontroller the same program would print 12 and 4.
Example 2: Array of Pointers Into Another Array
A pointer table can also aim at the elements of a second array. This is common when you want a second, differently ordered, or partial view of the same buffer. Here an ADC result buffer is exposed as four named channels.
ch[] aims at the elements of adc[]
| Index | adc[i] (data) | ch[i] (stored address) | *ch[i] |
|---|---|---|---|
| 0 | 512 | address of adc[0] | 512 |
| 1 | 1023 | address of adc[1] | 1023 |
| 2 | 256 | address of adc[2] | 256 |
| 3 | 768 | address of adc[3] | 768 |
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint16_t adc[4] = {512, 1023, 256, 768};
uint16_t *ch[4];
for (int i = 0; i < 4; i++) {
ch[i] = &adc[i];
}
for (int i = 0; i < 4; i++) {
printf("ch[%d] == &adc[%d] ? %s *ch[%d] = %u\n",
i, i, (ch[i] == &adc[i]) ? "yes" : "no",
i, (unsigned)*ch[i]);
}
/* distance in bytes between two stored addresses */
printf("ch[1] - ch[0] = %td bytes\n",
(char *)ch[1] - (char *)ch[0]);
/* %p expects a void * argument */
printf("ch[0] holds address %p\n", (void *)ch[0]);
return 0;
}
gcc -std=c11 -Wall -Wextra -pedantic -o ptrarr_adc ep_ptrarr_adc.c
./ptrarr_adc
Sample output (the hexadecimal address will be different on your machine, because modern systems randomize stack addresses on every run):
ch[0] == &adc[0] ? yes *ch[0] = 512
ch[1] == &adc[1] ? yes *ch[1] = 1023
ch[2] == &adc[2] ? yes *ch[2] = 256
ch[3] == &adc[3] ? yes *ch[3] = 768
ch[1] - ch[0] = 2 bytes
ch[0] holds address 0x7fffe097a908
The comparison ch[i] == &adc[i] proves that each slot stores exactly the address of the matching element. The two stored addresses are only 2 bytes apart because the elements are uint16_t; pointer values move by the size of what they point to. We cast to char * before subtracting so that the result is counted in bytes. Finally, %p is defined for void * arguments, so the pointer is cast before printing.
| Two ways to fill the table | Why it works |
|---|---|
ch[i] = &adc[i]; | take the address of element i explicitly |
ch[i] = adc + i; | pointer arithmetic from the array’s first element |
Example 3: Sorting With an Array of Pointers in C
Here is the most useful trick in this lecture. Suppose you must display lap times from best to worst but must also keep the original recording order. Instead of moving the data, sort the pointers. The data array never changes; only the order of the addresses does.
Before and after sorting rank[]
#include <stdio.h>
int main(void)
{
int lap_ms[5] = {930, 812, 1004, 799, 860};
int *rank[5];
int i, j;
for (i = 0; i < 5; i++) {
rank[i] = &lap_ms[i];
}
/* bubble sort the POINTERS by the value each one points at */
for (i = 0; i < 4; i++) {
for (j = 0; j < 4 - i; j++) {
if (*rank[j] > *rank[j + 1]) {
int *tmp = rank[j];
rank[j] = rank[j + 1];
rank[j + 1] = tmp;
}
}
}
printf("original data : ");
for (i = 0; i < 5; i++) {
printf("%d ", lap_ms[i]);
}
printf("\nsorted view : ");
for (i = 0; i < 5; i++) {
printf("%d ", *rank[i]);
}
printf("\n");
return 0;
}
gcc -std=c11 -Wall -Wextra -pedantic -o ptrarr_rank ep_ptrarr_rank.c
./ptrarr_rank
original data : 930 812 1004 799 860
sorted view : 799 812 860 930 1004
The comparison *rank[j] > *rank[j + 1] looks at the values, but the swap exchanges only the pointers. Swapping an 8-byte (or 4-byte) pointer is cheap even when the thing pointed at is a large structure, and the technique also works when the data sits in read-only flash and cannot be moved at all.
Where an Array of Pointers Appears in Embedded C
- Command-line arguments:
char *argv[]inmain()is an array of pointers to strings, normally ended by a null pointer. - String tables: a list such as
const char *state_name[] = {"IDLE", "RUN", "FAULT"};lets you print a name from a numeric state without a longswitch. - Channel and register maps: a table of pointers to sampling buffers or to peripheral result variables, so one loop can service all channels.
- Virtual ordering: sorted or filtered views over a buffer you must not disturb, as in Example 3.
- Jagged data: rows of different lengths, which the next lecture explores together with two-dimensional arrays.
Common Mistakes With an Array of Pointers in C
| Mistake | What goes wrong | Fix |
|---|---|---|
| Using a slot before assigning it | the pointer holds garbage, dereferencing it is undefined behavior | fill every slot first, or initialize the table to NULL |
| Aiming a slot at a local variable that dies | after the function returns the stored address is dangling | only keep pointers to objects that outlive the table |
Mixing up int *p[3] and int (*p)[3] | wrong type, confusing warnings | read the declaration from the name outward |
Expecting sizeof to measure the data | you only get the size of the pointer table | keep the element count in a separate constant |
Looping to <= size | reads one slot past the table | use < size and a named constant |
Troubleshooting tip. If your program crashes only when it touches a particular slot, print the pointer with %p before dereferencing it. A value such as (nil) or an obviously random number tells you the slot was never filled.
Best Practices, Performance and Safety
- Initialize pointer tables at declaration, for example
int *ch[4] = {0};, so unused slots are null and checkable. - Prefer
constwhere the data must not change:const int *for read-only views. - Each access through the table costs one extra memory load. In a hot loop, copy the pointer into a local variable once and reuse it.
- Check a slot against
NULLbefore dereferencing whenever the table may be partially filled. A null-terminated table, likeargv, lets a loop stop without knowing the length. - Remember that the table does not own the data. Do not free or reuse an object while a table still points at it.
Summary and Key Takeaways
Key takeaways
- An array of pointers in C stores addresses, one per element, all of the same size.
int *p[N]is N pointers;int (*p)[N]is a single pointer to an array.p[i]is an address and*p[i]is the value at that address.- The pointers may aim at separate variables or at elements of another array.
- Sorting the pointers gives a reordered view without moving the data.
- Always initialize every slot and keep the pointed-to objects alive.
Conclusion
The array of pointers in C is a simple idea with a wide reach. It lets one name stand for many places, it keeps large data where it is, and it turns clumsy chains of conditions into small tables. Master it now, because the next lecture in this free embedded C course applies it to two-dimensional arrays, and later chapters build strings, function-pointer tables and dynamic data structures on exactly the same foundation. Make sure you can explain the difference between int *p[3] and int (*p)[3] out loud; interviewers love that question.
Frequently Asked Questions
What is an array of pointers in C?
It is an array where each element is a pointer. It stores addresses rather than values, so a declaration like int *p[4] creates four separate pointer variables that can each aim at a different int.
How do I declare an array of pointers to int?
Write the element type, a star, the name, then the size in brackets: int *p[5];. Read it as “p is an array of 5 pointers to int”.
What is the difference between an array of pointers and a pointer to an array?
int *p[3] is three pointers. int (*p)[3] is one pointer that points at an entire array of three ints. The parentheses change which operator binds first.
How big is an array of pointers?
The element count multiplied by the size of one pointer. That is 8 bytes per element on a 64-bit PC and usually 4 bytes on a 32-bit Cortex-M microcontroller, regardless of the type being pointed at.
Can an array of pointers point to elements of another array?
Yes. Assign p[i] = &arr[i]; or p[i] = arr + i;. Each slot then holds the address of the matching element, and *p[i] reads the element itself.
Why sort an array of pointers instead of the data?
Moving pointers is cheap even when the data items are large structures, the original order is preserved, and it works on data that cannot be moved, such as constants stored in flash.
Is argv an array of pointers?
Yes. In int main(int argc, char *argv[]), argv is an array of pointers to char, each pointing at one command-line argument string, followed by a null pointer.
Do I need to free an array of pointers?
Only the memory that was obtained dynamically needs freeing. A plain array of pointers declared in a function is released automatically when the function returns, and it does not own the objects it points to.
Next up: row pointers for 2D arrays
You now know how one pointer table can stand for many places. In the next lecture of this free C programming course we aim the same idea at the rows of a two-dimensional array.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

1 Comment