Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 13

Avoiding Common Pointer Pitfalls

Recommended resources

Introduction

In our last lesson, you gained a powerful new skill: using the GNU Debugger (GDB) to peer inside a running program and observe its state. You learned to navigate code, inspect variables, and examine raw memory. Today, we put that skill to its intended use by exploring the most common sources of memory-related bugs in C. We'll focus on three notorious pointer pitfalls: uninitialized pointers, null pointers, and dangling pointers.

This lesson will teach you not only to identify these issues but, more importantly, to adopt programming habits that prevent them from occurring in the first place. Understanding these pitfalls is a significant step toward C mastery, as it shifts your perspective from merely writing code that works to writing code that is robust, safe, and predictable—a non-negotiable requirement for complex projects like game engines or emulators.


The Landscape of Pointer Errors

Most severe pointer errors manifest as "undefined behavior." This can mean a clean crash (a segmentation fault), which is often the best-case scenario, or, far worse, silent memory corruption that leads to bizarre bugs much later in the program's execution. Your GDB skills are your primary tool for investigating crashes, but today we focus on the root causes.

Let's begin by distinguishing between an uninitialized pointer and a null pointer.

C_81 Null Pointer in C | C Programming Tutorials

In this video, Jenny's Lectures CS IT clearly contrasts an uninitialized (or "wild") pointer with a well-defined null pointer.

Watch the first three minutes of the video, from the start to the end of the section. Pay close attention to the explanation of why an uninitialized pointer is dangerous—it holds a garbage value, and dereferencing it can lead anywhere.


Pitfall 1: Uninitialized (Wild) Pointers

As the video explained, an uninitialized pointer is one that has been declared but not yet assigned a specific memory address.
int *p_score;
This pointer p_score doesn't point anywhere meaningful. It contains a garbage value leftover from whatever was in that memory location previously. Dereferencing it, as in *p_score = 100;, is an attempt to write to a random memory address. This could crash the program or, more sinisterly, overwrite a value used by another part of your program, leading to maddeningly difficult-to-find bugs.

This is often called a wild pointer.

This diagram visually contrasts two types of pointer errors. On the right, a "Wild Pointer" is shown as an uninitialized pointer that can point to any random memory location, leading to undefined behavior when dereferenced.

The defense against uninitialized pointers is simple yet crucial.

Top 20 C pointer mistakes and how to fix them

This article from A Coder's Journey provides concise, practical examples of common pointer errors.

Read through Mistake #2 and Mistake #3. The key takeaway is to always give a pointer a known value upon creation.

The Rule: Always initialize your pointers. If you don't have a valid memory address to assign at the time of declaration, initialize the pointer to NULL.

Incorrect: int *p_health;
Correct: int *p_health = NULL; or int *p_health = &some_variable;

This simple habit transforms a wild pointer problem into a null pointer problem, which is much easier to manage.


Pitfall 2: Null Pointer Dereference

A null pointer is a pointer that is intentionally set to not point to any valid object. In C, this is represented by the macro NULL, which is typically defined as (void*)0. Unlike a wild pointer with a random address, a NULL pointer has a consistent, invalid address.

This is a feature, not just a bug. It's a standard way to signal intent:

  • As a return value from a function that may fail to allocate memory (e.g., malloc).
  • As an initial value for a pointer that will be assigned a real address later.
  • To mark the end of a data structure, like a linked list (a topic we'll cover in depth later).

The pitfall is attempting to dereference a null pointer (*my_null_ptr). On most modern operating systems, this causes an immediate and predictable crash: a segmentation fault. While a crash is never ideal, this is far better than silent corruption because it points you directly to the offending line of code.

With the debugging skills from our last lesson, finding this is straightforward. When you run the program in GDB and it crashes, you can print the pointer's value:
(gdb) print my_null_ptr
$1 = 0x0
Seeing 0x0 is the classic signature of a NULL pointer. The fix is not to stop using NULL, but to check for it before dereferencing.

// Incorrect - assumes malloc succeeded
int *inventory_counts = malloc(10 * sizeof(int));
inventory_counts[0] = 5; // Crash if malloc returned NULL!

// Correct - defensive check
int *inventory_counts = malloc(10 * sizeof(int));
if (inventory_counts == NULL) {
    // Handle memory allocation failure
    fprintf(stderr, "Failed to allocate memory for inventory!\n");
    exit(EXIT_FAILURE);
}
// It's now safe to use the pointer
inventory_counts[0] = 5;

This pattern of checking for NULL is fundamental to robust C programming.

C_81 Null Pointer in C | C Programming Tutorials

Jenny's Lectures CS IT now covers the practical application of null pointers, specifically in the context of error handling with dynamic memory allocation.

Watch the segment from this timestamp. This explains why functions like malloc return NULL on failure and how you should write your code to handle that possibility.


Pitfall 3: Dangling Pointers

A dangling pointer is perhaps the most subtle of the three. It's a pointer that was valid but now points to memory that has been deallocated or is no longer in scope. The pointer variable itself still holds the old address, but the memory it points to is no longer yours to use.

There are two common ways this happens:

1. Using a Pointer After free()

When you dynamically allocate memory with malloc, you are responsible for releasing it with free(). A common mistake is to think that free(ptr) also clears ptr. It does not. The ptr variable still holds the address of the now-freed memory.

Dangling Pointers | C Programming Tutorial

This video from Portfolio Courses gives an excellent demonstration of what a dangling pointer is and the single most effective way to prevent it.

First, watch the section from this timestamp. Notice how the program is still able to write to the memory after it has been freed, a classic example of memory corruption. Next, watch the crucial solution presented from this part. This introduces the best practice for mitigating this risk.

As the video demonstrates, the solution is simple but powerful: after calling free(), immediately set the pointer to NULL.

Incorrect:

free(p_enemy_data);
// p_enemy_data is now dangling
// ...
if (p_enemy_data != NULL) { // This check might still pass!
    // Catastrophic bug
}

Correct:

free(p_enemy_data);
p_enemy_data = NULL; // No longer dangling
// ...
if (p_enemy_data != NULL) { // This check correctly fails
    // Code here will not be executed
}

This practice transforms a difficult dangling pointer bug into an easy-to-diagnose null pointer dereference.

2. Returning a Pointer to a Local Variable

A function's local variables exist on the stack and are destroyed when the function returns. Returning a pointer to a local variable is a classic error that creates a dangling pointer.

int* create_score() {
    int score = 100;
    return &score; // BIG MISTAKE! 'score' is destroyed on return
}

void main() {
    int *p_score = create_score();
    printf("%d\n", *p_score); // Undefined behavior!
}

The memory location that p_score points to is no longer valid. It might temporarily hold the value 100, but it's likely to be overwritten by subsequent function calls, leading to garbage data.

This topic is covered very well in the article on cprogramming.com.

Debugging Segmentation Faults and Pointer Problems

This article provides a fantastic overview of what causes segmentation faults and how to debug them. It connects directly to our pointer topics.

Please read the following three sections: Start with the introductory section, What is a segmentation fault?, which lists our three pitfalls as the primary causes. Read the section on Uninitialized Pointers. This reinforces the practice of initializing pointers to NULL. Finally, read the crucial section Dereferencing Freed Memory. This covers both the free() case and the problem of returning a pointer to a local variable.


Conclusion

Today we've taken a deep dive into the dark corners of C pointers. While the behavior of uninitialized, null, and dangling pointers can seem complex, the strategies to avoid them are straightforward and form the bedrock of disciplined C programming. The debugging skills you acquired in the last lesson are your safety net, allowing you to effectively diagnose these issues when they inevitably occur.

Key Takeaways:

  • Uninitialized (Wild) Pointers: Hold garbage addresses. Fix: Always initialize pointers, using NULL if no valid address is available.
  • Null Pointers: Are intentionally invalid. Dereferencing them causes a crash. Fix: Always check for NULL before using a pointer that might be null.
  • Dangling Pointers: Point to memory that is no longer valid (either freed or out of scope). Fix: After calling free(ptr), immediately set ptr = NULL. Never return the address of a local variable from a function.

In our next lesson, we will build on this solid foundation of pointer safety to explore one of C's most powerful features: the deep, interchangeable relationship between pointers and arrays. You'll learn how to use pointer arithmetic to navigate arrays efficiently, a technique that is fundamental to high-performance C code.