A pointer can itself be pointed to by another pointer — a pointer to a pointer — and C also has a special generic pointer type that can point to anything.
void modify(int **pp) {
*pp = malloc(sizeof(int)); // changes what the CALLER's pointer points to
**pp = 42;
}
int main(void) {
int *p = NULL;
modify(&p); // pass the ADDRESS of the pointer itself
printf("%d\n", *p); // 42
free(p);
void *generic = p; // void* can hold an address of ANY type
// printf("%d\n", *generic); // ❌ error: can't dereference void* directly
printf("%d\n", *(int*)generic); // ✅ cast back to the real type first
return 0;
}
Just as a function must receive &variable to modify a caller's int, it must receive &pointer (a pointer to a pointer, int**) to modify what the caller's pointer itself points to — this is exactly why command-line argument parsing and dynamic array-of-strings code frequently use char ** or char ***.
void* is C's generic pointer type — it can hold the address of any object, but the compiler doesn't know what type of data lives there, so it cannot be dereferenced directly, and must first be cast to a concrete pointer type ((int*)generic). This is exactly the mechanism malloc (which returns void*, usable for any data type) and qsort's comparator (which receives const void* so it can compare any element type) rely on — void* trades compile-time type safety for genuine generality, which is why casts back to a concrete type always follow it closely in careful code.