Advanced Pointers
Pointers are like address slips — a pointer to a pointer is an address slip written on another address slip. It sounds convoluted, but in real life, "the locker number where you store a package" works exactly this way, layer upon layer.
Pointer to Pointer
A pointer variable is itself a variable — it lives in memory and has its own address. When you use a pointer to point to another pointer, you get a "pointer to pointer."
int x = 42;
int *p = &x;
int **pp = &p;
Through **pp you can indirectly modify the value of x:
**pp = 100;
printf("%d\n", x);
100
The most common use of a double pointer is "modifying the pointer itself inside a function," such as having a function allocate memory for a pointer:
void alloc_buf(char **ptr, int size) {
*ptr = (char *)malloc(size);
}
int main(void) {
char *buf = NULL;
alloc_buf(&buf, 128);
if (buf) {
strcpy(buf, "hello");
printf("%s\n", buf);
free(buf);
}
return 0;
}
hello
char *ptr, the function modifies a copy — the original pointer outside won't change. To modify the pointer itself, you must pass its address.
Pointer Array vs. Array Pointer
These two concepts are easily confused. The key is the precedence of * and [].
Pointer Array
int *arr[4] — binds with [] first, so it's an array whose elements are int *.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
printf("%d ", *arr[i]);
}
10 20 30
A typical application: an array of strings.
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
Alice
Bob
Carol
Array Pointer
int (*p)[4] — binds with * first, so it's a pointer pointing to an array of 4 ints. Commonly used for passing 2D arrays to functions.
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*p)[4] = matrix;
printf("%d\n", p[1][2]);
7
Example
#include <stdio.h>
void print_matrix(int (*m)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
}
int main(void) {
int matrix[2][4] = {
{10, 20, 30, 40},
{50, 60, 70, 80}
};
print_matrix(matrix, 2);
const char *fruits[3] = {"apple", "banana", "cherry"};
for (int i = 0; i < 3; i++) {
printf("%s ", fruits[i]);
}
printf("\n");
return 0;
}
10 20 30 40
50 60 70 80
apple banana cherry
int *a[4] is a pointer array (an array of pointers), int (*a)[4] is an array pointer (a pointer to an array).
Function Pointer and Callback
A function name is the entry address of that function. A function pointer can store this address, enabling "deferred calls" or "callbacks."
Function Pointer Declaration
int (*pf)(int, int);
This declares pf as a pointer to a function that takes two ints and returns an int.
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main(void) {
int (*pf)(int, int) = add;
printf("%d\n", pf(3, 5));
pf = sub;
printf("%d\n", pf(10, 4));
return 0;
}
8
6
Callback Mechanism
Pass a function pointer as an argument to another function, so the latter can "call it back" at the right time.
void process(int *arr, int len, int (*transform)(int)) {
for (int i = 0; i < len; i++) {
arr[i] = transform(arr[i]);
}
}
int double_it(int n) { return n * 2; }
int negate(int n) { return -n; }
int main(void) {
int data[4] = {1, 2, 3, 4};
process(data, 4, double_it);
for (int i = 0; i < 4; i++) printf("%d ", data[i]);
printf("\n");
process(data, 4, negate);
for (int i = 0; i < 4; i++) printf("%d ", data[i]);
return 0;
}
2 4 6 8
-2 -4 -6 -8
Example
#include <stdio.h>
#include <stdlib.h>
int cmp_asc(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int cmp_desc(const void *a, const void *b) {
return *(int *)b - *(int *)a;
}
void sort_and_print(int *arr, int n, int (*cmp)(const void *, const void *)) {
qsort(arr, n, sizeof(int), cmp);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int nums[5] = {42, 7, 19, 3, 55};
sort_and_print(nums, 5, cmp_asc);
int nums2[5] = {42, 7, 19, 3, 55};
sort_and_print(nums2, 5, cmp_desc);
return 0;
}
3 7 19 42 55
55 42 19 7 3
qsort is a classic application of function pointer callbacks — you only need to provide the comparison rule, and the sorting algorithm takes care of the rest.
Three Combinations of const and Pointers
const combined with pointers has three positions, each with different semantics.
Pointer to const
const int *p;
int const *p;
You cannot modify the pointed-to value through p, but p itself can point elsewhere.
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
10
20
const Pointer
int * const p = &a;
p cannot change what it points to, but you can modify the value through p.
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
99
const Pointer to const
const int * const p = &a;
Neither the pointer's target nor the value it points to can be changed — the strictest read-only.
const is to the left or right of *. On the left, it modifies the data; on the right, it modifies the pointer itself.
void Pointer
void * is a "generic pointer" that can point to any type, but you must cast it before use.
int a = 42;
double b = 3.14;
void *p;
p = &a;
printf("%d\n", *(int *)p);
p = &b;
printf("%.2f\n", *(double *)p);
42
3.14
Typical uses of void *:
mallocreturnsvoid *, which can be assigned to any pointer typeqsort's comparison function usesconst void *parameters- Implementing generic data structures (e.g., a generic linked list)
void * — the compiler doesn't know the size of the data it points to. You must first cast it to a concrete pointer type.
❓ FAQ
[] first, it's an array (pointer array); if it binds with * first, it's a pointer (array pointer). Use parentheses to change precedence.const int *p and int const *p the same?p+1 is undefined. You must first cast it to a concrete type before doing arithmetic.📖 Summary
- A double pointer
int **ppis used to modify a pointer itself inside a function - A pointer array
int *a[N]is an array of pointers; an array pointerint (*p)[N]is a pointer to an array - A function pointer
int (*pf)(int,int)stores a function address, and combined with callbacks enables flexible design constto the left of*modifies the data; to the right of*it modifies the pointer itselfvoid *is a generic pointer — you must cast it before use
📝 Exercises
- Write a function
void swap_ptr(int a, int b)that swaps what two pointers point to. Verify in main that each pointer points to the other's original value after the swap - Declare a function pointer array
int (*ops[4])(int,int)containing add, subtract, multiply, and divide functions. Call the corresponding operation based on a user-input index - Write a generic print function
void print_any(void *data, char type)where type is 'i' for int, 'd' for double, and 's' for string



