Skip to main content

Welcome to a Grasp lesson

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

Lesson 71

Pointer Safety: Preventing Dereference Errors

Recommended resources

Hello! Welcome to the first lesson in our new module, "Debugging, Best Practices, and Interview Patterns."

In our previous module, you mastered the art of analyzing and refactoring code to comply with MISRA-C standards—a critical skill for ensuring functional safety. We saw how many MISRA rules are designed to prevent specific, dangerous pointer behaviors. Today, we zoom in on the root causes of those dangers. Our goal is to identify and prevent null pointer dereferences, uninitialized pointers, and dangling pointers.

These three issues are the most common and notorious sources of bugs in C. Understanding them deeply is not just about passing technical interviews; it's about applying a foundational layer of defensive programming that is essential in the safety-critical environments you work in, such as AUTOSAR and ADAS development. We'll define each bug, explore why it's so problematic, and establish the primary technique to prevent it.

1. The Uninitialized Pointer: A Time Bomb

An uninitialized pointer is one that has been declared but has not been assigned a specific memory address. It holds a "garbage" value—whatever bits happened to be in that memory location when it was allocated for the pointer.

int *p_data; // p_data is uninitialized. It points to a random, invalid address.
*p_data = 100; // UNDEFINED BEHAVIOR!

Dereferencing an uninitialized pointer leads to undefined behavior. Your program might crash immediately, or worse, it might corrupt a random piece of memory belonging to another part of your application. This can lead to subtle bugs that are incredibly difficult to trace back to their source—a nightmare scenario in any system, but especially in one governed by ISO 26262.

Top 20 C pointer mistakes and how to fix them

The following article, 'Top 20 C pointer mistakes and how to fix them' from A Coder's Journey, clearly demonstrates the problem of using uninitialized pointers and the resulting behavior.

Please read 'Mistake # 2: Using uninitialized pointers'. The example shows how accessing an uninitialized pointer leads to a runtime crash and explains the debug markers used by some compilers.

Prevention: Always Initialize

The prevention is simple and absolute: always initialize a pointer before you use it. If you don't have a valid memory address to assign at the time of declaration, initialize it to NULL.

// Good: Initialized with a valid address
int value = 10;
int *p_value = &value;

// Also good: Initialized to NULL when no address is ready
int *p_later;
p_later = NULL; 

Initializing to NULL doesn't make the pointer usable, but it gives it a known, safe state. This prevents it from pointing to a random location and transforms a potential "undefined behavior" bug into a predictable "null pointer dereference," which we'll discuss next.

Best practices to safely navigate pointers in C/C++

The article 'Best practices to safely navigate pointers in C/C++' from Embedded.com reinforces this fundamental rule.

Read 'Best Practice #1 – Always Initialize Pointers'. This section confirms that initializing to NULL is the standard way to give a pointer a safe, known value upon declaration.


2. The Null Pointer Dereference: A Controlled Crash

A null pointer dereference occurs when you attempt to read from or write to the memory address represented by NULL.

int *p_data = NULL; // p_data is explicitly null.
*p_data = 100;      // CRASH! (on most systems)

On most modern operating systems, the memory region around address 0 is protected. Attempting to access it results in a hardware exception, which the OS catches and typically terminates your program with a segmentation fault.

While a crash is undesirable, it is vastly preferable to the silent memory corruption caused by an uninitialized pointer. A null pointer dereference provides an immediate, loud, and clear signal that something has gone wrong, and the source of the error is exactly where the crash occurred.

Prevention: Check Before You Dereference

The defensive pattern against this is straightforward: always check if a pointer is NULL before dereferencing it.

Best practices to safely navigate pointers in C/C++

Let's return to the Embedded.com article for the canonical solution.

Read 'Best Practice #2 – Check for NULL Before Dereferencing'. This section shows the classic if (ptr != NULL) guard clause that is fundamental to robust C programming.

This pattern is a form of contract. A function that accepts a pointer should validate it before use. You've likely seen this countless times in the AUTOSAR APIs you work with, where a function will check a pointer to a COM message buffer before attempting to serialize or deserialize data.

void process_data(int *p_data) {
    if (p_data != NULL) {
        // Safe to proceed
        *p_data = calculate_new_value(*p_data);
    } else {
        // Handle the error: log, return an error code, etc.
    }
}

3. The Dangling Pointer: Corrupting the Future

A dangling pointer is one that continues to point to a memory location that has been deallocated (e.g., via free()) or has gone out of scope (e.g., a local variable in a function that has returned).

Dangling Pointer Illustration
This diagram illustrates a dangling pointer. The pointer `p` still holds the address `1300`, but the memory at that location has been deallocated (indicated by the red 'X'), making the pointer invalid.

The primary danger is that the memory manager is now free to re-allocate that same memory block for a completely different purpose. Your dangling pointer still points to it, but it now holds new, unrelated data.

  • Writing through the dangling pointer will corrupt this new data, causing bizarre and seemingly impossible bugs in a completely separate part of the system.
  • Reading from it will yield garbage or, deceptively, data that looks valid for a while.
Dangling Pointer in C Example
This code snippet shows the most common way a dangling pointer is created: a pointer `ptr` is used to point to memory allocated by `malloc`. After `free(ptr)` is called, the memory is deallocated, but `ptr` itself still holds the old address, thus becoming a dangling pointer.

To see this problem and its solution in action, let's watch a short, clear tutorial.

Dangling Pointers | C Programming Tutorial

This video from Portfolio Courses provides a concise explanation of dangling pointers, why they are a problem, and the standard practice to prevent them.

Please watch the following two segments: Explanation and Demonstration (from 01:34 to 04:04): This part shows how a pointer becomes dangling after free() is called and demonstrates that it's still possible to write to that memory, causing potential corruption. The Prevention Technique (from 04:04 to 05:11): This segment introduces the best practice of setting the pointer to NULL immediately after freeing it.

Prevention: Null After Free

As the video explained, the most effective way to defuse a dangling pointer is to set it to NULL immediately after the memory it points to has been deallocated.

free(p_data);
p_data = NULL; // Prevents the pointer from dangling.

This simple action transforms a dangerous dangling pointer situation into a safe and predictable null pointer. If you accidentally try to use p_data again, you won't be corrupting unknown memory; you will get a clean, immediate null pointer dereference crash. This turns a subtle, latent bug into an obvious, immediate one.

This principle is reinforced in multiple best-practice guides.

Best practices to safely navigate pointers in C/C++

Let's look at two more sources that confirm this critical practice.

From the Embedded.com article, read 'Strategy #2 – Set Pointers to NULL After Freeing'.

Top 20 C pointer mistakes and how to fix them

And again from 'Top 20 C pointer mistakes'.

Read the 'Fix' for 'Mistake # 7: Dereferencing the value of a pointer after it has been freed'. It explicitly states this best practice.

Test your understanding!

The following function is a minefield of pointer errors. Review the code and identify the line number and the specific type of error for all three pitfalls we've discussed: uninitialized pointer, null pointer dereference, and dangling pointer.

#include <stdio.h>
#include <stdlib.h>

char* create_error_message(int error_code) {
    char message[50]; // Local variable on the stack
    sprintf(message, "Error code: %d", error_code);
    return message; // Line 7
}

void process_data(int *data_block) {
    int *local_ptr; // Line 11
    
    if (data_block != NULL) {
        *local_ptr = *data_block; // Line 14
        printf("Data processed: %d\n", *local_ptr);
    }
}

int main() {
    int *p1 = (int*)malloc(sizeof(int));
    *p1 = 123;
    free(p1);
    
    int *p2 = NULL;
    
    process_data(p1); // Line 26
    
    printf("Value of p2 is %d\n", *p2); // Line 28

    return 0;
}
Show answer

Here are the errors in the code:

  1. Line 14: Use of an uninitialized pointer. The pointer local_ptr was declared on line 11 but never initialized. On line 14, the code attempts to write to the random memory address local_ptr holds, causing undefined behavior.

  2. Line 26: Use of a dangling pointer. The memory for p1 was deallocated on line 22. Passing p1 to process_data on line 26 means the function receives a dangling pointer. If the if condition were different and process_data tried to use it, it would access freed memory. (Note: The code inside process_data has its own bug and doesn't actually dereference data_block, but passing the dangling pointer is itself the error here). A more direct dangling pointer error is also present on Line 7, where the function create_error_message returns a pointer to a local variable message. As soon as the function returns, message goes out of scope, and the returned pointer becomes dangling.

  3. Line 28: Null pointer dereference. The pointer p2 is explicitly set to NULL on line 24. The printf on line 28 attempts to dereference p2 to print its value, which will cause a crash.


Conclusion

Mastering these three concepts moves you from being a user of pointers to a disciplined C programmer. These are precisely the kinds of errors that static analysis tools like Coverity and Helix QAC are designed to find automatically, but understanding their root cause is what allows you to write code that is safe by design.

Key Takeaways:

  • Uninitialized Pointers: Cause undefined behavior and silent corruption. Prevention: Always initialize pointers, to NULL if no other address is available.
  • Null Pointer Dereferences: Cause predictable crashes. Prevention: Always check for NULL before dereferencing a pointer.
  • Dangling Pointers: Point to invalid memory, causing subtle corruption. Prevention: Set a pointer to NULL immediately after freeing the memory it points to.

Preview of the Next Lesson

Today, we've identified the "big three" pointer bugs and their direct, tactical fixes. In the next lesson, we will elevate these fixes into a broader strategy. We'll focus on how to apply defensive programming techniques systematically, looking at concepts like pointer ownership and API design to create systems where these errors are less likely to occur in the first place.