DebugAllocator used to be called GeneralPurposeAllocator until they renamed it in Zig 0.14.0 because the old name was confusing as hell.
DebugAllocator focuses on heap allocations - the dynamic memory where your bugs hide
Use-after-free bugs are the absolute worst. Your program runs fine for weeks, then crashes in production at 3am with a stack trace pointing to malloc()
- not the line where you actually fucked up three functions earlier. DebugAllocator fixes this by never reusing memory addresses and keeping full stack traces. It's like AddressSanitizer but actually built into the language, or Valgrind without the "install 47 packages and pray" dance.
It Catches the Stuff That Matters
- Memory leaks with exact stack traces
- Use-after-free bugs (never reuses addresses)
- Double-free attempts
- Fills freed memory with garbage to trigger obvious crashes
When you access freed memory, it crashes immediately with a stack trace pointing to exactly where you allocated it. No more 3-hour debugging sessions where you're staring at a segfault in __libc_start_main
wondering what the fuck happened to your perfectly good program.
Basic Usage
var debug = std.heap.DebugAllocator(.{}){};
defer {
const leak_status = debug.deinit();
if (leak_status == .leak) {
std.process.exit(1); // Fail CI builds on leaks
}
}
const allocator = debug.allocator();
// This leaks memory - DebugAllocator will catch it
const data = try allocator.alloc(u8, 1024);
// You forgot: defer allocator.free(data);
When you run this, DebugAllocator prints exactly where you allocated the leaked memory. Line numbers, function names, the whole deal. Much cleaner than spending 20 minutes parsing Valgrind's XML output or figuring out why LeakSanitizer isn't working on your specific GCC version.
Why It's Slow as Hell
DebugAllocator tracks every allocation with full metadata - stack traces, allocation sizes, the works. Your test suite will take 5x longer and your laptop fan will sound like a jet engine, but you'll catch bugs in minutes instead of losing your weekend to a mysterious leak.
const allocator = if (builtin.mode == .Debug)
std.heap.DebugAllocator(.{}).allocator()
else
std.heap.SmpAllocator(.{}).allocator();
Use it for debugging, never in production. SmpAllocator is what you want for actual performance.
Configuration That Actually Matters
DebugAllocator configuration is straightforward - no complex setup like external tools
var debug = std.heap.DebugAllocator(.{
.thread_safe = true, // If you're doing multi-threaded stuff
}){};
Keep the defaults for everything else. Don't enable verbose_log
unless you want your terminal spammed with allocation messages.
The deinit() Gotcha That Will Bite You
Don't ignore the return value of deinit()
:
// This silently ignores leaks - your CI will pass with memory bugs
defer _ = debug.deinit();
// This actually fails when you leak memory
defer {
if (debug.deinit() == .leak) std.process.exit(1);
}
I spent 2 hours debugging why my "memory-safe" program was leaking 200MB/hour in production, only to discover I was discarding leak detection in CI like an idiot. The leaks were right there in debug builds, but I wasn't checking the return value.
Platform Support
Works everywhere Zig does. No Valgrind setup, no Dr. Memory bullshit. Just works on Windows, Linux, macOS.
What It Won't Catch
DebugAllocator won't catch buffer overflows within allocations. You write past the end of a 100-byte allocation, it won't know. You need AddressSanitizer for that shit.
Stack corruption? Nope, only tracks heap allocations.
But it catches 90% of the memory bugs you'll actually encounter - leaks, use-after-free, double-free. That's enough to save you hours of debugging.