CoreTechED

Loading

Pointer to Pointer (Double Pointer) in C: Complete Guide

Learn C double pointers: declaration, dereferencing, dynamic 2D arrays, array of strings, and multilevel pointers. Master memory management.

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

AspectSingle PointerDouble Pointer
Declarationint *ptrint **ptr
Points toVariablePointer to variable
Dereference*ptr (value)**ptr (value), *ptr (address)
Common useDirect data accessDynamic 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

  1. char **database: Array of string pointers.
  2. addString(&database, &count, ...): Passes address of database to modify it.
  3. realloc: Resizes the pointer array dynamically.
  4. Sorting: Swaps pointers (not strings) for efficiency.
  5. 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.
  • **ptr dereferences 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

  1. Check for NULL before dereferencing double pointers.
  2. Always check malloc/calloc return values.
  3. Free memory in reverse order (rows first, then pointer array).
  4. Use sizeof(int *) when allocating pointer arrays.
  5. Pass address of pointer (&ptr) when function expects int **.
  6. Use const for read-only string arrays to prevent modification.
  7. Document ownership of dynamically allocated memory.
  8. Consider contiguous 1D arrays for performance-critical code.
  9. Set pointers to NULL after freeing to avoid dangling pointers.
  10. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *