Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Pointer to Pointer in C
Free C programming course: declaration, double dereference and the memory picture, with a tested program.
Pointer to pointer in C is the idea that a pointer can itself be pointed to. It sounds abstract, but it is the foundation of out parameters, linked lists and arrays of strings in embedded C programming. This lecture of our free C programming course draws the memory picture first, then proves it with a tested program, so you never have to guess what **ppl means again.
What You Will Learn
- How to declare a pointer to pointer and what each asterisk means
- How
*and&change the type of an expression one level at a time - The difference between changing data and re-aiming a pointer
- Why an
int **is not the same as a two-dimensional array - Where double pointers appear in embedded C programming
Prerequisites
- Pointer declaration, address-of and dereference from the first lectures of this chapter
- Pointer arithmetic, increment and comparison from the earlier lectures
- Comfort reading C types from right to left
What Is a Pointer to Pointer in C?
A pointer is a variable that holds an address. Because it is a variable, it lives in memory and therefore has an address of its own. A pointer to pointer, also called a double pointer, is a variable that holds that second address. You declare it with two asterisks:
data_type **name;
The two stars tell the compiler that name holds the address of a pointer, and that pointer in turn holds the address of a data_type object. In practice you will almost never need more than two levels, so this lecture stays at two.
| Variable | Type | Address | Value it holds | Meaning |
|---|---|---|---|---|
level | int | 0x5000 | 7 | the data |
pl | int * | 0x5010 | 0x5000 | address of level |
ppl | int ** | 0x5020 | 0x5010 | address of pl |
Reading Every Expression in the Chain
The most reliable way to understand a double pointer is to unwrap it one star at a time. Each dereference removes one level of indirection and lands you on the next object in the chain.
| Expression | Type | What it means |
|---|---|---|
level | int | the integer itself |
&level | int * | address of the integer |
pl | int * | holds the address of level |
*pl | int | the integer, reached through pl |
&pl | int ** | address of the pointer pl |
ppl | int ** | holds the address of pl |
*ppl | int * | the pointer pl itself |
**ppl | int | the integer, reached through two hops |
&ppl | int *** | address of the double pointer |
Notice the pattern: every * you add to an expression strips one star from its type, and every & adds one. This habit of tracking the type as you read is the single most useful debugging skill for pointer code.
Hands-On Program: Double Pointer in Action
Save this as ptr_ptr.c. It proves the relationships with equality tests rather than printing raw addresses, so the output is the same on every machine. It then writes through the double pointer and finally re-aims the inner pointer.
#include <stdio.h>
int main(void)
{
int level = 7, other = 99;
int *pl = &level;
int **ppl = &pl;
printf("level=%d *pl=%d **ppl=%d\n", level, *pl, **ppl);
printf("*ppl == pl : %d\n", *ppl == pl);
printf("*ppl == &level : %d\n", *ppl == &level);
printf("ppl == &pl : %d\n", ppl == &pl);
**ppl = 15;
printf("after **ppl = 15 : level=%d\n", level);
*ppl = &other;
printf("after *ppl = &other: *pl=%d level=%d\n", *pl, level);
printf("sizeof pl == sizeof ppl : %d\n", sizeof pl == sizeof ppl);
return 0;
}
gcc -std=c11 -Wall -Wextra -o ptr_ptr ptr_ptr.c
./ptr_ptr
Expected output:
level=7 *pl=7 **ppl=7
*ppl == pl : 1
*ppl == &level : 1
ppl == &pl : 1
after **ppl = 15 : level=15
after *ppl = &other: *pl=99 level=15
sizeof pl == sizeof ppl : 1
Understanding each result
- The first line prints 7 three times:
level,*pland**pplare all the same integer reached by zero, one and two hops. - The three equality tests all print 1, confirming
*pplis the pointerpl,*pplequals&level, andpplis&pl. **ppl = 15writes through two levels, andlevelreally becomes 15.*ppl = &otherchanges the inner pointer. Now*plreads 99 fromother, whilelevelstays 15 because nothing touched it.- The size test prints 1 because on a normal platform every object pointer has the same size, no matter how many stars its type has.
Two Different Things You Can Change
This lecture hides an important distinction. Through a double pointer you can change the data (**ppl = 15) or you can change where the inner pointer aims (*ppl = &other). The second ability is the reason double pointers exist. A function that receives only pl can change the data but can never redirect the caller’s pointer. A function that receives ppl can do both.
Printing real addresses
If you want to see actual address values, print them with the %p conversion and cast to void *, for example printf("%p\n", (void *)ppl);. Avoid %u for addresses, because an address does not fit in an unsigned int on 64-bit systems.
Double Pointers and Two-Dimensional Arrays
A very common misunderstanding is to treat a two-dimensional array as an int **. They are different types. A real int grid[3][4] is one contiguous block, and passing it to a function expecting int ** compiles with a warning at best and crashes at worst. A pointer to a row array, such as int (*row)[4], is the correct type for that job. An int ** fits a different layout: an array of separate row pointers, each possibly allocated on its own.
Where Double Pointers Show Up in Embedded C
- Driver init functions that hand back a newly created handle through an out parameter.
- Linked list code that needs to change the head pointer.
- Arrays of strings such as command tables, where the element type is
const char *. - Command-line style arguments, the familiar
char **argv. - Memory pool allocators that return a block and update the caller’s free-list pointer.
Common Mistakes with Pointer to Pointer in C
- Dereferencing a double pointer before it is initialised.
- Passing a two-dimensional array where an
int **is expected. - Losing track of the type and assigning
&pplto anint *. - Printing addresses with
%dor%uinstead of%p. - Using a double pointer when a single pointer is enough.
Best Practices for Embedded C Programming
- When you read pointer to pointer in C code, write the type of each expression next to it.
- Initialise every pointer at the point of declaration, with an address or
NULL. - Name double pointers clearly, for example
pp_headorout_handle. - Stay at two levels of indirection unless there is a strong reason.
- Check for
NULLat each level before dereferencing.
Key Takeaways
- A pointer to pointer in C is declared with two asterisks and holds the address of a pointer.
- Each
*removes one level of indirection and each&adds one. **pplreaches the original data,*pplreaches the inner pointer.- Through a double pointer you can change either the data or the inner pointer’s target.
- An
int **is not anint [][]array.
Conclusion
A pointer to pointer in C is only a pointer whose target happens to be another pointer. Draw the three boxes, label each with its type, and every expression becomes a short walk from one box to the next.
In the final lecture of this section we put double pointers to work inside functions, finish the chapter with a quiz and collect the interview questions you are most likely to be asked.
Frequently Asked Questions
What is a pointer to pointer in C?
It is a variable that stores the address of another pointer. It is declared with two asterisks, for example int **ppl.
What does **ppl mean?
It dereferences ppl to get the inner pointer, then dereferences that to reach the final data.
What is the difference between *ppl and **ppl?
*ppl is the inner pointer itself, while **ppl is the data the inner pointer points to.
How many levels of pointers can I have?
The language allows many, but in practice only pointer to pointer is common. More levels quickly become hard to read.
Is a two-dimensional array the same as an int **?
No. A true two-dimensional array is one contiguous block with a different type, so the two are not interchangeable.
How should I print a pointer?
Use the %p conversion with the pointer cast to void pointer. Do not use %d or %u for addresses.
Why do embedded programmers use double pointers?
To let a function return a handle or modify a caller’s pointer, for example when initialising a driver or inserting at the head of a linked list.
Learn Embedded C Programming Free
Finish the pointer section of our free C programming course with double pointers in functions, a quiz and interview questions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments