Tracking
Memory safety
C allocations
For high performances and very few safety checks, you might want to use the std.heap.c allocator.
Those allocations are not tracked by the Zig runtime, so you will have to use a tool like Valgrind to track memory leaks which can be done via the appropriate valgrind wrappers in the std or simply executing valgrind on the executable.
Running this excecutable code with Valgrind will give you a memory leak error.
const allocator = std.heap.c_allocator;
const just_forgotten_variable = try allocator.create(usize);
// defer allocator.destroy(just_forgotten_variable);
std.debug.print("Hello, {}!\n", .{just_forgotten_variable.*});
==121407== HEAP SUMMARY:
==121407== in use at exit: 8 bytes in 1 blocks
==121407== total heap usage: 1 allocs, 0 frees, 8 bytes allocated
This tool might be useful when you integrate C code in your Zig project and want to check memory leaks in the C code. Note that it can not detect leaks from other allocators like the GPA.
Zig allocations
The General Purpose Allocator (GPA) is a general-purpose memory allocator that is used by the Zig standard library. Its advantages over the other implementations in the std it that it is designed for safety over performance.
It can in fact detect memory problems like:
- memory leaks
- use-after-free
- double-free
Note that all those problems are given to us at runtime and not compile time.
But it cannot detect things like data races, which you could think it could with the .thread_safe
option but it does not.
What .thread_safe
does is that it makes the allocator thread-safe, meaning that it can be used in a multi-threaded environment without any problem. If you don't and use the same allocator for multiples threads, the allocator might not behave correctly.
You can have more informations on the topic following a discussion I had on the Zig discord server here.
Memory leaks
In the exemple below, the memory allocated to a
is freed (with destroy
here), if you were to comment the destroy line, the GPA would detect a memory leak and tell you what memory leaked giving a stacktrace and the memory address of the leaked memory.
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer {
const status = gpa.deinit(); // deinit is going to detect memory leaks, calls gpa.detectLeaks() under the hood
if (status == .leak) {
std.debug.print("Memory leak detected\n", .{});
} else {
std.debug.print("No memory leak detected\n", .{});
}
}
const allocator = gpa.allocator();
const a = try allocator.create(u8);
allocator.destroy(a); // Memory freed
Use after free
Following the previous exemple we are going to try to access the memory after it has been freed, this will result in a use-after-free error.
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer _ = gpa.deinit(); // deinit is going to detect memory leaks, calls gpa.detectLeaks() under the hood
const allocator = gpa.allocator();
const a = try allocator.create(u8);
allocator.destroy(a); // Memory freed
a.* = 4; // USE AFTER FREE
Invalid free
By still keeping the previous exemples, we are going to double free and get an error.
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer _ = gpa.deinit(); // deinit is going to detect memory leaks, calls gpa.detectLeaks() under the hood
const allocator = gpa.allocator();
const a = try allocator.create(u8);
allocator.destroy(a); // Memory freed
allocator.destroy(a); // DOUBLE FREE