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.

comments powered by Disqus