advanced22 min

Common Undefined-Behavior Pitfalls

Buffer overflows, use-after-free, and uninitialized variables -- and the defensive habits that avoid them.

What you'll learn

  • Explain what a buffer overflow is and how a bounded copy like strncpy helps avoid one
  • Explain why reading an uninitialized variable is undefined behavior, not just 'probably zero'
  • Recognize use-after-free as a dangling-pointer bug and describe how to avoid it

Explanation

C's speed and control come from giving the programmer very little built-in safety net. Several categories of mistakes don't produce a clean error -- they produce undefined behavior, meaning the C standard places no requirement at all on what happens next. The program might crash, might silently corrupt unrelated data, or might appear to "work" for years until it doesn't. This lesson doesn't execute any undefined behavior (there's no well-defined "expected output" for it to honestly show you) -- instead, it walks through the most common categories and the concrete habits that avoid them.

A buffer overflow happens when code writes past the end of an array or char buffer's allocated space, corrupting whatever memory happens to sit next to it. Copying a string with a plain strcpy into a fixed-size buffer, without checking the source's length against the buffer's size, is a classic way this happens. A bounded function like strncpy(buffer, source, sizeof(buffer) - 1) limits how many bytes get written, and explicitly setting the last byte to '\0' afterward guarantees proper termination even if the source was longer than the buffer.

An uninitialized variable holds whatever bits happened to already be in that memory location -- not reliably zero, not reliably anything. Reading it before assigning it a real value is undefined behavior, even though it often "looks like" it just contains garbage. The fix is simple and absolute: always give a local variable an initial value before its first read, e.g. int total = 0; instead of declaring int total; and hoping.

Use-after-free is the dangling-pointer problem from the previous lesson viewed from the bug's perspective: dereferencing, reading, writing, or re-freeing a pointer after its memory has already been released with free. The defensive habit from that lesson -- setting a pointer to NULL immediately after freeing it -- turns an unpredictable use-after-free into a predictable, immediately obvious NULL-dereference instead.

Guided lab

Predict: A safely bounded copy and a properly initialized total

CNot executed
This lab does not run in your browser or on VisaSparkSchools's servers. Read the code, predict what it does, then reveal the real expected output.

This program applies both defensive habits from this lesson correctly. Read it and predict exactly what it prints.

#include <stdio.h>
#include <string.h>

int main(void) {
    char buffer[10];
    strncpy(buffer, "hello", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';

    int total = 0;
    for (int i = 0; i < 5; i++) {
        total += i;
    }

    printf("buffer: %s\n", buffer);
    printf("total: %d\n", total);

    return 0;
}

Stuck? Get a hint.

Common mistakes

  • Copying a string into a fixed-size buffer without bounding the copy by the buffer's actual size, risking a buffer overflow.
  • Reading a local variable before giving it an initial value, assuming it defaults to zero the way some other languages guarantee.
  • Continuing to use a pointer after calling free() on it, instead of setting it to NULL immediately to make any accidental reuse fail predictably.

Knowledge check

Knowledge check

1. What does the C standard guarantee about the value of an uninitialized local variable?
2. What is a buffer overflow?
3. What defensive habit turns an unpredictable use-after-free into a predictable failure?

Takeaway

Always initialize local variables before reading them, bound every copy into a fixed buffer by that buffer's real size, and null out a pointer right after freeing it.

Summary

Buffer overflows, uninitialized reads, and use-after-free are all undefined behavior with no guaranteed outcome -- bounded copies, always-initialize, and null-after-free are the standard defenses.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.