Zig, Memory, and the mysterious 170
I’m hacking on a small Zig application with a GTK frontend, and part of using GTK is passing around ?*anyopaque
pointers and you never quite know if it’s working. In my app I’m passing around a struct that holds a string []const u8
to various GTK callbacks. However the string was empty. This led me down a rabbit hole of trying to figure out if my pointers were correctly passed, even going as far as stepping through it with a debugger. Turns out, the pointers are all correct. Even my string []const u8
was there with the correct length, except every single byte was set to 170
and I realized I was looking at undefined memory. Zig in debug mode sets undefined memory to 0xaa
which is 170
.
But why was my pointer pointing to undefined memory, I clearly initialized it?!
Turns out it was initialized and then it got uninitialized:
pub fn start(allocator: std.mem.Allocator) !MyStruct {
var app = MyStruct{...};
var envMap = try std.process.getEnvMap(allocator); <- get the map of env vars
defer envMap.deinit(); <- deinit at end of scope
if (envMap.get("NIRI_SOCKET")) |socket_path| {
app.socket_path = socket_path; <- assign socket_path
} else {
// ...
}
return app;
}
// later
std.debug.print("socket_path: {s}\n", .{app.socket_path}); <- nothing!
std.debug.print("socket_path: {d}\n", .{app.socket_path}); <- { 170, 170, 170, ... }
It seems rather obvious now that I’ve listed out the steps here, but it wasn’t as I was debugging this. What’s going wrong is that getEnvMap
allocates memory for the socket path and then at the end of the scope, when envMap.deinit()
is called, it clears it.
Coming from languages that “magically” handle strings for you it’s a trap that I keep falling into. But having to think about when and where things actually reside in memory is a great exercise and makes me more conscious of the performance trade-offs involved.