What is Pointer to an Array- Free C Programming Course

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 Embedded C Course · Pointers · Lecture 21

Pointer to an Array

Understand c programming for embedded systems at the type level: declare int (*ptr)[5], see why the parentheses matter and watch ptr++ jump a whole array. A hands-on free c tutorial with tested output.

20
Bytes moved by whole++
4
Declaration shapes compared
1
Runnable demo

What You Will Learn

  • What a pointer to an array is, and how to declare one with int (*ptr)[5]
  • Why the parentheses are mandatory and what int *ptr[5] means instead
  • How a pointer to one element and a pointer to the whole array differ in type, size of target and behaviour under ++
  • Why you must write &samples, not samples, when you initialise such a pointer
  • How to read any C declaration from the inside out

Prerequisites

  • The previous lecture on the four array access forms
  • Pointer arithmetic and the idea that p + 1 moves by the size of the pointed-to type
  • GCC on Linux, or an ARM GCC toolchain, to try the demo

Pointer to an Array in C Programming for Embedded Systems

Most pointers you write point at a single object. In c programming for embedded systems there is a second kind that shows up the moment you handle fixed-size frames: a pointer whose target is an entire array. Think of an 8-byte CAN payload, a 16-sample audio block or a row of an LED matrix. If a function receives a pointer to the whole block, the compiler knows the size and can reject a wrong-sized buffer at build time instead of letting it corrupt memory at run time.

Good c programming for embedded systems is mostly about knowing exactly what your types mean, because there is no operating system to catch a mistake. This lecture of our free embedded c course builds the idea slowly: declaration syntax first, then the difference from a normal pointer, then a program that proves it.

Declaring a Pointer to an Array

The declaration looks like this:

int (*whole)[5];

Read it from the name outwards. whole is a pointer (the * sits next to the name inside the parentheses), and what it points to is an array of 5 (the [5] on the right), and the array elements are int. So the type of whole is pointer to an array of 5 integers.

Reading int (*whole)[5] from the inside out
whole step 1: the name being declared
(*whole) step 2: it is a pointer, because * binds first inside the parentheses
(*whole)[5] step 3: what it points to is an array of 5
int (*whole)[5] step 4: each element of that array is an int

Why the Parentheses Matter

In a declarator the subscript [] binds tighter than the star. Without parentheses the meaning flips completely, as this table shows. Mixing these two up is one of the most common stumbling blocks, so compare them line by line.

DeclarationMeaningSize on 64-bit Linux
int *pPointer to one int8 bytes
int (*q)[5]Pointer to an array of 5 int8 bytes (it is only a pointer)
int *r[5]Array of 5 pointers to int40 bytes (five pointers)
int *(*s)[5]Pointer to an array of 5 pointers to int8 bytes

Where each shape is used

An array of pointers (r) is the natural way to keep a table of strings or a list of buffers. A pointer to an array (q) is the natural way to hand one fixed-size block to a function, or to walk the rows of a two-dimensional array.

Pointer to Element Versus Pointer to Array

Both kinds of pointer can hold the same numeric address, because the first byte of an array is also the first byte of its first element. They are still different types, and the difference shows up as soon as you do arithmetic or dereference them.

Same start address, different reach
elem 35679 elem covers one int
whole 35679 whole covers all five ints
Propertyint *elemint (*whole)[5]
Points toOne intA block of 5 int
Size of the target4 bytes20 bytes
Effect of ++Moves 4 bytesMoves 20 bytes
Result of *The int valueThe whole array (which decays to an address)
Natural partner1-D buffer walkingFixed-size blocks and 2-D arrays

Demo: How Far Does ++ Move?

The only way to really believe the table is to run it. The program creates both pointers over the same array, prints their sizes, increments each one and reports how far it travelled. An array of five pointers is included so you can see the 40-byte size from the declaration table.

#include <stdio.h>

int main(void)
{
    int samples[5] = {3, 5, 6, 7, 9};
    int *elem = samples;
    int (*whole)[5] = &samples;
    int *slots[5];

    printf("sizeof(elem)  = %zu\n", sizeof elem);
    printf("sizeof(whole) = %zu\n", sizeof whole);
    printf("sizeof(slots) = %zu\n", sizeof slots);

    elem++;
    whole++;
    printf("elem  moved %td bytes\n", (char *)elem  - (char *)samples);
    printf("whole moved %td bytes\n", (char *)whole - (char *)samples);
    return 0;
}
gcc -std=c11 -Wall -Wextra -o e21 e21.c
./e21

Expected output on a 64-bit Linux machine:

sizeof(elem)  = 8
sizeof(whole) = 8
sizeof(slots) = 40
elem  moved 4 bytes
whole moved 20 bytes

Both pointers are 8 bytes, because every data pointer on this platform is the same size. The array of pointers is 40 bytes because it really stores five of them. The decisive lines are the last two: the element pointer stepped over one int, while the array pointer stepped over all five, landing exactly one past the end of samples. That jump of 5 * sizeof(int) is the single most important behaviour of this pointer type.

What ++ does to each pointer
before 35679 elem and whole both start at the first byte
elem++ 3elem679 one int forward (+4)
whole++ 35679whole one whole array forward (+20)

Use &samples, Not samples

A very common slip is to initialise the array pointer with the bare array name. Many older textbooks write exactly that, but it has the wrong type. In an expression samples becomes int * (a pointer to the first element), while &samples has type int (*)[5], which is what whole needs. The two expressions hold the same address and different types.

int samples[5] = {3, 5, 6, 7, 9};
int (*whole)[5] = samples;      /* wrong type on the right-hand side */

GCC 13 reports it like this:

warning: initialization of 'int (*)[5]' from incompatible pointer type 'int *' [-Wincompatible-pointer-types]

GCC 14 and later treat that diagnostic as an error by default, so code that merely warned in the past can suddenly fail to build after a toolchain upgrade. Fix it by taking the address of the whole array: int (*whole)[5] = &samples;.

Do not silence the warning with a cast

A cast such as (int (*)[5])samples compiles, but it hides a real type mismatch. Use &samples and let the compiler keep protecting you.

Common Mistakes in C Programming for Embedded Systems

MistakeSymptomFix
Dropping the parentheses: int *whole[5]You declared five pointers, not one pointer to an arrayWrite int (*whole)[5]
Initialising with samplesIncompatible pointer type warning, or an error on GCC 14+Initialise with &samples
Expecting whole++ to move one elementYou skip the whole array and land past the endStep by one element with an element pointer instead
Using a pointer to the wrong array lengthCompiler warns when you pass &buf8 where a 16-byte block is expectedMatch the length in the declaration or use a typedef
Assuming a pointer to array is larger than a normal pointerConfusion about sizeofRemember it is just an address; the target size lives in the type

Best Practices, Performance and Security

Teams doing c programming for embedded systems usually settle this with a typedef. A typedef makes the declaration readable and keeps the size in one place. With it, nobody has to decode parentheses during code review:

typedef int sample_block_t[5];

sample_block_t block = {3, 5, 6, 7, 9};
sample_block_t *whole = &block;     /* pointer to the whole block */
  • Name block types with a suffix such as _t and define the length once with a macro or enum constant.
  • Prefer a pointer to an array over (uint8_t *buf, size_t len) when the size is fixed by a protocol, because the compiler checks it for free.
  • Keep -Wall -Wextra on and treat incompatible-pointer warnings as errors in your build, as GCC 14 now does.
  • Do not return a pointer to an array that lives on the stack of the function that returns.

On performance, a pointer to an array has no run-time overhead: the scaled increment is a compile-time constant added to an address. On the security side, the fixed size in the type is a modest safety net because it stops a caller from passing a shorter buffer by accident. It is not a bounds check, though; an index past the end is still undefined behaviour.

Real-World C Programming for Embedded Systems

  • CAN and SPI frames: a function takes uint8_t (*frame)[8] and is guaranteed an 8-byte payload.
  • Audio and sensor blocks: a driver passes a pointer to a 16-sample block, and the compiler enforces the block length.
  • Two-dimensional tables: the rows of a keypad or LED matrix are walked with a pointer to one row, which the next lectures cover in detail.

Key Takeaways and Conclusion

  • int (*p)[5] is a pointer to an array of 5 integers; int *p[5] is an array of 5 pointers.
  • Incrementing a pointer to an array moves by the size of the whole array, here 20 bytes with a 4-byte int.
  • Initialise it with &array, never with the bare array name.
  • A typedef keeps the declaration readable and the length in one place.

You have now seen the declaration syntax, the type difference and the stride difference with real output. That is the core of this topic for anyone learning c programming for embedded systems from a free c programming course. In the next lecture we dereference this pointer and discover what comes back, which finally explains how two-dimensional arrays decay into pointers to rows.

Frequently Asked Questions

What is a pointer to an array in C?

It is a pointer whose target type is an entire array, such as int (*p)[5]. It stores an address like any pointer but moves by the whole array size when incremented.

What is the difference between int *p[5] and int (*p)[5]?

The first is an array of five pointers to int. The second is one pointer to an array of five ints. The parentheses change which operator binds first.

Why does whole++ move 20 bytes?

Pointer arithmetic is scaled by the size of the pointed-to type. The target is an array of five 4-byte ints, so one step is 20 bytes.

Why should I write &samples instead of samples?

The bare name converts to int *, which is not compatible with int (*)[5]. Taking the address of the array gives the correct type.

Do pointers to arrays cost more memory than normal pointers?

No. Every data pointer on the same platform has the same size. The extra information is part of the type and exists only at compile time.

When would I use this in firmware?

Use it when a block has a fixed size dictated by hardware or a protocol, such as an 8-byte CAN payload, so the compiler can verify the size.

Does a typedef change anything at run time?

No. A typedef only gives a name to the type. It improves readability and keeps the length in a single place.

Continue the Free Embedded C Course

Next we dereference a pointer to an array and see exactly what comes back. Keep going with this free c programming course.

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 *