Returning a pointer to a local variable is a classic C mistake: the function returns the address of something that stops existing the moment the function returns. The result is a dangling pointer into a dead stack frame — and dereferencing it is undefined behavior. This question from our C Programming Quiz App looks innocent, and when we actually ran it, the program didn’t print a wrong number — it crashed outright. Here’s why.
The Quiz Question
int *f(void) {
int x = 42;
return &x;
}
printf("%d", *f());
What is the output of this code?
- 42
- 0
- Undefined behavior
- Compile error
The Correct Answer: Undefined Behavior
x is an automatic (stack) variable. Its lifetime ends when f() returns — the C standard says using a pointer to an object after its lifetime ends is undefined behavior. Here is exactly what gcc 13.3 on Ubuntu 24.04 did with this program:
$ gcc -Wall -Wextra quiz.c -o quiz
quiz.c:5:12: warning: function returns address of local variable [-Wreturn-local-addr]
5 | return &x;
| ^~
$ ./quiz
Segmentation fault (core dumped)
A crash, not 42. Modern gcc doesn’t just warn — when it can prove a function returns a local’s address, it replaces the returned pointer with NULL in the generated code. Dereferencing NULL then segfaults deterministically. The same happens at -O2. The compiler is entitled to do this precisely because the behavior is undefined: any outcome is conforming.
Why Each Wrong Answer Is Wrong
Why not 42?
The intuition: “the value 42 is still sitting in that stack slot right after the call, so reading it works.” Historically, on some compilers with optimizations off, that’s exactly what you’d observe — the stack frame hadn’t been overwritten yet. But it’s an accident of timing: the very next function call (even printf setting up its own frame) can clobber the slot. And as our test shows, gcc now actively sabotages the read by returning NULL. Code that “worked” for years crashes on a compiler upgrade — the signature of undefined behavior.
Why not 0?
There’s no rule that dead stack memory reads as zero. The slot holds whatever was written there last. (Ironically, with modern gcc you crash before reading anything, because the pointer — not the pointee — was replaced with NULL.)
Why not “Compile error”?
The code is syntactically and type-correct: &x is a valid int *, and returning it breaks no constraint the compiler is required to reject. That’s why you get a warning (-Wreturn-local-addr), not an error. It compiles, links, and runs — into undefined behavior. This distinction (diagnosable vs. ill-formed) is a favorite interview follow-up.
Stack Frames and Object Lifetime
Every function call pushes a stack frame holding its local variables. When the function returns, the frame is popped — the memory isn’t erased, but it’s no longer reserved: the next call reuses the same addresses for its own locals. A pointer into a popped frame is a dangling pointer, exactly like a pointer to freed heap memory.
Three correct ways to return data from a function:
/* 1. Return by value — copies are cheap for scalars and small structs */
int f(void) { int x = 42; return x; }
/* 2. static storage — lives for the whole program (shared across calls!) */
int *f(void) { static int x = 42; return &x; }
/* 3. Heap allocation — caller owns it and must free() it */
int *f(void) {
int *p = malloc(sizeof *p);
if (p) *p = 42;
return p;
}
Each has a trade-off: by-value is simplest and usually right; static means every caller shares one object (not thread-safe, next call overwrites it); malloc shifts cleanup responsibility to the caller.
How to Catch This Bug
gcc -Wall -Wextra file.c # -Wreturn-local-addr flags the return clang -Wall file.c # -Wreturn-stack-address flags it too gcc -fsanitize=address file.c # ASan catches stack-use-after-return variants
For the simple direct return &x; both compilers see it statically. The nastier variants — storing a local’s address into a struct or global that outlives the call — evade the static warning, which is where AddressSanitizer earns its keep.
Frequently Asked Questions
Can a C function return a pointer?
Absolutely — returning pointers is idiomatic C. The bug isn’t returning a pointer; it’s returning a pointer to an object whose lifetime ends with the function. Pointers to static objects, heap allocations, or objects owned by the caller are all fine.
Why does it sometimes print 42 anyway?
If the compiler doesn’t intervene and nothing overwrites the dead frame before the read, the stale bytes still spell 42. That’s luck. Different compiler, flags, or an added function call in between changes the result — our gcc 13 test crashed instead.
Is returning a static local’s address safe?
It’s well-defined — a static local lives for the entire program. But all callers share the single object: a second call overwrites the value the first caller may still be using, and it’s not thread-safe. Fine for small lookup-style helpers; wrong for anything reentrant.
Related Reading
- Pointers in C – Complete Guide
- Dynamic Memory Allocation in C
- Recursion in C – Complete Guide
- C Aptitude Questions and Answers
Recommended Books
- The C Programming Language – Kernighan & Ritchie (India) | Amazon.com
- C Programming: A Modern Approach – K.N. King (India) | Amazon.com
This question is #96 in the C Programming Quiz App — 155 questions with explanations covering memory, pointers, operators, and more.
Download on Google Play →