What are Array of Pointers in C- Best C Programming Course

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

array of pointers in C free C programming course free C tutorial C programming for embedded systems embedded C programming pointer array example

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.

DeclarationMeaningMemory it owns
int *p;one pointer to intone address
int *p[3];array of 3 pointers to intthree addresses
int (*p)[3];one pointer to an array of 3 intsone address
int p[3];array of 3 intsthree 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

reading[0]
holds the address of
temperature = 31
reading[1]
holds the address of
humidity = 64
reading[2]
holds the address of
pressure = 1013

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.

ExpressionWhat it gives you
readingaddress 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[]

Indexadc[i] (data)ch[i] (stored address)*ch[i]
0512address of adc[0]512
11023address of adc[1]1023
2256address of adc[2]256
3768address 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 tableWhy 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[]

lap_ms[] fixed: 930, 812, 1004, 799, 860
rank[] before: 930, 812, 1004, 799, 860
sort the pointers
rank[] after: 799, 812, 860, 930, 1004
#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[] in main() 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 long switch.
  • 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

MistakeWhat goes wrongFix
Using a slot before assigning itthe pointer holds garbage, dereferencing it is undefined behaviorfill every slot first, or initialize the table to NULL
Aiming a slot at a local variable that diesafter the function returns the stored address is danglingonly keep pointers to objects that outlive the table
Mixing up int *p[3] and int (*p)[3]wrong type, confusing warningsread the declaration from the name outward
Expecting sizeof to measure the datayou only get the size of the pointer tablekeep the element count in a separate constant
Looping to <= sizereads one slot past the tableuse < 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 const where 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 NULL before dereferencing whenever the table may be partially filled. A null-terminated table, like argv, 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

Leave a Reply

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