More Pointers
Pointers to pointers, strings and functionsA pointer can point to anything that has an address — including another pointer and even a function. In the lab below you follow two hops with **pp, re-order names by swapping pointers instead of text, and run two different functions through the same function pointer.
- int **pppoints to a pointer
- char *names[3]three addresses of strings
- int (*op)(int, int)points to a function
Ready
Keyboard: ← → step · Space play / pause · R reset · Esc exit classroom mode
The ideas behind the lab
Exactly what you watched happen, in plain steps.
Anything with an address can be pointed to
A pointer is itself a variable, so it has an address — and a pointer to it is a pointer to a pointer (int **). An array can hold pointers, which is the usual way to keep a list of strings of different lengths. Functions also live at addresses, so a function pointer can store which function to call. const decides whether the pointed-to data, the pointer, or both may change, and void * can hold any address but must be cast before use.
int x = 5, *p = &x, **pp = &p; // **pp is x
const char *names[] = {"Ann", "Bob"}; // array of pointers
int add(int a, int b);
int (*op)(int, int) = add; // function pointer
op(2, 3); // calls add: 5
void *any = &x; // any address
int y = *(int *) any; // cast before use
| Declaration | Change *p? | Change p? | Read as |
|---|---|---|---|
| int *p | yes | yes | p is a pointer to int |
| const int *p | no | yes | p is a pointer to a const int |
| int *const p | yes | no | p is a const pointer to int |
| const int *const p | no | no | p is a const pointer to a const int |
| void *p | only after a cast | yes | p points to "something" |
Pointers to pointers
- int **pp = &p; — pp holds the address of the pointer p.
- *pp is p (an address); **pp is the int p points to.
- A function that must change the caller's pointer takes a pointer to it: void f(int **pp).
- char **argv in main is a pointer to an array of string pointers.
- Each extra * is one more hop to follow.
Arrays of pointers
- const char *names[3] holds 3 addresses (24 bytes), not the text.
- Each string can have a different length — no wasted space.
- names[i] is a string; names[i][j] is one character of it.
- Swapping or sorting moves only the 8-byte pointers.
- Compare char grid[3][10]: fixed-size rows that are copied to swap.
Function pointers and void *
- int (*f)(int) — the brackets make f a pointer; int *f(int) would be a function returning int *.
- Assign a function name: f = square; call it: f(5).
- Pass functions to functions ("callbacks"), e.g. qsort's compare function.
- void * holds any data address; malloc returns one (Module 33).
- You cannot dereference a void * or do arithmetic on it — cast it first.
Callbacks, an array of strings, ** and void *
Every line, explained.
#include <stdio.h>
int twice(int n) { return 2 * n; }
int square(int n) { return n * n; }
void applyAll(int a[], int n, int (*f)(int))
f: any function taking int and returning int.
{
for (int i = 0; i < n; i++)
a[i] = f(a[i]);
Calls whatever function was passed in.
}
void setToNull(int **pp)
Receives the address of a pointer.
{
*pp = NULL;
Changes the caller's pointer.
}
int main(void)
{
int v[] = {1, 2, 3};
applyAll(v, 3, twice);
v = 2 4 6
applyAll(v, 3, square);
v = 4 16 36
printf("%d %d %d\n", v[0], v[1], v[2]);
Prints 4 16 36
const char *days[] = {"Mon", "Tue", "Wed"};
printf("%s %c\n", days[1], days[2][0]);
Prints Tue W
int x = 1;
int *p = &x;
setToNull(&p);
printf("%s\n", p == NULL ? "p is NULL" : "p is set");
Prints p is NULL
void *any = &x;
A void * may hold any address …
printf("%d\n", *(int *) any);
… but must be cast before use: prints 1
return 0;
}
Spot the bug
Mistakes almost every beginner makes — and how to fix them.
Missing brackets in a function pointer
int *op(int, int); // a function returning int *
int (*op)(int, int); // a pointer to a function
( ) binds tighter than *, so the brackets around *op are essential.
Changing a pointer parameter instead of the caller's pointer
void clear(int *p) { p = NULL; } // caller unchanged
void clear(int **pp) { *pp = NULL; }
clear(&p);
To change the caller's pointer, pass its address — a pointer to the pointer.
Dereferencing a void *
void *v = &x;
printf("%d", *v); // error
void *v = &x;
printf("%d", *(int *) v);
The compiler does not know what type is stored there; tell it with a cast.
Writing through a pointer to const
const int *p = &x; *p = 5; // error
int *p = &x; *p = 5;
const int * promises not to change the data through p.
Changing a string through an array of literal pointers
char *names[] = {"Ann"};
names[0][0] = 'a'; // crash
char names[][4] = {"Ann"};
names[0][0] = 'a';
The pointers point at string literals in read-only memory. Use const char * to make the compiler catch it.
Check your understanding
Answer each question — you will see the explanation straight away.
int x = 3; int *p = &x; int **pp = &p; — what is **pp?
Two hops: pp → p → x.
const char *n[] = {"Ann", "Bob"}; — what does n[1][0] give?
n[1] is "Bob"; [0] is its first character.
Which declares a pointer to a function taking an int and returning an int?
The brackets make f a pointer.
const int *p = &x; — which is allowed?
The data is const; the pointer itself may move.
What must you do before using the value behind a void * ?
Only a typed pointer can be dereferenced.
More Pointers quiz
Test yourself: 10 questions, 70% to pass. Create a free account to take the quiz, save your progress and work towards the certificate.