Skip to content
  • Common Memory Bugs · C

Common Memory Bugs

LearnViaNotes @learnvianotes· 4 min read

C’s most expensive bugs are memory errors: they compile silently, often seem to work, and later crash or become security holes. Sanitizers (-fsanitize=address,undefined) and Valgrind find them.

BugWhat it isCaught by
Buffer overflowRead/write past the end (or before the start) of a bufferASan, -D_FORTIFY_SOURCE
Use after freeAccess through a pointer to freed memoryASan, Valgrind
Double freefree the same block twiceASan, glibc checks
Memory leakLose the last pointer to a block without freeing itLeakSanitizer, Valgrind
Dangling stack pointerUse a pointer to a local after its function returnedCompiler warning, ASan (use-after-return)
Uninitialised readRead a variable / malloc’d memory before writing it-Wuninitialized, MSan, Valgrind
Invalid freefree a pointer not from malloc (stack array, p + 1)ASan
NULL dereferenceUse a pointer that is NULL (e.g. unchecked malloc)Crash (SIGSEGV), UBSan

Heap buffer overflow

C
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *name = malloc(5);
    strcpy(name, "Alice");        /* needs 6 bytes: 5 letters + '\0' */
    free(name);
    return 0;
}
AddressSanitizer (trimmed)
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000955
WRITE of size 6 at 0x602000000955 thread T0
0x602000000955 is located 0 bytes after 5-byte region [0x602000000950,0x602000000955)

Use after free

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (!p) return 1;
    *p = 42;
    free(p);
    printf("%d\n", *p);           /* the block now belongs to the allocator */
    return 0;
}
AddressSanitizer (trimmed)
==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000950
READ of size 4 at 0x602000000950 thread T0
freed by thread T0 here: ... in free
previously allocated by thread T0 here: ... in malloc

Double free

C
#include <stdlib.h>

int main(void) {
    char *buf = malloc(16);
    free(buf);
    free(buf);                    /* second free of the same block */
    return 0;
}
AddressSanitizer (trimmed)
==ERROR: AddressSanitizer: attempting double-free on 0x602000000950 in thread T0

Invalid free

C
#include <stdlib.h>

int main(void) {
    int *a = malloc(4 * sizeof *a);
    if (!a) return 1;
    a++;                          /* lost the original pointer */
    free(a);                      /* not the address malloc returned */
    return 0;
}

Stack buffer overflow & use after scope

C — stack-use-after-scope caught by AddressSanitizer
#include <stdio.h>

int main(void) {
    int *p;
    {
        int inner = 7;
        p = &inner;
    }                             /* inner's lifetime ends here */
    printf("%d\n", *p);
    return 0;
}

Memory leak

C — a leak runs “fine”, which is exactly the danger
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *greet(const char *name) {
    char *s = malloc(strlen(name) + 7);
    if (s) { strcpy(s, "Hello "); strcat(s, name); }
    return s;                      /* the caller now owns s */
}

int main(void) {
    for (int i = 0; i < 3; i++) {
        char *msg = greet("Bob");
        if (msg) puts(msg);
        /* missing free(msg): each iteration leaks 10 bytes */
    }
    return 0;
}
Output
Hello Bob
Hello Bob
Hello Bob
LeakSanitizer on Linux (illustrative)
==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 30 byte(s) in 3 object(s) allocated from:
    #0 in malloc
    #1 in greet main.c:6

Uninitialised reads

C — rejected: variable 'total' is uninitialized when used
#include <stdio.h>

int main(void) {
    int total;
    for (int i = 0; i < 3; i++) total += i;    /* total starts as garbage */
    printf("%d\n", total);
    return 0;
}

Defensive habits

  • Initialise every variable at declaration; = {0} for arrays and structs.

  • Check every malloc / fopen / realloc result.

  • Set pointers to NULL after free; keep one clear owner per allocation.

  • Pass buffer sizes everywhere; prefer snprintf, fgets, strncat/memcpy with explicit lengths.

  • Build debug and test runs with -g -fsanitize=address,undefined; run Valgrind in CI.

  • Use the goto cleanup pattern so every exit path frees what was allocated.

What is a dangling pointer and how do you avoid it?

What is the difference between a memory leak and a dangling pointer?

How does AddressSanitizer work?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report