Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A C memory leak happens when a program loses the usable pointer to allocated memory without releasing it. Related errors—including invalid frees, double frees, and use-after-free—come from mishandling ownership. The examples below show how to recognize these mistakes, correct them, and choose a debugging tool for your compiler and platform.

What counts as a memory leak or memory error in C?

A dynamically allocated block should have a clear owner: code responsible for releasing it or explicitly transferring that responsibility. A leak occurs when an allocation is still live but the program has lost its usable ownership path to it. Other common errors arise when code frees memory it does not own, frees it more than once, or uses it after release.

These are distinct problems. A program can leak memory without an invalid access, and freeing every allocation does not make a program safe if it later uses a freed pointer.

Leak example: overwriting the only pointer

Assigning a new value to an owning pointer does not release the allocation it previously pointed to. In this example, the second malloc overwrites the only pointer to the first block:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) return 1;

    p = malloc(sizeof *p); /* First allocation is now unreachable: leak. */
    if (p == NULL) return 1; /* This path also cannot release the first block. */

    free(p);
    return 0;
}

The final free(p) releases only the second allocation. The first allocation remains unfreed because its address was lost.

Keep the original pointer, or resize through a temporary

If you need two allocations, retain both pointers and release each one. If you intend to resize an allocation with realloc, use a temporary pointer so a failed resize does not overwrite your only reference to the original block:

int *tmp = realloc(p, new_count * sizeof *p);
if (tmp == NULL) {
    /* p still owns the original allocation; keep using or free it. */
    free(p);
    return 1;
}
p = tmp;

Choose what to do with the original allocation on failure according to the function’s ownership policy. Do not pass an invalid or already-freed pointer to realloc; CERT identifies that as undefined behavior. CERT MEM34-C

Leak example: skipping cleanup after an error

Every exit after an allocation must either release the block or transfer ownership. A cleanup label makes that responsibility visible when a function has several failure paths:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#include <stdlib.h>

int process(void) {
    char *buffer = malloc(1024);
    int result = -1;

    if (buffer == NULL) return -1;

    if (/* a later operation fails */) goto cleanup;

    /* Use buffer. */
    result = 0;

cleanup:
    free(buffer);
    return result;
}

Replace the illustrative failure condition and success work with the real operation. The important property is that every path reaching the end releases buffer, while the return value still reports whether processing succeeded.

Double free and invalid free examples

free must receive either NULL or the allocation pointer for a currently live block. It must not receive a stack address, a string literal, an interior pointer, or a pointer whose allocation has already been freed. Passing an invalid or already-deallocated pointer to free has undefined behavior. CERT MEM34-C

#include <stdlib.h>

int main(void) {
    char *p = malloc(10);
    if (p == NULL) return 1;

    free(p);
    free(p); /* Invalid: the allocation was already released. */
    return 0;
}

After freeing an owning pointer, setting that variable to NULL can help prevent accidental reuse; free(NULL) is harmless. But nulling one variable does not make another alias to the same freed block valid. For example, free(p + 1) is not a valid way to free part of an allocation: pass the original allocation pointer.

Microsoft’s C-oriented AddressSanitizer example demonstrates a double-free diagnostic involving a later free(x + argc - 1). That example is useful for recognizing an invalid second deallocation, not as a valid ownership pattern. Microsoft’s double-free example

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use-after-free example

A pointer becomes invalid for access after its allocation is released. Any aliases to that same allocation are invalid to use as well:

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

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) return 1;

    *p = 7;
    free(p);
    printf("%dn", *p); /* Invalid: reads memory after release. */
    return 0;
}

Freed memory may later be reused by another allocation. Even if a stale pointer appears to show its old contents, reading or writing through it is not valid. Define which component owns the allocation and when borrowed aliases stop being usable.

Make ownership visible to prevent errors

Keep allocation and release at the same level of abstraction, ideally in the same module. When ownership is split across unrelated functions, it becomes harder to see whether memory has already been freed or who must clean it up. CERT warns that this ambiguity can contribute to leaks, double frees, use-after-free, and writes to freed or unallocated memory. CERT MEM00-C

  • Document whether a function borrows a pointer, takes ownership of it, or returns ownership to its caller.
  • Give each allocation one clearly responsible owner, and make transfer of ownership explicit.
  • On every error path, release owned allocations or transfer them before returning.
  • After release, do not use any pointer alias to the allocation.

Poor memory management can also create security and reliability risks, including resource depletion; whether a particular bug is exploitable depends on the program and its context. CERT MEM00-C

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to check C code for memory problems

Choose a diagnostic based on the compiler, target operating system, and error you suspect. Sanitizers and debug heaps are toolchain features, not facilities guaranteed by the C language. A runtime tool can only report problems along execution paths that actually run.

Diagnostic route Useful for Scope and limits
AddressSanitizer (ASan) Runtime reports for supported memory-access errors; Microsoft’s documentation includes setup and diagnostic examples. Microsoft documents its implementation for x86/x64 on Windows 10 and later, enabled with the sanitizer build option, and says not to use that implementation in production. Support and leak-detection capabilities vary by compiler and platform; do not assume every ASan implementation detects leaks.
MSVC CRT debug heap Debug-build tracking of allocations and deallocations, with a route to report outstanding allocations. _CRTDBG_MAP_ALLOC adds source file and line details for malloc allocations. Specific to Microsoft’s CRT debug configuration, not a portable C facility. Release builds use ordinary allocation functions.
Static analysis Can help find some common coding mistakes before or apart from running the program. Exact memory checks and setup depend on the analyzer. Apple’s documentation search result recommends running its static analyzer for C-family code, but does not establish detailed capabilities here.

Run the Microsoft tools in their documented context

For Microsoft’s AddressSanitizer double-free example, the documented setup uses a Visual Studio 2019 version 16.9 or later Developer Command Prompt and the options /fsanitize=address /Zi. Check the current Microsoft documentation and your installed compiler before relying on that command for another target. Microsoft’s double-free example and build setup

For outstanding-allocation reports with the CRT debug heap, follow Microsoft’s debug-heap procedure for a debug build. Its report is specific to Microsoft’s CRT; it is not evidence that the same configuration or output is available with another C toolchain. Microsoft: Find memory leaks with the CRT library

Quick Recap

A practical debugging sequence

  1. Trace ownership. For each allocation, identify the pointer that owns it, the code responsible for releasing it, and any functions that only borrow it.
  2. Inspect all exits. Follow success and failure paths after each allocation. Confirm each path releases the allocation or transfers ownership.
  3. Check pointer replacement and release order. Look for an owning pointer overwritten before release, a second free, an invalid free argument, and any access after a free.
  4. Run a suitable diagnostic build. Use the sanitizer or debug-heap facility supported by your compiler and target, and exercise the code paths where the error may occur.
  5. Fix the ownership rule, not only the reported line. Make responsibility clear so related paths and aliases cannot repeat the mistake.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.