C course menu
Advanced Pointers in C: Pointers to Pointers, void Pointers and Reading Complex Declarations
Go deeper with C pointers: pointers to pointers and why functions need them to change a caller's pointer, void pointers and generic code, the restrict qualifier, and how to read complex declarations such as arrays of function pointers.
On this page
This lesson covers the pointer features you meet in real C code: pointers to pointers, generic void * pointers, restrict, and complicated-looking declarations.
Pointers to pointers#
A pointer is a variable, so it has an address too. A pointer to a pointer (int **) stores that address:
int value = 7;
int *p = &value;
int **pp = &p;
printf("%d %d %d\n", value, *p, **pp);
**pp = 9;
printf("%d\n", value);7 7 7
9*pp is p, and **pp is value.
Changing the caller's pointer#
Pointers are passed by value like everything else. To let a function change which address a caller's pointer holds, pass a pointer to that pointer:
#include <stdio.h>
void find_first_digit(const char *s, const char **out) {
*out = NULL;
for (; *s != '\0'; s++) {
if (*s >= '0' && *s <= '9') {
*out = s; /* changes the caller's pointer */
return;
}
}
}
int main(void) {
const char *pos;
find_first_digit("Room B-204", &pos);
printf("%s\n", pos ? pos : "(none)");
find_first_digit("no digits", &pos);
printf("%s\n", pos ? pos : "(none)");
return 0;
}204
(none)You will see ** in functions that allocate memory for the caller, and in linked-list code that changes the list's head (Linked Lists). main itself can take char **argv, a pointer to an array of strings (Command-Line Arguments).
void pointers#
A void * can hold the address of any object. It cannot be dereferenced directly, because the compiler does not know the type; convert it to the right type first. malloc, memcpy and qsort use void * so they work with any data:
#include <stdio.h>
#include <string.h>
void swap_any(void *a, void *b, size_t size) {
unsigned char tmp[64]; /* enough for this example's types */
if (size > sizeof tmp) return;
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
}
int main(void) {
int x = 1, y = 2;
double d1 = 1.5, d2 = 9.75;
swap_any(&x, &y, sizeof x);
swap_any(&d1, &d2, sizeof d1);
printf("%d %d | %.2f %.2f\n", x, y, d1, d2);
return 0;
}2 1 | 9.75 1.50In C, a void * converts to any other object pointer type automatically, so int *p = malloc(...) needs no cast (C++ is different here).
restrict#
restrict (C99) is a promise to the compiler that, for the pointer's lifetime, the object it points to is reached only through that pointer. It lets the compiler optimise more aggressively. The standard library uses it, for example:
void *memcpy(void *restrict dest, const void *restrict src, size_t n);This says the two regions must not overlap; for overlapping copies use memmove. Breaking a restrict promise is undefined behavior.
Reading complex declarations#
Read declarations from the name outwards: first to the right ([] "array of", () "function returning"), then to the left (* "pointer to"), respecting parentheses:
typedef makes such types readable:
#include <stdio.h>
typedef int (*BinaryOp)(int, int);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main(void) {
BinaryOp ops[2] = {add, sub}; /* same as int (*ops[2])(int, int) */
printf("%d %d\n", ops[0](9, 4), ops[1](9, 4));
return 0;
}13 5Practice#
Practice exercise
Write void split_at(char *s, char sep, char **rest) that replaces the first sep in s with '\0' and sets *rest to the text after it (or NULL if sep is not found). Use it on "key=value" with '='.
Show solution
#include <stdio.h>
void split_at(char *s, char sep, char **rest) {
*rest = NULL;
for (; *s != '\0'; s++) {
if (*s == sep) {
*s = '\0';
*rest = s + 1;
return;
}
}
}
int main(void) {
char line[] = "key=value";
char *rest;
split_at(line, '=', &rest);
printf("[%s] [%s]\n", line, rest ? rest : "");
return 0;
}[key] [value]Key takeaway#
Finished this lesson?