AddressSanitizer for C++: Complete Guide with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

AddressSanitizer for C++: The Complete Guide to Memory Error Detection

1. What It Is and Why You Need It

AddressSanitizer (ASan) isn't a library in the traditional sense — it's a powerful dynamic analysis tool built directly into the Clang and GCC compilers. It detects memory-related bugs at runtime. Developed by Google, ASan has become the de facto standard for catching dangerous defects like buffer overflows, use-after-free errors, double-frees, and memory leaks.

Why does this matter? Memory bugs are among the trickiest in C++. They can lurk for years, causing random crashes, data corruption, or security vulnerabilities. Traditional debuggers often fail because the program may crash far from the actual bug location. ASan solves this by instrumenting code at compile time: it adds checks around every memory operation (malloc, new, free, delete, pointer reads/writes). When it detects an anomaly, ASan immediately halts the program and outputs a detailed stack trace with the exact line of code, error type, and memory state.

For the PythonLib educational site, here's the key takeaway: even though Python manages memory automatically, knowing ASan is invaluable for C++ developers building high-performance or security-critical applications. ASan integrates into CI/CD pipelines, catching bugs before they hit production. It runs on Linux, macOS, Windows, and FreeBSD, supporting C, C++, and Objective-C.

2. Installation

AddressSanitizer isn't installed separately — it's part of the compiler. You need Clang (version 3.1+) or GCC (version 4.8+).

Installing the compiler (Ubuntu/Debian example):

sudo apt update
sudo apt install clang-14  # or g++-11

For macOS: Xcode Command Line Tools include Clang with ASan.

xcode-select --install

For Windows: Use Visual Studio 2019+ (enable the /fsanitize=address flag) or install Clang via LLVM.

Verify support:

clang++ -fsanitize=address --version
g++ -fsanitize=address --version

3. Quick Start — Minimal Working Example

Create a file example.cpp with a deliberate bug (use-after-free):

#include <iostream>

int main() {
    int* p = new int(42);
    delete p;
    std::cout << *p << std::endl;  // Bug: use-after-free
    return 0;
}

Compile and run with ASan:

clang++ -fsanitize=address -g -o example example.cpp
./example

You'll see a detailed report with the exact line, error type, and call stack. Without ASan, the program might print 42 or crash with a segmentation fault.

4. Key Methods/Functions/Classes

AddressSanitizer doesn't provide an API to call from your code — it works automatically. However, there are helper macros and functions for fine-tuning and integration. Here are 8 key elements.

4.1. __asan_address_is_poisoned()

Signature: int __asan_address_is_poisoned(void const *addr);

What it does: Checks whether a memory region is marked as "poisoned" (inaccessible). Returns 1 if the address is in a poisoned zone, 0 otherwise.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

int main() {
    int* arr = new int[10];
    arr[5] = 100;
    // Check if address arr+10 is poisoned (out of bounds)
    if (__asan_address_is_poisoned(arr + 10)) {
        std::cout << "Address is poisoned (out of bounds)" << std::endl;
    } else {
        std::cout << "Address is safe" << std::endl;
    }
    delete[] arr;
    return 0;
}

When to use: For custom allocators or debugging complex data structures where you need to check memory accessibility manually.

4.2. __asan_poison_memory_region()

Signature: void __asan_poison_memory_region(void const volatile *addr, size_t size);

What it does: Manually marks a memory region as poisoned. Any subsequent read/write access to this region will trigger an ASan error.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

int main() {
    int* buffer = new int[100];
    // Poison the first 10 elements
    __asan_poison_memory_region(buffer, 10 * sizeof(int));
    // This will trigger an error
    buffer[5] = 42;  // ASan error: accessing poisoned memory
    delete[] buffer;
    return 0;
}

When to use: For implementing custom memory pools, freelists, or debugging memory corruption in specific regions.

4.3. __asan_unpoison_memory_region()

Signature: void __asan_unpoison_memory_region(void const volatile *addr, size_t size);

What it does: Reverses the effect of __asan_poison_memory_region(), making the memory accessible again.

Example:

#include <sanitizer/asan_interface.h>

int main() {
    int* buffer = new int[100];
    __asan_poison_memory_region(buffer, 10 * sizeof(int));
    // Unpoison the region
    __asan_unpoison_memory_region(buffer, 10 * sizeof(int));
    buffer[5] = 42;  // Now safe
    delete[] buffer;
    return 0;
}

When to use: In custom allocators when you need to temporarily mark memory as safe after poisoning it for debugging.

4.4. __asan_describe_address()

Signature: void __asan_describe_address(void *addr);

What it does: Prints a detailed description of the given address, including which allocation it belongs to, its size, and whether it's currently poisoned.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

int main() {
    int* arr = new int[20];
    __asan_describe_address(arr + 5);  // Prints info about this address
    delete[] arr;
    return 0;
}

When to use: During debugging to understand the state of a suspicious pointer or memory region.

4.5. __asan_set_error_report_callback()

Signature: void __asan_set_error_report_callback(void (*callback)(const char*));

What it does: Registers a custom callback function that ASan calls when it detects an error. The callback receives the error report as a string.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

void myErrorHandler(const char* report) {
    std::cerr << "ASan Error: " << report << std::endl;
    // You could log to a file, send an alert, etc.
}

int main() {
    __asan_set_error_report_callback(myErrorHandler);
    int* p = new int(10);
    delete p;
    *p = 20;  // Triggers callback
    return 0;
}

When to use: For custom error logging, integration with testing frameworks, or automated bug reporting in CI/CD pipelines.

4.6. __asan_get_current_allocated_bytes()

Signature: size_t __asan_get_current_allocated_bytes();

What it does: Returns the total number of bytes currently allocated and tracked by ASan. Useful for detecting memory leaks.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

int main() {
    std::cout << "Before allocation: " << __asan_get_current_allocated_bytes() << " bytes" << std::endl;
    int* arr = new int[1000];
    std::cout << "After allocation: " << __asan_get_current_allocated_bytes() << " bytes" << std::endl;
    delete[] arr;
    std::cout << "After deallocation: " << __asan_get_current_allocated_bytes() << " bytes" << std::endl;
    return 0;
}

When to use: For monitoring memory usage in long-running applications or detecting leaks during testing.

4.7. __asan_set_death_callback()

Signature: void __asan_set_death_callback(void (*callback)(void));

What it does: Registers a callback that ASan calls just before terminating the program due to a detected error. The callback receives no arguments.

Example:

#include <sanitizer/asan_interface.h>
#include <iostream>

void onDeath() {
    std::cerr << "ASan detected a fatal error. Saving crash dump..." << std::endl;
    // Save state, generate core dump, etc.
}

int main() {
    __asan_set_death_callback(onDeath);
    int* p = new int(5);
    delete p;
    *p = 10;  // Triggers death callback
    return 0;
}

When to use: For cleanup operations, saving diagnostic data, or generating crash reports before the program exits.

4.8. __asan_report_error()

Signature: void __asan_report_error(void *pc, void *bp, void *sp, void *addr, int is_write, size_t access_size);

What it does: Manually triggers an ASan error report. This is useful for testing ASan's behavior or simulating errors in custom allocators.

Example:

#include <sanitizer/asan_interface.h>

int main() {
    int dummy = 0;
    // Simulate a write error at address &dummy
    __asan_report_error(__builtin_return_address(0), 0, 0, &dummy, 1, sizeof(dummy));
    return 0;
}

When to use: For testing ASan integration, validating error handling logic, or debugging custom memory management code.

5. Best Practices and Common Pitfalls

  • Always compile with debug symbols (-g): Without them, stack traces won't show line numbers.
  • Use ASan in debug builds only: It adds significant overhead (2x-3x slowdown) and memory usage. Never ship ASan-instrumented code to production.
  • Combine with other sanitizers: Use -fsanitize=address -fsanitize=undefined for comprehensive coverage.
  • Suppress false positives: Use suppressions.txt files to ignore known issues in third-party libraries.
  • Integrate with testing: Run ASan-enabled tests in CI to catch regressions early.
  • Watch for stack overflow: ASan increases stack usage; use ulimit -s unlimited on Linux if needed.

6. Conclusion

AddressSanitizer is an indispensable tool for any C++ developer serious about writing safe, reliable code. It catches memory bugs that might otherwise go undetected for years, saving countless hours of debugging. By integrating ASan into your development workflow — from local builds to CI/CD pipelines — you can dramatically reduce the risk of crashes, data corruption, and security vulnerabilities in your applications.

Start using ASan today: add -fsanitize=address -g to your compiler flags and watch your code quality improve. For more advanced usage, explore the ASan API functions covered in this guide to customize error handling and memory monitoring.

Recommendations