Pointers in C: Complete Guide with Examples
Pointers in C: A Comprehensive Tutorial
A pointer is a variable that stores the memory address of another variable. Instead of holding a direct value, it holds the address where the value is stored in memory. Pointers are the backbone of low-level memory manipulation in C, enabling dynamic memory allocation, efficient array processing, and complex data structures.
This tutorial provides an in-depth exploration of pointers in C: syntax, initialization, dereferencing, pointer types, arithmetic, function pointers, multilevel pointers, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is a Pointer?
A pointer is a variable that stores a memory address. It “points to” the location where data is stored rather than storing the data itself.
Example:
c
int var = 10; // Normal variable: stores value 10 int *ptr = &var; // Pointer variable: stores address of var
Visual representation:
text
var (value: 10) ptr (value: 0x7ffc...) +--------+ +----------------+ | 10 | <------ | 0x7ffc... | +--------+ +----------------+ Address: 0x7ffc... Address: 0x7ffd...
Why Pointers Matter
- Dynamic memory allocation: Allocate/deallocate memory at runtime (
malloc,free). - Efficient array processing: Access array elements without copying.
- Function parameter passing: Modify original variables.
- Complex data structures: Linked lists, trees, graphs.
- Low-level programming: Direct memory manipulation.
- Function pointers: Implement callbacks and dynamic dispatch.
Key Concepts
| Term | Description |
|---|---|
Address operator & | Returns the memory address of a variable |
Dereference operator * | Accesses the value at a memory address |
| Null pointer | Pointer that points to nothing (NULL) |
| Pointer arithmetic | Operations on pointers (increment, decrement, etc.) |
| Void pointer | Generic pointer that can point to any type |
Syntax & Core Mechanics
Declaration
Syntax:
c
data_type *pointer_name;
Examples:
c
int *intPtr; // Pointer to integer char *charPtr; // Pointer to character double *doublePtr; // Pointer to double
Initialization
A pointer is initialized by assigning it the address of a variable using the address operator (&).
c
#include <stdio.h> int main(void) { int var = 10; int *ptr = &var; // Initialize with address of var printf("Value of var: %d\n", var); printf("Address of var: %p\n", (void*)&var); printf("Value of ptr: %p\n", (void*)ptr); printf("Address of ptr: %p\n", (void*)&ptr); return 0; } Expected output (addresses will vary):
text
Value of var: 10 Address of var: 0x7ffc1234 Value of ptr: 0x7ffc1234 Address of ptr: 0x7ffc1238
Dereferencing
Use the dereference operator (*) to access the value stored at the address.
c
#include <stdio.h> int main(void) { int var = 10; int *ptr = &var; printf("var = %d\n", var); printf("*ptr = %d\n", *ptr); // Dereference /* Modify through pointer */ *ptr = 20; printf("After *ptr = 20: var = %d\n", var); return 0; } Expected output:
text
var = 10 *ptr = 10 After *ptr = 20: var = 20
Size of Pointers
Pointer size depends on the system architecture, not the data type:
- 32-bit systems: typically 4 bytes
- 64-bit systems: typically 8 bytes
c
#include <stdio.h> int main(void) { int *intPtr; char *charPtr; double *doublePtr; void *voidPtr; printf("sizeof(int*): %zu\n", sizeof(intPtr)); printf("sizeof(char*): %zu\n", sizeof(charPtr)); printf("sizeof(double*): %zu\n", sizeof(doublePtr)); printf("sizeof(void*): %zu\n", sizeof(voidPtr)); return 0; } Expected output (64-bit system):
text
sizeof(int*): 8 sizeof(char*): 8 sizeof(double*): 8 sizeof(void*): 8
Types of Pointers
1. NULL Pointer
A pointer that does not point to any valid memory location.
c
#include <stdio.h> #include <stddef.h> int main(void) { int *ptr = NULL; // Null pointer if (ptr == NULL) { printf("ptr is NULL\n"); } /* Safe to check before dereferencing */ if (ptr != NULL) { printf("%d\n", *ptr); } return 0; } Expected output:
text
ptr is NULL
2. Void Pointer
A generic pointer that can store the address of any data type. Must be cast before dereferencing.
c
#include <stdio.h> int main(void) { int a = 42; double b = 3.14; char c = 'Z'; void *ptr; ptr = &a; printf("a via void pointer: %d\n", *(int*)ptr); ptr = &b; printf("b via void pointer: %.2f\n", *(double*)ptr); ptr = &c; printf("c via void pointer: %c\n", *(char*)ptr); return 0; } Expected output:
text
a via void pointer: 42 b via void pointer: 3.14 c via void pointer: Z
3. Wild Pointer
An uninitialized pointer that contains garbage.
c
int *ptr; // Wild pointer: uninitialized /* Dereferencing this is undefined behavior */
Solution: Always initialize pointers to NULL or a valid address.
4. Dangling Pointer
A pointer that refers to memory that has been freed or deallocated.
c
#include <stdio.h> #include <stdlib.h> int main(void) { int *ptr = malloc(sizeof(int)); *ptr = 42; free(ptr); // ptr is now dangling printf("Memory freed\n"); ptr = NULL; // Remove dangling pointer return 0; } Expected output:
text
Memory freed
Pointer Arithmetic
Only a limited set of operations are allowed on pointers.
Increment and Decrement
c
#include <stdio.h> int main(void) { int arr[] = {10, 20, 30, 40, 50}; int *ptr = arr; printf("*ptr = %d\n", *ptr); ptr++; printf("*ptr after ++ = %d\n", *ptr); ptr--; printf("*ptr after -- = %d\n", *ptr); return 0; } Expected output:
text
*ptr = 10 *ptr after ++ = 20 *ptr after -- = 10
Addition and Subtraction of Integer
c
#include <stdio.h> int main(void) { int arr[] = {10, 20, 30, 40, 50}; int *ptr = arr; printf("*(ptr + 2) = %d\n", *(ptr + 2)); // 30 printf("*(ptr + 4) = %d\n", *(ptr + 4)); // 50 ptr += 3; printf("*ptr after += 3 = %d\n", *ptr); // 40 ptr -= 2; printf("*ptr after -= 2 = %d\n", *ptr); // 20 return 0; } Expected output:
text
*(ptr + 2) = 30 *(ptr + 4) = 50 *ptr after += 3 = 40 *ptr after -= 2 = 20
Subtracting Two Pointers
c
#include <stdio.h> int main(void) { int arr[] = {10, 20, 30, 40, 50}; int *p1 = &arr[1]; int *p2 = &arr[4]; ptrdiff_t difference = p2 - p1; printf("p2 - p1 = %td\n", difference); // 3 return 0; } Expected output:
text
p2 - p1 = 3
Comparing Pointers
c
#include <stdio.h> int main(void) { int arr[] = {10, 20, 30}; int *p1 = &arr[0]; int *p2 = &arr[2]; if (p1 < p2) { printf("p1 points to earlier element\n"); } if (p1 == &arr[0]) { printf("p1 equals &arr[0]\n"); } return 0; } Expected output:
text
p1 points to earlier element p1 equals &arr[0]
Constant Pointers
Constant Pointer (Pointer Cannot Change)
c
int *const ptr = &a; /* ptr = &b; // Error: ptr is constant */
Pointer to Constant (Value Cannot Change)
c
const int *ptr = &a; /* *ptr = 10; // Error: value is constant */
Constant Pointer to Constant
c
const int *const ptr = &a; /* ptr = &b; // Error */ /* *ptr = 10; // Error */
Function Pointers
Pointers that store the address of functions.
c
#include <stdio.h> int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main(void) { int (*operation)(int, int); // Function pointer declaration operation = &add; printf("add(10, 5) = %d\n", operation(10, 5)); operation = &subtract; printf("subtract(10, 5) = %d\n", operation(10, 5)); return 0; } Expected output:
text
add(10, 5) = 15 subtract(10, 5) = 5
Multilevel Pointers
Pointers that store the address of other pointers.
c
#include <stdio.h> int main(void) { int var = 10; int *ptr1 = &var; // Pointer to int int **ptr2 = &ptr1; // Pointer to pointer int ***ptr3 = &ptr2; // Triple pointer printf("var: %d\n", var); printf("*ptr1: %d\n", *ptr1); printf("**ptr2: %d\n", **ptr2); printf("***ptr3: %d\n", ***ptr3); return 0; } Expected output:
text
var: 10 *ptr1: 10 **ptr2: 10 ***ptr3: 10
Full Practical Code Example with Explanation
This program demonstrates pointers in a realistic scenario: a dynamic array manager with memory allocation.
Program: Dynamic Array Manager
c
/** * dynamic_array_manager.c * Demonstrates pointers: dynamic allocation, pointer arithmetic, * function pointers, and memory management. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes */ int *createArray(int size, int initialValue); void displayArray(const int *arr, int size); int findMax(const int *arr, int size); int findMin(const int *arr, int size); void sortArray(int *arr, int size, int (*compare)(int, int)); int ascending(int a, int b); int descending(int a, int b); int readInteger(const char *prompt, int *outValue); int main(void) { int *arr = NULL; int size = 0; int choice; char buffer[MAX_LINE_LEN]; printf("=== Dynamic Array Manager ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Create Array\n"); printf("2. Display Array\n"); printf("3. Find Maximum\n"); printf("4. Find Minimum\n"); printf("5. Sort Ascending\n"); printf("6. Sort Descending\n"); printf("7. Free Array and Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: { if (arr != NULL) { free(arr); arr = NULL; } int newSize, initialValue; if (readInteger("Enter array size: ", &newSize) != 0 || readInteger("Enter initial value: ", &initialValue) != 0) { fprintf(stderr, "Invalid input.\n"); break; } if (newSize <= 0) { printf("Size must be positive.\n"); break; } arr = createArray(newSize, initialValue); size = newSize; printf("Array created with %d elements.\n", size); break; } case 2: if (arr != NULL) { displayArray(arr, size); } else { printf("No array created yet.\n"); } break; case 3: if (arr != NULL) { int max = findMax(arr, size); printf("Maximum: %d\n", max); } else { printf("No array created yet.\n"); } break; case 4: if (arr != NULL) { int min = findMin(arr, size); printf("Minimum: %d\n", min); } else { printf("No array created yet.\n"); } break; case 5: if (arr != NULL) { sortArray(arr, size, ascending); printf("Array sorted ascending.\n"); } else { printf("No array created yet.\n"); } break; case 6: if (arr != NULL) { sortArray(arr, size, descending); printf("Array sorted descending.\n"); } else { printf("No array created yet.\n"); } break; case 7: if (arr != NULL) { free(arr); arr = NULL; printf("Array freed. Exiting.\n"); } else { printf("Exiting.\n"); } return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } /* Cleanup (should not reach here due to exit in case 7) */ if (arr != NULL) { free(arr); } return EXIT_SUCCESS; } /* Creates an array using dynamic memory allocation */ int *createArray(int size, int initialValue) { int *arr = malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } for (int i = 0; i < size; i++) { arr[i] = initialValue; } return arr; } /* Displays array elements using pointer arithmetic */ void displayArray(const int *arr, int size) { printf("Array elements: "); for (const int *p = arr; p < arr + size; p++) { printf("%d ", *p); } printf("\n"); } /* Finds maximum using pointer traversal */ int findMax(const int *arr, int size) { if (size <= 0) return 0; int max = arr[0]; for (const int *p = arr + 1; p < arr + size; p++) { if (*p > max) { max = *p; } } return max; } /* Finds minimum using pointer traversal */ int findMin(const int *arr, int size) { if (size <= 0) return 0; int min = arr[0]; for (const int *p = arr + 1; p < arr + size; p++) { if (*p < min) { min = *p; } } return min; } /* Sorts array using function pointer for comparison */ void sortArray(int *arr, int size, int (*compare)(int, int)) { for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { if (compare(arr[j], arr[j + 1]) > 0) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } /* Comparison functions */ int ascending(int a, int b) { return a - b; } int descending(int a, int b) { return b - a; } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; } Explanation of Pointer Usage
- Dynamic allocation:
mallocreturns a pointer to allocated memory. - Pointer arithmetic: Traversal using
p++andp < arr + size. - Function pointers:
sortArrayaccepts a comparison function. - Memory management:
freedeallocates memory; pointer set toNULL. - Null checks: Prevents dereferencing null pointers.
Expected Output (sample run)
text
=== Dynamic Array Manager === --- Menu --- 1. Create Array 2. Display Array 3. Find Maximum 4. Find Minimum 5. Sort Ascending 6. Sort Descending 7. Free Array and Exit Enter choice: 1 Enter array size: 5 Enter initial value: 42 Array created with 5 elements. --- Menu --- ... Enter choice: 2 Array elements: 42 42 42 42 42 --- Menu --- ... Enter choice: 3 Maximum: 42 --- Menu --- ... Enter choice: 7 Array freed. Exiting.
Common Pitfalls, Edge Cases & Memory Considerations
1. Dereferencing NULL Pointer
Dereferencing a NULL pointer causes a segmentation fault.
c
int *ptr = NULL; printf("%d\n", *ptr); // BUG: crash Solution: Always check if (ptr != NULL) before dereferencing.
2. Wild Pointer (Uninitialized)
Using an uninitialized pointer is undefined behavior.
c
int *ptr; // Wild pointer *ptr = 10; // BUG: undefined behavior
Solution: Initialize to NULL or a valid address: int *ptr = NULL;
3. Dangling Pointer
Accessing freed memory is undefined behavior.
c
int *ptr = malloc(sizeof(int)); free(ptr); *ptr = 10; // BUG: dangling pointer
Solution: Set pointer to NULL after free: ptr = NULL;
4. Memory Leaks
Forgetting to free allocated memory causes leaks.
c
int *ptr = malloc(100 * sizeof(int)); /* Missing free(ptr); */
Solution: Always free dynamically allocated memory.
5. Double Free
Calling free twice on the same pointer corrupts the heap.
c
int *ptr = malloc(sizeof(int)); free(ptr); free(ptr); // BUG: double free
Solution: Set pointer to NULL after freeing: free(ptr); ptr = NULL;
6. Pointer Arithmetic Out of Bounds
Accessing beyond array bounds is undefined behavior.
c
int arr[5]; int *p = arr + 5; // One past the end *p = 10; // BUG: out of bounds
Solution: Keep pointers within [arr, arr + size - 1].
7. Wrong Pointer Type Casting
Casting pointers incorrectly can cause alignment issues and undefined behavior.
c
char c = 'A'; int *ptr = (int*)&c; // Alignment issue
Solution: Use correct pointer types; avoid unnecessary casts.
8. Function Pointer Type Mismatch
Using a function pointer with wrong signature causes undefined behavior.
c
int (*fptr)(int, int) = &someFunction; // Must match exactly
Solution: Ensure the function pointer signature matches the function.
9. Void Pointer Dereferencing
Cannot dereference a void * without casting.
c
void *ptr = &someInt; printf("%d\n", *ptr); // BUG: cannot dereference void* Solution: Cast first: *(int*)ptr.
10. Memory Considerations
- Pointers themselves occupy memory (8 bytes on 64-bit).
- Dynamically allocated memory must be freed.
- Large allocations can fail; always check
mallocreturn value. - Memory fragmentation can occur with frequent allocate/free cycles.
Summary & Best Practices
Pointers are a powerful feature of C that enable dynamic memory allocation, efficient array processing, and complex data structures. They require careful handling to avoid crashes, leaks, and undefined behavior.
Key Takeaways
- Pointers store memory addresses.
- Use
&to get an address,*to dereference. - Pointer size is architecture-dependent (4 or 8 bytes).
- Types: NULL, void, wild, dangling.
- Pointer arithmetic advances by the size of the pointed-to type.
- Function pointers enable callbacks.
- Multilevel pointers (
**,***) store addresses of pointers.
Best Practices
- Always initialize pointers to
NULLor a valid address. - Check for
NULLbefore dereferencing. - Set pointers to
NULLafterfreeto avoid dangling pointers. - Free dynamically allocated memory to prevent leaks.
- Use correct pointer types and avoid unnecessary casts.
- Keep pointer arithmetic within bounds.
- Use
constfor read-only pointer parameters. - Document pointer ownership (who allocates, who frees).
- Use
sizeoffor allocation sizes to avoid mistakes. - Enable compiler warnings (
-Wall -Wextra) to catch pointer issues.
By mastering pointers, you’ll unlock the full power of C for systems programming, dynamic data structures, and efficient memory management.



