Skip to content

[中文版本](/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_demo

Valgrind 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 lost

You 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 behavior

Afterfree(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 error

Most 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-free

The 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 read

malloc() does not zero out memory, usecalloc() if you need cleared data.

Common Causes of Undefined Behavior ​

TypeExampleConsequence
Out-of-bounds accessarr[10] in a 5-element arrayCorrupts memory
Use-after-freeDereferencing freed pointerCrash or silent corruption
Null pointer dereference*NULLCrash
Division by zerox / 0Crash
Invalid pointer arithmetic(int *)0 + 1Undefined
Modifying string literalchar *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 ​

  1. Always checkmalloc() return values.
  2. Initialize pointers toNULL.
  3. Set pointers toNULL after freeing.
  4. Use Valgrind (Linux) or AddressSanitizer (Clang/GCC) to detect leaks and invalid access.
  5. Prefer small, testable functions, easier to verify memory ownership.
  6. Avoid mixing stack and heap memory unless you’re certain of lifetimes.

Try It Yourself ​

  1. Write a small program that intentionally leaks memory. Run it under Valgrind.
  2. Fix the leak by callingfree() properly.
  3. Create a dangling pointer and observe what happens (on some systems it crashes, on others not).
  4. Experiment withcalloc() to see how zero-initialized memory behaves.
  5. 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.

本站总访问 — 次