Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Double Pointers in Embedded C
Free embedded C course: out parameters, pointer swaps, string tables, plus a quiz and interview questions.
A double pointer in C is the answer to one practical question: how does a function change a pointer that belongs to its caller? In this final lecture of the pointer-arithmetic section of our free C programming course you will see four working double pointer patterns used daily in embedded C programming, then test yourself with a chapter quiz and a set of interview questions. It is the perfect finish to this free C tutorial on pointers.
What You Will Learn
- Why passing a pointer by value cannot change the caller’s pointer
- Four real patterns: cursor skip, out parameter, pointer swap and string table
- How to document ownership of memory returned through an out parameter
- How to test yourself with verified quiz questions
- The pointer questions most often asked in embedded C interviews
Prerequisites
- Pointer to pointer basics from the previous lecture
- Functions with parameters and return values
- Dynamic memory allocation with
callocandfree, at a basic level
Why a Function Needs a Double Pointer in C
C passes every argument by value. When you call a function with a pointer, the function receives a copy of that pointer. It can follow the copy to change the data, but if it changes the copy itself, the caller never notices. To let a function change the pointer in the caller, you pass the address of that pointer, which is a double pointer.
Hands-On Program: Four Double Pointer Patterns
Save this as ptr_out.c. It shows the broken version next to the working one for the cursor skip, an out parameter that returns an allocated buffer, a pointer swap, and a walk over an array of strings.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
static void skip_spaces_wrong(const char *cursor)
{
while (*cursor == ' ')
cursor++;
}
static void skip_spaces(const char **cursor)
{
while (**cursor == ' ')
(*cursor)++;
}
static int make_buffer(uint8_t **out, size_t n)
{
*out = calloc(n, 1);
return *out ? 0 : -1;
}
static void swap_ptrs(int **a, int **b)
{
int *t = *a; *a = *b; *b = t;
}
int main(void)
{
const char *line = " AT+RST";
const char *c1 = line;
skip_spaces_wrong(c1);
printf("by value : starts with '%c'\n", *c1);
const char *c2 = line;
skip_spaces(&c2);
printf("by address: starts with '%c'\n", *c2);
uint8_t *buf = NULL;
if (make_buffer(&buf, 8) == 0) {
buf[0] = 0xA5;
printf("buf[0]=0x%02X buf[7]=0x%02X\n", buf[0], buf[7]);
free(buf);
}
int hi = 90, lo = 10;
int *pa = &hi, *pb = &lo;
swap_ptrs(&pa, &pb);
printf("pa->%d pb->%d (hi=%d lo=%d untouched)\n", *pa, *pb, hi, lo);
const char *cmds[] = {"START", "STOP", "RESET"};
const char **walk = cmds;
for (size_t i = 0; i < 3; i++) {
printf("cmd %zu: %s (first char %c)\n", i, *walk, **walk);
walk++;
}
return 0;
}
gcc -std=c11 -Wall -Wextra -o ptr_out ptr_out.c
./ptr_out
Expected output:
by value : starts with ' '
by address: starts with 'A'
buf[0]=0xA5 buf[7]=0x00
pa->10 pb->90 (hi=90 lo=10 untouched)
cmd 0: START (first char S)
cmd 1: STOP (first char S)
cmd 2: RESET (first char R)
Pattern 1: advancing the caller’s cursor
skip_spaces_wrong receives a copy of the cursor and moves the copy. When it returns, the caller’s c1 still points at the first space, which is why the first result line shows a blank. skip_spaces takes a const char **, so (*cursor)++ moves the caller’s own pointer and the second line prints A. This is exactly how tokenisers and command parsers consume input.
Pattern 2: the out parameter
make_buffer returns a status code and hands the new buffer back through uint8_t **out. Returning the status separately from the result is a very common embedded style because the status can carry an error code. After the call, buf[0] is 0xA5 and buf[7] is 0x00, because calloc zeroes the memory.
Pattern 3: swapping two pointers
swap_ptrs exchanges what two pointers point at. Afterwards pa aims at the 10 and pb aims at the 90, yet the variables hi and lo still hold their original values. Only the pointers changed.
Pattern 4: arrays of strings
An array of const char * decays to a const char ** when you pass it or walk it. In the sample, *walk is the current string and **walk is its first character, which is why RESET prints an R. Command tables in firmware are built exactly this way.
Own your allocations
Whoever receives a buffer through an out parameter must know who frees it. Document the rule in a comment, free exactly once, and set the pointer to NULL afterwards. On bare-metal targets without a heap, the same pattern works with a static pool instead of calloc.
Security and Reliability Notes
- Validate the out pointer itself: if the caller passes
NULLwhere a double pointer is expected, dereferencing it crashes immediately. - Set
*outtoNULLon failure so callers can safely test the result. - Never return the address of a local variable through an out parameter; it dies when the function returns.
- Use
conston the inner type when the callee only reads through the pointer, as inconst char **.
Common Mistakes with Double Pointers in C
- Expecting a function that receives
char *to move the caller’s pointer. - Dereferencing the pointer-to-pointer parameter without checking it for
NULL. - Forgetting to document who frees the memory returned through the out parameter.
- Returning the address of a local variable through
*out. - Using
char **whereconst char **communicates the intent better.
Best Practices for Embedded C Programming
- Return a status code and deliver the result through an out parameter.
- Set
*outtoNULLon every failure path. - Use
constcorrectly on the inner type. - Keep ownership rules in a comment next to the function prototype.
- Prefer a struct and single pointers when a function needs many out values.
Key Takeaways
- C is pass by value, so changing a caller’s pointer needs its address.
- A pointer-to-pointer parameter is the standard way to write an out parameter for a pointer.
*ppis the caller’s pointer and**ppis the data it points to.- Arrays of strings are naturally walked with a
const char **. - Test your understanding with the quiz before moving on.
Chapter Quiz: Pointer Increment, Comparison and Double Pointers
Try each question before you open the answer. Everything here was verified by compiling and running the code.
Multiple choice
- An
intis 4 bytes on your target. Ifpholds the address 0x1000, what doesp + 3hold?
A) 0x1003 B) 0x100C C) 0x1012 D) 0x1004Show answer
B) 0x100C. Scaling multiplies 3 by 4 and adds 12 to the address.
- Which of these is a compile error?
A)p - q(same array) B)p + 2C)p + qD)p++Show answer
C)
p + q. Adding two pointers is not allowed. - What type does
p - qhave when both areint *into one array?
A)intB)size_tC)ptrdiff_tD)int *Show answer
C)
ptrdiff_t, printed with%td. x = *p++;parses as
A)(*p)++B)*(p++)C)++(*p)D)(*p++)with data incrementShow answer
B)
*(p++): postfix++binds tighter than*.- After
int v = 3; int *q = &v; int **pp = &q; **pp += 4;the value ofvis
A) 3 B) 4 C) 7 D) undefinedShow answer
C) 7. Two dereferences reach
v, and the compound assignment adds 4. - Which comparison is always well defined?
A)<between pointers to two different arrays B)==between two compatible pointers C)>=between unrelated pointers D) none of theseShow answer
B) Equality between compatible pointers is always well defined.
- What does
*pplevaluate to whenpplisint **?
A) anintB) anint *C) anint **D) an address of address of addressShow answer
B) an
int *, the inner pointer. - A function must change the caller’s
char *cursor. Its parameter type should be
A)char *B)char **C)charD)char ***Show answer
B)
char **, so the function can modify the caller’s pointer.
Predict the output
int a[] = {5, 6, 7, 8}; int *p = a; p += 2; printf("%d %td", *p, p - a);Show answer
Output:
7 2int b[] = {1, 2, 3}; int *r = b; int x = (*r)++; printf("%d %d %td", x, b[0], r - b);Show answer
Output:
1 2 0int c[] = {10, 20, 30}; int *s = c; int y = *++s; printf("%d %td", y, s - c);Show answer
Output:
20 1int d[] = {10, 20, 30}; int *t = d; int z = *t++; printf("%d %d", z, *t);Show answer
Output:
10 20int e[5] = {0}; int *m = e, *n = e + 4; printf("%td %d", n - m, m < n);Show answer
Output:
4 1
Embedded C Interview Questions on Pointers
- What is the difference between
*p++and(*p)++?Show answer
*p++reads the data and then moves the pointer.(*p)++reads the data and then increments the data, leaving the pointer where it is. - Why does
p + 1on anint *advance by 4 bytes?Show answer
Pointer arithmetic is scaled by the size of the pointed-to type, so the compiler adds
1 * sizeof(int). - What does subtracting two pointers give you?
Show answer
The number of elements between them as a
ptrdiff_t, valid only for pointers into the same array. - Can you compare two arbitrary pointers with
<?Show answer
No. Ordering is only defined inside one array object. Equality is safe for compatible pointers.
- What is a one-past-the-end pointer and what can you do with it?
Show answer
It is the address just after the last array element. You can form and compare it but never dereference it.
- What is a pointer to pointer and when do you need one?
Show answer
It stores the address of a pointer. You need it when a function must modify the caller’s pointer, for example an out parameter or a list head.
- Is
int **the same as a two-dimensional array?Show answer
No. A two-dimensional array is one contiguous block with type like
int [3][4]. Anint **points to an array of separate row pointers. - Why must you not write
*p++ = *p?Show answer
It modifies and reads
pwithout a sequence point, which is undefined behaviour. - How do you print a pointer value portably?
Show answer
Use
%pand cast the pointer tovoid *. - Why is arithmetic on
void *not portable?Show answer
voidhas no size, so standard C cannot scale the step. GCC treats it as 1 byte as an extension.
Conclusion
Double pointers turn from a puzzle into a tool as soon as you connect them to pass by value. The four patterns in this lecture cover most of what you will write in firmware: advancing a parser cursor, returning an allocated resource, swapping targets, and walking tables of strings.
You have now finished this block of the pointer chapter in our free embedded C course. Review the quiz answers, rewrite the programs from memory, and then continue with the next topic in the course.
Frequently Asked Questions
Why does my function not change the pointer I pass in?
C passes arguments by value, so the function gets a copy of the pointer. Pass the address of the pointer, using a pointer-to-pointer parameter, to change the original.
What is an out parameter in C?
A pointer parameter that the function writes its result through. For a pointer result, the parameter is a double pointer.
How do I swap two pointers?
Write a function taking two double pointers and swap the values they point to, which are the pointers themselves.
What is const char ** used for?
It points to an array of read-only strings, such as a table of command names.
Who should free memory returned through an out parameter?
The caller usually owns it, and the function’s comment must say so. Free it once and set the pointer to NULL afterwards.
Can bare-metal code without malloc use this pattern?
Yes. The same out parameter pattern works with a static pool or a fixed array of slots.
Where can I find more free embedded C programming material?
The free C programming course on EmbeddedPathashala continues with strings, structures and memory topics.
Keep Going With the Free C Course
Pointers are the heart of embedded C. Continue through the free C programming course to build real firmware skills.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments