Pointers to Pointers, Arrays of Pointers, Function Pointers
What This Concept Is
Once a pointer is just a typed address, you can have pointers to pointers, arrays of pointers, and pointers to code. All three are common in real C:
T **(pointer to pointer): used to let a function allocate or replace a caller's pointer, to build jagged 2D structures, or to walk linked lists in-place.- Array of pointers (
T *a[n]): a compact way to hold a collection of independently sized / independently stored objects, most famouslyargv. - Function pointers (
R (*f)(args...)): a variable whose value is the entry address of a function. Dispatch tables, callbacks, and plugin systems all live here.
Declaration reads "inside out": int *(*f)(int) is "f is a pointer to a function taking an int and returning a pointer to int."
Why It Matters Here
These three patterns cover most interfaces you will meet:
main(int argc, char *argv[])is an array of string pointersqsort(base, nmemb, size, cmp)takes a function pointerint **pp = &p;is howfree_and_null(&ptr)works- dynamic dispatch in C (vtables, handlers, hook tables) is an array of function pointers
Concrete Example
Suppose you have:
int x = 42;
int *p = &x;
int **pp = &p;
On a 64-bit machine:
Address Name Value Meaning
0x1000 x 0x0000002A int 42
0x2000 p 0x0000000000001000 pointer to x
0x3000 pp 0x0000000000002000 pointer to p
*ppisp(address0x1000)**ppisx(42)*pp = NULL;makespnull without touchingx
Now a function-pointer table:
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main(void) {
int (*ops[2])(int, int) = { add, sub };
printf("%d %d\n", ops[0](3, 4), ops[1](3, 4)); /* 7 -1 */
return 0;
}
ops is an array of two function pointers. ops[0](3, 4) calls add.
Common Confusion / Misconception
"int **pp is for 2D arrays." Only jagged ones allocated row-by-row. A true int m[3][4] is one contiguous block and decays to int (*)[4], not to int **.
"Function pointers need & and *." In C, a function name decays to a function pointer in a value context, so f, &f, and *f are all equivalent. f(x) and (*f)(x) both call it. Pick one style and stick with it.
"I can just printf("%p", fptr);." It is undefined to print a function pointer through %p (which is for object pointers). Cast to void * only after you have confirmed it works on your platform, or use %zu-style prints of the bit pattern via memcpy.
How To Use It
For every indirection beyond one level:
- Read the type right-to-left: find the name, then apply each operator outward.
- Draw the diagram: boxes for each object, arrows for each address.
- When a function modifies a caller's pointer, take
T **and assign through it. - For function pointers,
typedefthe signature.typedef int (*cmp_t)(const void *, const void *);reads far better than the inline form.
Check Yourself
- Write the type of a pointer to an array of 10
int. - What is the difference between
int *a[5]andint (*a)[5]? - How would you declare an array of pointers to functions that each take a
const char *and returnvoid?
Mini Drill or Application
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int cmp_str(const void *a, const void *b) {
const char *const *sa = a;
const char *const *sb = b;
return strcmp(*sa, *sb);
}
int main(int argc, char **argv) {
if (argc < 2) return 0;
qsort(&argv[1], (size_t)(argc - 1), sizeof(char *), cmp_str);
for (int i = 1; i < argc; i++) puts(argv[i]);
return 0;
}
Build: gcc -Wall -Wextra -o sort_args sort_args.c. Run ./sort_args banana apple cherry. Draw the state of argv as an array of char *, then the state after qsort rewrites it.