devnotes.

Pointers to Pointers and Void Pointers

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.

Pointers to Pointers and Void Pointers in C | DevNotes