Common pointer bugs
Identify and understand the most dangerous pointer bugs in C — null dereference, dangling pointers, double free, wild pointers, and off-by-one out-of-bounds accesses.
- Identify null pointer dereference and explain the crash it causes
- Explain what a dangling pointer is and how it arises
- Describe the double free bug and why it corrupts the heap
- Recognise wild (uninitialised) pointers and why they are dangerous
Pointer bugs are the most common source of crashes and security vulnerabilities in C programs. Unlike many bugs that cause wrong output, pointer bugs often cause undefined behaviour — the program might appear to work until a specific memory layout change causes it to corrupt data silently or crash unpredictably. This lesson catalogs the main categories so you can recognise them.
1. Null pointer dereference
Dereferencing a NULL pointer is always a crash (segmentation fault):
int *p = NULL;
*p = 42; /* CRASH: cannot write to address 0 */The most common cause: a function returns NULL on failure and the caller does not check:
int *arr = malloc(100 * sizeof(int));
arr[0] = 1; /* CRASH if malloc returned NULL (out of memory) */Fix: always check the return value of functions that can return NULL:
int *arr = malloc(100 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "malloc failed\n");
exit(1);
}
arr[0] = 1; /* safe */2. Dangling pointer
A dangling pointer points to memory that has been freed or gone out of scope:
/* Dangling pointer after free */
int *p = malloc(sizeof(int));
*p = 42;
free(p);
*p = 100; /* UNDEFINED BEHAVIOUR: memory has been returned to allocator *//* Dangling pointer to a local variable */
int *bad_pointer(void) {
int local = 42;
return &local; /* WRONG: local is gone when function returns */
}After free(p), the allocator may reuse that memory for something else. Writing to it can corrupt an unrelated data structure. Accessing freed memory is undefined behaviour and a security vulnerability.
Fix: set pointers to NULL after freeing:
free(p);
p = NULL;
/* Now *p = 100 would crash immediately (null dereference) rather than silently corrupt */3. Double free
Calling free on the same pointer twice corrupts the allocator's internal bookkeeping:
int *p = malloc(sizeof(int));
free(p);
free(p); /* UNDEFINED BEHAVIOUR: heap corruption */The allocator uses the freed memory block for its own metadata. Freeing it again overwrites that metadata with garbage, often causing the next malloc or free call to crash in a completely unrelated part of the program.
The double free bug is particularly nasty to debug because the crash happens far from the original mistake. Tools like valgrind catch it immediately.
Fix: set to NULL after free (freeing NULL is a safe no-op):
free(p);
p = NULL;
free(p); /* safe -- free(NULL) is defined to do nothing */4. Wild (uninitialised) pointer
A pointer that has never been assigned holds whatever bytes were in that stack location:
int *p; /* not initialised -- could hold any value */
*p = 42; /* UNDEFINED BEHAVIOUR: writes to a random address */The address could be anything: another variable's memory, the stack's return addresses, or an unmapped region. Writes can silently corrupt data; the crash (if any) comes later in an unrelated location.
Fix: always initialise pointers. Either to a valid address or to NULL:
int *p = NULL; /* explicitly "points nowhere" */
/* or */
int x = 0;
int *p = &x;5. Out-of-bounds access
Writing past the end of an array can overwrite the return address on the stack:
char buf[8];
strcpy(buf, "This string is too long for the buffer"); /* buffer overflow */This is the basis of classic stack-smashing exploits. The overflow overwrites the saved return address with an attacker-supplied value, redirecting execution when the function returns.
Buffer overflows are security vulnerabilities. Even on modern systems with stack canaries and ASLR, buffer overflows are among the most exploited bug classes. Use strncpy, snprintf, and size-checked functions everywhere user-supplied data interacts with fixed-size buffers.
6. Pointer aliasing bugs
When two pointers point to overlapping memory, modifying through one can produce unexpected results through the other:
void bad_copy(int *dst, int *src, int n) {
for (int i = 0; i < n; i++) {
dst[i] = src[i]; /* if dst and src overlap, src[i] may have been modified */
}
}Use memmove instead of memcpy when source and destination may overlap — memmove handles this correctly.
Debugging pointer bugs
- Valgrind: catches null/dangling pointer reads, double free, and out-of-bounds heap accesses. Run with
valgrind --leak-check=full ./program. - AddressSanitizer: compile with
-fsanitize=address(gcc or clang). Catches stack and heap out-of-bounds, use-after-free, and double free at runtime, with precise error messages. - NULL after free: the simplest discipline that prevents half of these bugs.
Where to go next
Next: the strlen and strcpy lab — you will implement your own versions of strlen and strcpy from scratch using raw pointer manipulation, which solidifies everything in this module.
const pointers
Learn how const interacts with pointers in C — the distinction between pointer to const data, const pointer, and const pointer to const data.
Lab: Implement strlen and strcpy from scratch
Build your own versions of strlen, strcpy, strcmp, and strcat using raw pointer manipulation — cementing everything from the Pointers module.