Skip to content
Laviyx Learn

Try

C course menu

Advanced Pointers in C: Pointers to Pointers, void Pointers and Reading Complex Declarations

Go deeper with C pointers: pointers to pointers and why functions need them to change a caller's pointer, void pointers and generic code, the restrict qualifier, and how to read complex declarations such as arrays of function pointers.

Intermediate7 min readUpdated
On this page

This lesson covers the pointer features you meet in real C code: pointers to pointers, generic void * pointers, restrict, and complicated-looking declarations.

Pointers to pointers#

A pointer is a variable, so it has an address too. A pointer to a pointer (int **) stores that address:

C
int value = 7;
int *p = &value;
int **pp = &p;

printf("%d %d %d\n", value, *p, **pp);
**pp = 9;
printf("%d\n", value);
Output
7 7 7
9

*pp is p, and **pp is value.

Changing the caller's pointer#

Pointers are passed by value like everything else. To let a function change which address a caller's pointer holds, pass a pointer to that pointer:

C
#include <stdio.h>

void find_first_digit(const char *s, const char **out) {
    *out = NULL;
    for (; *s != '\0'; s++) {
        if (*s >= '0' && *s <= '9') {
            *out = s;          /* changes the caller's pointer */
            return;
        }
    }
}

int main(void) {
    const char *pos;
    find_first_digit("Room B-204", &pos);
    printf("%s\n", pos ? pos : "(none)");
    find_first_digit("no digits", &pos);
    printf("%s\n", pos ? pos : "(none)");
    return 0;
}
Output
204
(none)

You will see ** in functions that allocate memory for the caller, and in linked-list code that changes the list's head (Linked Lists). main itself can take char **argv, a pointer to an array of strings (Command-Line Arguments).

void pointers#

A void * can hold the address of any object. It cannot be dereferenced directly, because the compiler does not know the type; convert it to the right type first. malloc, memcpy and qsort use void * so they work with any data:

C
#include <stdio.h>
#include <string.h>

void swap_any(void *a, void *b, size_t size) {
    unsigned char tmp[64];             /* enough for this example's types */
    if (size > sizeof tmp) return;
    memcpy(tmp, a, size);
    memcpy(a, b, size);
    memcpy(b, tmp, size);
}

int main(void) {
    int x = 1, y = 2;
    double d1 = 1.5, d2 = 9.75;
    swap_any(&x, &y, sizeof x);
    swap_any(&d1, &d2, sizeof d1);
    printf("%d %d | %.2f %.2f\n", x, y, d1, d2);
    return 0;
}
Output
2 1 | 9.75 1.50

In C, a void * converts to any other object pointer type automatically, so int *p = malloc(...) needs no cast (C++ is different here).

restrict#

restrict (C99) is a promise to the compiler that, for the pointer's lifetime, the object it points to is reached only through that pointer. It lets the compiler optimise more aggressively. The standard library uses it, for example:

C
void *memcpy(void *restrict dest, const void *restrict src, size_t n);

This says the two regions must not overlap; for overlapping copies use memmove. Breaking a restrict promise is undefined behavior.

Reading complex declarations#

Read declarations from the name outwards: first to the right ([] "array of", () "function returning"), then to the left (* "pointer to"), respecting parentheses:

DeclarationRead as
int *a[5]a is an array of 5 pointers to int
int (*a)[5]a is a pointer to an array of 5 int
int *f(void)f is a function returning a pointer to int
int (*f)(void)f is a pointer to a function returning int
int (*ops[3])(int, int)ops is an array of 3 pointers to functions taking two ints and returning int

typedef makes such types readable:

C
#include <stdio.h>

typedef int (*BinaryOp)(int, int);

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    BinaryOp ops[2] = {add, sub};      /* same as int (*ops[2])(int, int) */
    printf("%d %d\n", ops[0](9, 4), ops[1](9, 4));
    return 0;
}
Output
13 5

Practice#

Practice exercise

Write void split_at(char *s, char sep, char **rest) that replaces the first sep in s with '\0' and sets *rest to the text after it (or NULL if sep is not found). Use it on "key=value" with '='.

Show solution
C
#include <stdio.h>

void split_at(char *s, char sep, char **rest) {
    *rest = NULL;
    for (; *s != '\0'; s++) {
        if (*s == sep) {
            *s = '\0';
            *rest = s + 1;
            return;
        }
    }
}

int main(void) {
    char line[] = "key=value";
    char *rest;
    split_at(line, '=', &rest);
    printf("[%s] [%s]\n", line, rest ? rest : "");
    return 0;
}
Output
[key] [value]

Key takeaway#

Finished this lesson?