Hello! Welcome to your next lesson.
In our last session, we focused on using const with pointers to create safer, more explicit function interfaces—a crucial skill for writing robust C code. We saw how to signal whether a function can or cannot modify the data it receives via a pointer.
Today, we'll investigate one of the most classic and dangerous C programming errors, a topic guaranteed to come up in technical interviews. We will explore what happens when a function returns a pointer to one of its own local variables. This error directly relates to how C manages memory for function calls and is a frequent source of hard-to-find bugs.
Your learning outcome for this lesson is to predict the behavior of code that returns a pointer to a local (stack) variable and explain the resulting undefined behavior. To do this, we first need to solidify our understanding of the function call stack.
The Life and Death of Local Variables: The Call Stack
Every time a function is called in C, the system sets aside a small block of memory for it on a special region called the call stack. This block is called a stack frame. It holds the function's local variables, its parameters, and the information needed to return to the caller.
The most important rule to remember is this: When the function returns, its stack frame is destroyed. This means all its local variables cease to exist.
To visualize this process, let's review how the call stack works.
Understanding Functions, Memory, and Pointers in C: A Complete ...
The following article, 'Understanding Functions, Memory, and Pointers in C', provides an excellent, step-by-step trace of how the call stack operates. Pay close attention to the diagrams showing stack frames being created ('pushed') and destroyed ('popped').
Please read sections 1.2 ('The Call Stack Explained'), 1.3 ('Tracing Through a Function Call: Step by Step'), and 1.4 ('Key Takeaways'). Focus on how each function call adds a new frame to the stack and how returning from a function removes its frame.

The key takeaway is the direct link between a variable's lifetime and its function's execution. Local variables are born when the function is called and die when the function returns. This simple fact is the root cause of the bug we are about to explore.
The Dangling Pointer: Returning a Reference to a Ghost
Now, let's see what happens when we combine our knowledge of pointers and the call stack. Consider a function that creates a local variable and returns a pointer to it.
int* create_value() {
int local_val = 100;
// ...
return &local_val; // DANGER!
} // local_val is destroyed HERE.
int main() {
int *ptr = create_value();
printf("%d\n", *ptr); // What will this print?
return 0;
}
The create_value function returns an address. However, by the time the main function receives this address, the local_val variable at that address is gone because the stack frame for create_value has been destroyed.
The pointer ptr is now a dangling pointer: it holds an address to memory that is no longer valid. Dereferencing it results in Undefined Behavior (UB).
What does Undefined Behavior mean in practice?
- It might work by coincidence: If the memory hasn't been overwritten by another function call yet, you might read the old value (100).
- It might return garbage: If another function (like
printfitself!) has been called, its stack frame might have overwritten that same memory location with new data. - It might crash: The operating system may detect that you are trying to access memory you don't own, resulting in a segmentation fault.
This unpredictability is what makes this bug so severe, especially in your field of automotive software where deterministic behavior is paramount.
The following resource provides a fantastic in-depth look at this specific problem.
Understanding Functions, Memory, and Pointers in C: A Complete ...
This section from the 'Understanding Functions...' article directly addresses our learning outcome. It provides a full code example, explains the resulting dangling pointer, and visualizes how the stack memory gets corrupted.
Read section 3.4, 'The Danger: Returning Pointers to Local Variables'. Pay close attention to the code example and the 'Possible Output', which demonstrates how the value can change after another function is called. The visual diagrams are key to understanding the state of the stack at each step.
To reinforce this, let's look at another perspective that explains exactly why the memory becomes corrupted.
Stack & Heap - CS 3410 - Cornell: Computer Science
The following material from Cornell University's CS course offers a concise explanation of this same bug using a different example. It elegantly describes how a subsequent function call reclaims the stack space.
Read the section 'Limitations of the Stack' and the explanation that follows the buggy create_exp function. It clearly states the rule 'never return a pointer to a local variable' and explains what happens to the memory when the next function is called.
A final point: any decent modern compiler will warn you about this mistake if you enable warnings (e.g., using the -Wall flag in GCC or Clang). This is one of many reasons why you should always compile with warnings enabled and treat them as errors.
The Right Way: Safe Alternatives
In an interview, after you explain why this is a bug, the next question will inevitably be, "So how do you fix it? How can a function safely return a complex piece of data?"
There are three standard patterns.
1. Return by Value
If the data is small (like a single int, char, or even a small struct), just return a copy of it. The value is safely copied out of the function's stack frame before it is destroyed.
// SAFE: The value 42 is copied and returned.
int get_value_safe() {
return 42;
}
2. Caller Provides the Storage (Pass-by-Pointer)
The caller declares the variable and passes a pointer to it into the function. The function then populates the data at that address. The memory is owned by the caller, so it remains valid after the function returns.
// SAFE: The function writes to memory owned by the caller.
void get_value_into(int *result_ptr) {
*result_ptr = 42;
}
int main() {
int my_value;
get_value_into(&my_value); // my_value now contains 42.
}
3. Heap Allocation
For large or dynamically-sized data, the function can allocate memory on the heap using malloc or calloc. Heap memory is not tied to any function's stack frame and persists until it is explicitly deallocated with free. The function returns a pointer to this heap memory.
The critical responsibility is that the caller must free the memory when it's no longer needed to prevent a memory leak.
Understanding Functions, Memory, and Pointers in C: A Complete ...
The following resources demonstrate these three safe patterns, including a corrected example using heap allocation.
First, read section 3.5, 'Safe Alternatives', which shows all three methods. Then, look at the code example in section 5.6, 'Why Heap Memory Survives Function Returns', which provides a complete, runnable example of the heap allocation pattern and contrasts it with the dangerous stack allocation.
Conclusion
You are now equipped to answer a very common and insightful C interview question. Understanding the function call stack is fundamental to diagnosing a wide range of bugs, and the issue of dangling pointers is a perfect test of that knowledge.
Key Takeaways:
- Local variables are stored on the call stack within a function's stack frame.
- A variable's lifetime is tied to its function's execution; when the function returns, its local variables are destroyed.
- Returning a pointer to a local variable creates a dangling pointer, which points to invalid memory.
- Dereferencing a dangling pointer results in Undefined Behavior (UB), a critical bug that can cause crashes or silent data corruption.
- The three safe patterns for returning data from a function are: return-by-value, caller-provides-storage, and heap allocation.
In our next lesson, we will begin a new module focusing on arrays and strings. We will start by examining their memory layout and the concept of pointer decay, a subtle but crucial aspect of how C handles arrays, especially when they are passed to functions.