Pointer to Pointer (Double Pointer) in C: Complete Guide
Pointer to Pointer (Double Pointer) in C: A Comprehensive Tutorial
A double pointer in C is a pointer that stores the address of another pointer. It is also known as a pointer to pointer because it points to another pointer instead of directly pointing to a variable. Double pointers are essential for dynamic memory allocation, passing pointers to functions, and managing complex data structures.
This tutorial provides an in-depth exploration of double pointers in C: syntax, dereferencing, size, dynamic 2D arrays, array of strings, multilevel pointers, practical applications, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is a Double Pointer?
A double pointer is a pointer that stores the address of another pointer. While a single pointer points directly to a variable, a double pointer points to a single pointer.
Visual representation:
text
var (value: 10) ptr1 (value: &var) ptr2 (value: &ptr1) +--------+ +----------------+ +-----------------+ | 10 | <------ | 0x7ffc1000 | <--- | 0x7ffc0ff8 | +--------+ +----------------+ +-----------------+ Address: 0x7ffc1000 Address: 0x7ffc0ff8 Address: 0x7ffc0ff0
Why Use Double Pointers?
- Dynamic 2D arrays: Create arrays with varying row sizes.
- Passing pointers to functions: Modify pointer values in functions.
- Array of strings: Manage collections of string pointers.
- Complex data structures: Linked lists, trees with pointer manipulation.
- Memory management: Dynamic allocation of pointer arrays.
Single Pointer vs. Double Pointer
| Aspect | Single Pointer | Double Pointer |
|---|---|---|
| Declaration | int *ptr | int **ptr |
| Points to | Variable | Pointer to variable |
| Dereference | *ptr (value) | **ptr (value), *ptr (address) |
| Common use | Direct data access | Dynamic arrays, pointer modification |
Syntax & Core Mechanics
Declaration
Syntax:
c
data_type **pointer_name;
Example:
c
int **doublePtr; // Pointer to pointer to int char **stringArray; // Pointer to pointer to char
Basic Usage
c
#include <stdio.h> int main(void) { int var = 10; int *ptr1 = &var; // Single pointer int **ptr2 = &ptr1; // Double pointer printf("var = %d\n", var); printf("*ptr1 = %d\n", *ptr1); printf("**ptr2 = %d\n", **ptr2); /* Accessing addresses */ printf("&var = %p\n", (void*)&var); printf("ptr1 = %p\n", (void*)ptr1); printf("&ptr1 = %p\n", (void*)&ptr1); printf("ptr2 = %p\n", (void*)ptr2); printf("*ptr2 = %p (same as ptr1)\n", (void*)*ptr2); return 0; } Expected output (addresses will vary):
text
var = 10 *ptr1 = 10 **ptr2 = 10 &var = 0x7ffc1000 ptr1 = 0x7ffc1000 &ptr1 = 0x7ffc0ff8 ptr2 = 0x7ffc0ff8 *ptr2 = 0x7ffc1000 (same as ptr1)
Size of Double Pointer
The size of a double pointer is the same as any other pointer (architecture-dependent).
c
#include <stdio.h> int main(void) { int a = 5; int *ptr = &a; int **d_ptr = &ptr; printf("Size of double pointer: %zu bytes\n", sizeof(d_ptr)); printf("Size of single pointer: %zu bytes\n", sizeof(ptr)); printf("Size of int: %zu bytes\n", sizeof(int)); return 0; } Expected output (64-bit system):
text
Size of double pointer: 8 bytes Size of single pointer: 8 bytes Size of int: 4 bytes
Modifying Pointer Through Double Pointer
c
#include <stdio.h> void modifyPointer(int **ptr) { static int newValue = 100; *ptr = &newValue; // Change what the pointer points to } int main(void) { int a = 10; int b = 20; int *ptr = &a; printf("Before: *ptr = %d\n", *ptr); modifyPointer(&ptr); printf("After: *ptr = %d\n", *ptr); return 0; } Expected output:
text
Before: *ptr = 10 After: *ptr = 100
Dynamic 2D Array Using Double Pointer
Double pointers enable creating dynamically sized 2D arrays.
c
#include <stdio.h> #include <stdlib.h> int main(void) { int rows = 2, cols = 3; /* Allocate memory for row pointers */ int **matrix = malloc(rows * sizeof(int *)); if (matrix == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return EXIT_FAILURE; } /* Allocate memory for each row */ for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); if (matrix[i] == NULL) { fprintf(stderr, "Memory allocation failed.\n"); /* Free previously allocated rows */ for (int j = 0; j < i; j++) { free(matrix[j]); } free(matrix); return EXIT_FAILURE; } } /* Initialize */ for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { matrix[i][j] = i * cols + j + 1; } } /* Display */ for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { printf("%d ", matrix[i][j]); } printf("\n"); } /* Free memory */ for (int i = 0; i < rows; i++) { free(matrix[i]); } free(matrix); return 0; } Expected output:
text
1 2 3 4 5 6
Jagged Array (Different Row Sizes)
c
#include <stdio.h> #include <stdlib.h> int main(void) { int rows = 3; /* Allocate row pointers */ int **jagged = malloc(rows * sizeof(int *)); if (jagged == NULL) return EXIT_FAILURE; /* Allocate different sizes for each row */ jagged[0] = malloc(2 * sizeof(int)); jagged[1] = malloc(4 * sizeof(int)); jagged[2] = malloc(3 * sizeof(int)); /* Initialize */ for (int i = 0; i < 2; i++) jagged[0][i] = i + 1; for (int i = 0; i < 4; i++) jagged[1][i] = i + 1; for (int i = 0; i < 3; i++) jagged[2][i] = i + 1; /* Display */ printf("Jagged array:\n"); printf("Row 0 (2 elements): "); for (int i = 0; i < 2; i++) printf("%d ", jagged[0][i]); printf("\n"); printf("Row 1 (4 elements): "); for (int i = 0; i < 4; i++) printf("%d ", jagged[1][i]); printf("\n"); printf("Row 2 (3 elements): "); for (int i = 0; i < 3; i++) printf("%d ", jagged[2][i]); printf("\n"); /* Free */ for (int i = 0; i < rows; i++) free(jagged[i]); free(jagged); return 0; } Expected output:
text
Jagged array: Row 0 (2 elements): 1 2 Row 1 (4 elements): 1 2 3 4 Row 2 (3 elements): 1 2 3
Passing Array of Strings to Function
Double pointers are used to pass arrays of strings to functions.
c
#include <stdio.h> void printStrings(char **arr, int n) { for (int i = 0; i < n; i++) { printf("%s\n", *(arr + i)); // Equivalent to arr[i] } } int main(void) { char *arr[] = {"Geek", "Geeks", "Geekfor"}; printStrings(arr, 3); return 0; } Expected output:
text
Geek Geeks Geekfor
Multilevel Pointers
C supports multiple levels of pointers beyond double pointers.
c
#include <stdio.h> int main(void) { int value = 10; int *ptr1 = &value; int **ptr2 = &ptr1; int ***ptr3 = &ptr2; int ****ptr4 = &ptr3; printf("value = %d\n", value); printf("*ptr1 = %d\n", *ptr1); printf("**ptr2 = %d\n", **ptr2); printf("***ptr3 = %d\n", ***ptr3); printf("****ptr4 = %d\n", ****ptr4); return 0; } Expected output:
text
value = 10 *ptr1 = 10 **ptr2 = 10 ***ptr3 = 10 ****ptr4 = 10
Full Practical Code Example with Explanation
This program demonstrates double pointers in a realistic scenario: a dynamic string database manager.
Program: Dynamic String Database
c
/** * string_database.c * Demonstrates double pointers for dynamic string management. * Includes dynamic allocation, string operations, and memory management. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_STRINGS 100 #define MAX_STR_LEN 100 #define MAX_LINE_LEN 200 /* Function prototypes */ int addString(char ***database, int *count, const char *newString); void displayStrings(char **database, int count); void sortStrings(char **database, int count); int searchString(char **database, int count, const char *target); void freeDatabase(char **database, int count); int main(void) { char **database = NULL; int count = 0; int choice; char buffer[MAX_LINE_LEN]; char input[MAX_STR_LEN]; printf("=== Dynamic String Database ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Add String\n"); printf("2. Display All Strings\n"); printf("3. Sort Strings\n"); printf("4. Search String\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); freeDatabase(database, count); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: printf("Enter string: "); fflush(stdout); if (fgets(input, sizeof(input), stdin) != NULL) { input[strcspn(input, "\n")] = '\0'; if (addString(&database, &count, input) == 0) { printf("String added. Total: %d\n", count); } } break; case 2: displayStrings(database, count); break; case 3: sortStrings(database, count); printf("Strings sorted alphabetically.\n"); displayStrings(database, count); break; case 4: printf("Enter string to search: "); fflush(stdout); if (fgets(input, sizeof(input), stdin) != NULL) { input[strcspn(input, "\n")] = '\0'; int index = searchString(database, count, input); if (index != -1) { printf("Found '%s' at position %d\n", input, index + 1); } else { printf("'%s' not found.\n", input); } } break; case 5: printf("Exiting.\n"); freeDatabase(database, count); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } freeDatabase(database, count); return EXIT_SUCCESS; } /* Adds a string to the database using double pointer */ int addString(char ***database, int *count, const char *newString) { if (*count >= MAX_STRINGS) { fprintf(stderr, "Database full.\n"); return -1; } /* Allocate memory for new string */ char *newEntry = malloc((strlen(newString) + 1) * sizeof(char)); if (newEntry == NULL) { fprintf(stderr, "Memory allocation failed.\n"); return -1; } strcpy(newEntry, newString); /* Reallocate the array of pointers */ char **temp = realloc(*database, (*count + 1) * sizeof(char *)); if (temp == NULL) { fprintf(stderr, "Memory reallocation failed.\n"); free(newEntry); return -1; } *database = temp; (*database)[*count] = newEntry; (*count)++; return 0; } /* Displays all strings */ void displayStrings(char **database, int count) { if (count == 0) { printf("No strings in database.\n"); return; } printf("\n=== String Database ===\n"); for (int i = 0; i < count; i++) { printf("%d. %s\n", i + 1, database[i]); } } /* Sorts strings alphabetically */ void sortStrings(char **database, int count) { for (int i = 0; i < count - 1; i++) { for (int j = 0; j < count - i - 1; j++) { if (strcmp(database[j], database[j + 1]) > 0) { /* Swap pointers, not strings */ char *temp = database[j]; database[j] = database[j + 1]; database[j + 1] = temp; } } } } /* Searches for a string; returns index or -1 */ int searchString(char **database, int count, const char *target) { for (int i = 0; i < count; i++) { if (strcmp(database[i], target) == 0) { return i; } } return -1; } /* Frees all allocated memory */ void freeDatabase(char **database, int count) { if (database == NULL) return; for (int i = 0; i < count; i++) { free(database[i]); } free(database); } Explanation of Double Pointer Usage
char **database: Array of string pointers.addString(&database, &count, ...): Passes address of database to modify it.realloc: Resizes the pointer array dynamically.- Sorting: Swaps pointers (not strings) for efficiency.
- Memory management: Frees both individual strings and the pointer array.
Expected Output (sample run)
text
=== Dynamic String Database === --- Menu --- 1. Add String 2. Display All Strings 3. Sort Strings 4. Search String 5. Exit Enter choice: 1 Enter string: banana String added. Total: 1 --- Menu --- ... Enter choice: 1 Enter string: apple String added. Total: 2 --- Menu --- ... Enter choice: 1 Enter string: cherry String added. Total: 3 --- Menu --- ... Enter choice: 2 === String Database === 1. banana 2. apple 3. cherry --- Menu --- ... Enter choice: 3 Strings sorted alphabetically. === String Database === 1. apple 2. banana 3. cherry --- Menu --- ... Enter choice: 4 Enter string to search: banana Found 'banana' at position 2 --- Menu --- ... Enter choice: 5 Exiting.
Common Pitfalls, Edge Cases & Memory Considerations
1. Dereferencing NULL Double Pointer
Dereferencing a NULL double pointer causes a crash.
c
int **ptr = NULL; int value = **ptr; // BUG: NULL dereference
Solution: Check if (ptr != NULL && *ptr != NULL) before dereferencing.
2. Memory Leaks in Dynamic 2D Arrays
Forgetting to free each row causes memory leaks.
c
int **matrix = malloc(rows * sizeof(int *)); for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); } /* Missing: free each row before free(matrix) */ Solution: Free each row first, then the pointer array.
3. Shallow Copy of Double Pointer
Assigning one double pointer to another copies the pointer, not the data.
c
int **copy = original; // Shallow copy
Solution: Create a deep copy if independent data is needed.
4. Incorrect Memory Allocation
Allocating wrong sizes for double pointers.
c
int **arr = malloc(rows * sizeof(int)); // BUG: should be sizeof(int*)
Solution: Use malloc(rows * sizeof(int *)) for pointer array.
5. Not Checking malloc Return
Forgetting to check if malloc succeeded.
c
int **arr = malloc(10 * sizeof(int *)); arr[0] = malloc(10 * sizeof(int)); // BUG if malloc failed
Solution: Always check if (arr != NULL) after allocation.
6. Confusing *ptr and **ptr
Misunderstanding dereference levels causes errors.
c
int **ptr; printf("%d\n", *ptr); // Prints address (int*), not value printf("%d\n", **ptr); // Prints value (int) Solution: Understand what each level dereferences to.
7. Passing Double Pointer Incorrectly
Forgetting to pass the address of the pointer.
c
void modify(int **ptr) { *ptr = ...; } int *singlePtr; modify(singlePtr); // BUG: should be &singlePtr Solution: Pass &singlePtr when the function expects int **.
8. String Array Modification
Modifying string literals through double pointers is undefined.
c
char *arr[] = {"Hello", "World"}; // Pointers to literals arr[0][0] = 'h'; // BUG: modifying read-only memory Solution: Use const char *arr[] or char arr[][20].
9. Function Pointer with Double Pointer
Double pointers cannot be used as function pointers directly.
c
int (**funcPtr)(int, int); // Pointer to function pointer
Solution: Use correct syntax for function pointers.
10. Memory Fragmentation
Frequent allocation/deallocation with double pointers can fragment memory.
Solution: Consider using contiguous 1D arrays with manual indexing for performance-critical code.
Summary & Best Practices
Double pointers are essential for dynamic memory allocation, passing pointers to functions, and managing complex data structures. They require careful handling to avoid memory leaks and undefined behavior.
Key Takeaways
- Double pointer stores the address of a single pointer.
**ptrdereferences to the original value.- Size of double pointer equals size of any other pointer.
- Used for dynamic 2D arrays, array of strings, and pointer modification in functions.
- Multilevel pointers (
***,****) extend the concept. - Always free dynamically allocated memory in reverse order.
Best Practices
- Check for NULL before dereferencing double pointers.
- Always check malloc/calloc return values.
- Free memory in reverse order (rows first, then pointer array).
- Use
sizeof(int *)when allocating pointer arrays. - Pass address of pointer (
&ptr) when function expectsint **. - Use
constfor read-only string arrays to prevent modification. - Document ownership of dynamically allocated memory.
- Consider contiguous 1D arrays for performance-critical code.
- Set pointers to NULL after freeing to avoid dangling pointers.
- Test with edge cases: empty database, single element, maximum size.
By mastering double pointers, you’ll efficiently manage dynamic data structures and write robust C programs.



