Skip to Content
Course content

47: Pointers to Pointers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you something that trips up almost everyone when they first hit intermediate C. You've already mastered pointers—you know they hold addresses. But there's a specific wall you hit when you try to change where a pointer points from inside a function. Let's walk through it together.

The disappearing memory

Imagine we're building a system that loads a configuration string from a file. I want to write a helper function that handles the memory allocation for that string so my main logic stays clean. Here is my first attempt:

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

void load_config(char *str) {
    str = malloc(100); 
    strcpy(str, "Setting=Enabled");
    printf("Inside function: %s\n", str);
}

int main() {
    char *my_config = NULL;
    load_config(my_config);
    printf("In main: %p\n", (void*)my_config); 
    return 0;
}

If you run this, you'll see the string printed inside the function, but in main, my_config is still NULL. Why? I passed the pointer, right? Well, here is the catch: in C, everything is passed by value. When I pass my_config to load_config, I'm not passing the pointer itself; I'm passing a copy of the address it holds. Inside the function, str is a local variable that happens to be a pointer. I changed str to point to the new memory, but the original my_config back in main never moved. It's still staring at NULL.

Looking at the address of the address

If I want to change a variable inside a function, I pass a pointer to that variable. It's the same logic here. If the variable I want to change is itself a pointer (char *), then I need to pass a pointer to that pointer. That's a char **.

Let's try that. I'll change the function signature to accept a pointer to a pointer, and in main, I'll pass the address of my pointer using the & operator.

void load_config(char **str_ptr) {
    // str_ptr is the address of my_config in main
    *str_ptr = malloc(100); 
    strcpy(*str_ptr, "Setting=Enabled");
    printf("Inside function: %s\n", *str_ptr);
}

int main() {
    char *my_config = NULL;
    load_config(&my_config); // Passing the address of the pointer
    printf("In main: %s\n", my_config); 
    return 0;
}

Now it works. By dereferencing str_ptr once (*str_ptr), I'm reaching back into main's memory and actually modifying the value of my_config. I'm not changing a local copy; I'm changing the actual pointer variable.

Connecting the dots with double dereferencing

It helps to visualize this as a chain. A regular pointer is one jump: Pointer → Data. A pointer to a pointer is two jumps: Pointer to Pointer → Pointer → Data.

This becomes incredibly powerful when you're dealing with arrays of strings. Think about it: a string in C is just a char *. If you want a list of strings, you need a pointer to a set of char * variables. That's exactly what char **argv is in your main function. It's a pointer to the first element of an array, where every element is another pointer to a character sequence.

I've found that the "mental click" happens when you stop thinking of ** as some scary advanced syntax and start thinking of it as simply "I want to modify a pointer from inside another function."




📋 Practical Task

Implementing a Dynamic String Array Resizer

You are building a simple system to manage a list of usernames. You have a function that needs to reallocate the array of pointers to make room for more names. Because realloc can change the base address of the array, you must update the original pointer in the calling function.

Your Task: Complete the resize_user_list function. It should take a pointer to the pointer of the user list (char ***list) and the new size. Allocate memory for the new number of string pointers and ensure the original pointer in main is updated to point to this new memory block.

#include <stdio.h>
#include <stdlib.h>

void resize_user_list(char ***list, int new_size) {
    // TODO: Use realloc to change the size of (*list)
    // Ensure you handle the pointer to pointer logic correctly
}

int main() {
    char **users = NULL;
    int size = 3;

    resize_user_list(&users, size);
    
    if (users != NULL) {
        printf("Successfully resized list to %d slots.\n", size);
        // Cleanup
        free(users);
    } else {
        printf("Resize failed.\n");
    }

    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.