Appearance
[中文版本](/posts/c教程/zh-CN/03-内存/026-Memory Leaks and Undefined Behavior)
C gives you total control over memory, which means you can do anything you want, including things that should never be done. Two of the biggest dangers are memory leaks (when memory is never released) and undefined behavior (when the program does something unpredictable). Learning to avoid these is the key to writing stable, safe, and correct C programs.
What Is a Memory Leak?
A memory leak happens when you allocate memory on the heap and never free it. The memory stays reserved even though you can’t access it anymore.
Example:
#include <stdlib.h>
void leak(void) {
int *data = malloc(100 * sizeof(int)); // allocated
data[0] = 42;
// forgot to free(data); memory is now lost
}Ifleak() runs many times, your program consumes more and more memory until it crashes or slows down. In long-running programs (like servers), this is deadly.
Rule: Everymalloc,calloc, orrealloc must eventually be paired with a matchingfree().
Tiny Code
Let’s see leaks and fixes in action.
#include <stdio.h>
#include <stdlib.h>
void with_leak(void) {
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) arr[i] = i;
printf("with_leak: allocated 5 ints, but not freed.\n");
}
void without_leak(void) {
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) arr[i] = i;
printf("without_leak: freeing memory.\n");
free(arr);
}
int main(void) {
with_leak();
without_leak();
return 0;
}Compile and run with Valgrind (a memory checker):
gcc leaks_demo.c -o leaks_demo
valgrind ./leaks_demoValgrind output (simplified):
==1234== HEAP SUMMARY:
==1234== definitely lost: 20 bytes in 1 blocks
==1234== indirectly lost: 0 bytes in 0 blocks
==1234== LEAK SUMMARY:
==1234== 1 blocks definitely lostYou can see the first function leaked memory, while the second freed it properly.
Dangling Pointers
A dangling pointer points to memory that has been freed or is otherwise invalid.
int *p = malloc(sizeof(int));
*p = 10;
free(p);
printf("%d\n", *p); // ❌ undefined behaviorAfterfree(p), the pointerp still holds the old address, but that memory no longer belongs to you. Accessing it may crash, or appear to work, or corrupt data, you can’t rely on it.
Always nullify freed pointers:
free(p);
p = NULL;Double Free
Freeing the same memory twice also leads to undefined behavior:
int *p = malloc(sizeof(int));
free(p);
free(p); // ❌ double free errorMost modern OSes detect this and abort, but it’s still a critical bug.
Use-After-Free
This is one of the worst kinds of memory errors. It happens when you access memory after it’s been freed.
int *arr = malloc(3 * sizeof(int));
arr[0] = 5;
free(arr);
arr[0] = 7; // ❌ use-after-freeThe compiler won’t catch this, but Valgrind will warn you.
Uninitialized Memory
Reading memory you never wrote to is also undefined:
int *arr = malloc(5 * sizeof(int));
printf("%d\n", arr[2]); // ❌ uninitialized readmalloc() does not zero out memory, usecalloc() if you need cleared data.
Common Causes of Undefined Behavior
| Type | Example | Consequence |
|---|---|---|
| Out-of-bounds access | arr[10] in a 5-element array | Corrupts memory |
| Use-after-free | Dereferencing freed pointer | Crash or silent corruption |
| Null pointer dereference | *NULL | Crash |
| Division by zero | x / 0 | Crash |
| Invalid pointer arithmetic | (int *)0 + 1 | Undefined |
| Modifying string literal | char *s = "hi"; s[0]='H'; | Crash |
Why It Matters
Undefined behavior is not “just a bug.” It means anything can happen:
- Your program may seem fine but fail later.
- Compiler optimizations may remove or reorder code unexpectedly.
- The same code might work on one system and crash on another.
In C, correctness is your responsibility. You must know when memory is valid, who owns it, and when to free it.
Defensive Techniques
- Always check
malloc()return values. - Initialize pointers to
NULL. - Set pointers to
NULLafter freeing. - Use Valgrind (Linux) or AddressSanitizer (Clang/GCC) to detect leaks and invalid access.
- Prefer small, testable functions, easier to verify memory ownership.
- Avoid mixing stack and heap memory unless you’re certain of lifetimes.
Try It Yourself
- Write a small program that intentionally leaks memory. Run it under Valgrind.
- Fix the leak by calling
free()properly. - Create a dangling pointer and observe what happens (on some systems it crashes, on others not).
- Experiment with
calloc()to see how zero-initialized memory behaves. - Write a function that allocates memory and returns it, then ensure the caller frees it.
Final Thought
Memory errors are the hardest bugs to track because they may not appear right away. But once you understand ownership, who allocates and who frees, memory in C becomes predictable, even elegant. This discipline is what separates casual C users from real systems programmers.
