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).

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.

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:
-
Line 14: Use of an uninitialized pointer. The pointer
local_ptrwas declared on line 11 but never initialized. On line 14, the code attempts to write to the random memory addresslocal_ptrholds, causing undefined behavior. -
Line 26: Use of a dangling pointer. The memory for
p1was deallocated on line 22. Passingp1toprocess_dataon line 26 means the function receives a dangling pointer. If theifcondition were different andprocess_datatried to use it, it would access freed memory. (Note: The code insideprocess_datahas its own bug and doesn't actually dereferencedata_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 functioncreate_error_messagereturns a pointer to a local variablemessage. As soon as the function returns,messagegoes out of scope, and the returned pointer becomes dangling. -
Line 28: Null pointer dereference. The pointer
p2is explicitly set toNULLon line 24. Theprintfon line 28 attempts to dereferencep2to 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
NULLif no other address is available. - Null Pointer Dereferences: Cause predictable crashes. Prevention: Always check for
NULLbefore dereferencing a pointer. - Dangling Pointers: Point to invalid memory, causing subtle corruption. Prevention: Set a pointer to
NULLimmediately 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.