Hello! Welcome to our fifth lesson in the "Complex Pointer Types" module.
In our previous lesson, we explored how pointer arithmetic works with multi-dimensional arrays, focusing on the type (*)[cols] pointer-to-array type. We saw how this type is essential for the compiler to correctly calculate offsets when traversing a contiguous block of memory.
Today, we shift our focus to another powerful, and often misunderstood, pointer type: the pointer to a pointer, or "double pointer." Our goal is to master a fundamental C programming technique: using a pointer to a pointer to modify a caller's pointer variable from within a function. This is a critical skill for managing dynamic memory and implementing many common data structures.
The Problem: C's Pass-by-Value Semantics
You'll recall from our earlier lessons on functions that C passes all arguments by value. This means the function receives a copy of the argument, not the original variable.
When we wanted a function to modify a simple int variable in the caller, we couldn't pass the int directly. Instead, we passed a pointer to it (int *). Inside the function, we could dereference the pointer and change the value at the original address.
Now, consider this question: What if the variable we want the function to modify is itself a pointer? For example, what if we want a function to allocate memory and make a caller's pointer point to that new memory?
If we pass the pointer directly, the function receives a copy of the pointer's value (the address it holds). The function can change its local copy to point somewhere else, but this will have no effect on the original pointer in the calling function.
The following resource provides a concise, perfect illustration of this exact problem.
Passing a double pointer in a function
This Stack Overflow answer clearly demonstrates the problem with a simple code example. Focus on the difference between the change1 and change2 functions.
Read the accepted answer by user 'Some programmer dude'. Pay close attention to the main function and the comments explaining why the call to change1(pointer_to_a) fails to modify pointer_to_a, while change2(&pointer_to_a) succeeds.
As you saw, change1 only modifies its local copy. To change the original pointer_to_a, we needed a way to pass its address to change2. This is where pointers to pointers come in.
The Solution: Pointers to Pointers (**)
A pointer to a pointer is exactly what it sounds like: a variable that stores the memory address of another pointer.
- An
intvariable stores an integer value. - An
int *variable stores the address of anint. - An
int **variable stores the address of anint *.
This creates another level of indirection, which we can visualize as a chain.

To truly grasp this, it helps to see how the addresses and values look at each step.
This video provides a clear walkthrough of the memory layout, showing how the values of variables, pointers, and double pointers relate to each other's addresses.
Watch the first 5 minutes (00:00 to 05:13). Focus on the diagrams and the printf outputs that trace the address of each variable, the value it holds (which may be an address), and the result of dereferencing.
The Mechanism: Dereferencing to Modify
Let's revisit the successful change2 function from the Stack Overflow example:
void change2(int **x)
{
*x = &b; // Make *x point to b
}
// Called from main as:
// change2(&pointer_to_a);
Let's break down how this works:
&pointer_to_a: Inmain, we take the address of the pointerpointer_to_a. The type of this expression isint **.void change2(int **x): This address is passed by value tochange2. The local variablexnow holds the address ofpointer_to_a. So,xpoints topointer_to_a.*x: Insidechange2, we dereferencexonce. Sincexpoints topointer_to_a,*xgives us an alias for the originalpointer_to_avariable back inmain.*x = &b;: We are assigning a new value (the address ofb) directly to the originalpointer_to_avariable. The change is successful and persists after the function returns.
This technique is extremely common for functions that need to allocate resources on behalf of the caller.
Pointer to Pointer as Function Argument in C
Let's study a practical example where a function allocates memory and modifies the caller's pointer. This article explains the concept and provides a clean code sample.
Read the section titled 'Pointer to Pointer as Function Argument in C'. Analyze the allocateAndInitialize function. Note how it takes int **arr as an argument and how the dereference *arr is used on the left-hand side of the assignment with malloc.
Test your understanding!
You are tasked with creating a helper function that initializes a communication buffer.
Implement the function void create_message_buffer(char **buffer_ptr, int size).
This function should:
- Dynamically allocate
sizebytes of memory. - Make the caller's pointer (passed via
buffer_ptr) point to this new memory. - As a simple initialization, it should set the first byte of the new buffer to
'H'and the last byte to'T'. - Handle the case where
mallocfails by printing an error.
In main, declare char *my_buffer = NULL;, call your function to allocate a 100-byte buffer, and then verify that my_buffer is no longer NULL.
Show answer
#include <stdio.h>
#include <stdlib.h>
void create_message_buffer(char **buffer_ptr, int size) {
// 1. Allocate memory.
// We dereference buffer_ptr to access the original pointer in main.
*buffer_ptr = (char *)malloc(size);
// 4. Handle allocation failure.
if (*buffer_ptr == NULL) {
printf("Error: Memory allocation failed.\n");
return;
}
// 3. Initialize the buffer.
if (size > 0) {
(*buffer_ptr)[0] = 'H'; // 'H' for Head
}
if (size > 1) {
(*buffer_ptr)[size - 1] = 'T'; // 'T' for Tail
}
}
int main(void) {
char *my_buffer = NULL;
int buffer_size = 100;
printf("Before call, my_buffer is: %p\n", (void *)my_buffer);
// Pass the address of the pointer my_buffer.
create_message_buffer(&my_buffer, buffer_size);
printf("After call, my_buffer is: %p\n", (void *)my_buffer);
if (my_buffer != NULL) {
printf("First byte: %c\n", my_buffer[0]);
printf("Last byte: %c\n", my_buffer[buffer_size - 1]);
// Clean up the allocated memory.
free(my_buffer);
my_buffer = NULL; // Good practice
}
return 0;
}
Key points: The call is create_message_buffer(&my_buffer, ...), passing the address of the pointer. Inside the function, *buffer_ptr is used to access and modify my_buffer.
Real-World Use Cases
This pattern isn't just a theoretical trick. It's used in many real-world APIs. Your familiarity with various software development models and tools will make this feel intuitive as a design pattern for resource management.
Making Peace with Double Pointers
To see this concept in a production-level API, this video uses the pthread_join function as an excellent example.
Watch the segment from 01:21 to 02:59. The pthread_join function needs to return a value to the caller, but its primary return value is already used for an error code. Notice how it uses a void ** parameter to allow the caller to pass the address of a void * variable, which the function then fills with the thread's return value. This is a classic example of using a double pointer to 'return' a pointer value through a parameter.
Another classic use case is modifying the head of a linked list. If you write a function to insert a new node at the beginning of the list, the head pointer itself must be updated to point to the new node. To do this, the insertion function must receive the address of the head pointer, i.e., a pointer to a pointer.
Conclusion
Today, we've tackled a crucial concept in C pointer manipulation. By understanding how to use a pointer to a pointer, you can now write functions that have the power to modify pointer variables in their calling scope.
Key Takeaways:
- Because C is pass-by-value, a function cannot modify a caller's variable (including pointers) that is passed directly.
- The solution is to pass a pointer to the variable you want to modify. If that variable is already a pointer, you must pass a pointer to a pointer (
**). - The function call involves the address-of operator (
&my_pointer). - Inside the function, a single dereference (
*) of the double-pointer parameter gives you access to the original pointer variable.
Preview of the Next Lesson:
We've just learned the essential mechanism for modifying pointers. In our next lesson, we will immediately apply this knowledge to a very practical and common task: implementing a dynamic two-dimensional array using a pointer to a pointer. This will combine today's concept with dynamic memory allocation to build flexible, resizable data structures.