What are Array of Pointers Explained- Best C Language Course 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 71

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.

2
Look-alike declarations
3
Reading rules
1
Tested program

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] and int (*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.

DeclarationRead asMeaning
int *row_ptrs[3]row_ptrs is an array of 3 pointers to intThree separate pointers, 24 bytes on a 64-bit system
int (*grid_ptr)[4]grid_ptr is a pointer to an array of 4 intsOne 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 pointerShows how far the rule scales

Memory Picture: Pointer Array vs Pointer to Array

int *row_ptrs[3]: an array of three pointers
row_ptrs[0]holds address of grid[0][0]
row_ptrs[1]holds address of grid[1][0]
row_ptrs[2]holds address of grid[2][0]
Cost3 pointers of extra storagerows may be different lengths
int (*grid_ptr)[4]: one pointer to a row of four ints
grid_ptrholds address of the first row
grid_ptr + 1next row, 16 bytes further
grid_ptr[2][3]row 2, element 3
Costone pointerall rows must be the same length

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

ObservationArray of pointersPointer to array
sizeof of the variable24 bytes (3 pointers)8 bytes (1 pointer)
sizeof of what it points at4 bytes (one int)16 bytes (a row of four)
Step of + 18 bytes (next pointer in the array)16 bytes (next row of data)
Values printed with [i][j]Same table valuesSame 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 cmds array above holds strings of different lengths with no wasted padding. Declare it as const 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 write m[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

SituationBetter choiceReason
List of strings in flashArray of pointersDifferent lengths, no padding
Fixed-width numeric tablePointer to array, or a plain 2-D arrayNo extra pointer storage, simple arithmetic
Rows of varying lengthArray of pointersEach row can be its own size
Passing a table to a functionPointer to arrayCompiler knows the row size
Sorting large recordsArray of pointersMove pointers, not data

Common Mistakes and Troubleshooting

MistakeEffectFix
Dropping the parentheses in int (*p)[4]Declares an array of pointers insteadRe-read the declaration with the right-left rule
Passing a 2-D array to int **Type mismatch, crash at run timeUse int (*)[cols]
Forgetting to initialise the pointer arrayWild pointersFill or NULL-initialise every entry
Pointing array entries at local buffersDangling pointers after returnUse static or caller-owned storage
Modifying a string literal through a table entryUndefined behaviour, often a faultDeclare entries as const char *
An array of pointers is not a 2-D array. It has no guarantee that rows sit next to each other, and 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 *const for 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 ideaALT text
Three pointer slots each pointing to a rowArray of pointers in C pointing at rows of an integer table
A single pointer stepping across rows of a tablePointer to an array in C stepping one row at a time
Flash memory holding a string tableEmbedded 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 const to 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.

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 *