Zig - Bus error at address and memory management
Assumed audience: People learning the Zig programming language
Note that since Zig is in pretty active development this error message differs quite a bit between versions though the general idea should apply. For context though I'm using zig v0.17.0-dev.690+c5a61e899 which is the latest master and is not yet a stable release
Error
So I've been learning Zig since about yesterday and since then I've started writing a scripting language. During my test runs I kept running into this error:
Bus error at address 0x104f6fa5d
/zig/bin/lib/compiler_rt.zig:663:14: 0x104f3b9c8 in memset (compiler_rt)
/zig/zig/lib/std/mem/Allocator.zig:450:5: 0x104e46c93 in free__anon_10187 (test)
@memset(bytes, undefined);
^
/repo/src/root.zig:262:17: 0x104f2ff9b in deinit (test)
gpa.free(self.content);
^
/repo/src/root.zig:281:26: 0x104f2dfff in test.Bus error at address repro (test)
defer expected.deinit(gpa);
^
/zig/zig/lib/compiler/test_runner.zig:143:68: 0x104f0860b in mainServer (test)
const status: TestResults.Status = if (test_fn.func()) |v| s: {
^
/zig/zig/lib/compiler/test_runner.zig:71:26: 0x104f08c87 in main (test)
return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err});
^
/zig/zig/lib/std/start.zig:740:88: 0x104f06ad7 in callMain (test)
if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
^
???:?:?: 0x195fe5d53 in start (/usr/lib/dyld)
Explanation
The message is quite cryptic: "Bus error at address" and just points to some call to free that I've made. I have no idea why that was the issue and assumed the issue was me messing around with my memory management
I reimplemented the solution and tried multiple fixes and couldn't get the bug to go away, so I started reading everything I could understand about Zig memory allocation and some different management strategies
The comment that led me to some insight was from a question about invalid frees where a commenter says "Invalid free means that you are trying to free data that was never allocated with that allocator." - really right in front my face. And then I though - hmm, where am I allocating memory that I am using a different allocator for. Well - nowhere.
But then I thought - what if I was trying to free something that I didn't allocate memory for - which led me to a string - which is a compile time constant, and so isn't allocated on the heap.
Turned out that the string I had to verify my implementation in my test code was being freed by implementation's cleanup code. The issue was the test. I know - incredibly silly
The takeaway however is - If you see the error above, make sure you're only freeing memory that you've allocated
Reproduction
For the sake of completeness and having something more than just a big error dump - here's a reproduction I made:
Here's a small function that creates a struct called Data with a content property and a deinit method for freeing any associated memory.
const Data = struct {
content: []const u8,
fn deinit(self: *Data, gpa: Allocator) void {
// just frees the content
gpa.free(self.content);
}
};
fn createData(gpa: Allocator, input: []const u8) !Data {
var buf: std.ArrayList(u8) = .empty;
// iterate through a string and add each character twice to a buffer
// for funsies
for (input) |c| {
try buf.append(gpa, c);
try buf.append(gpa, c);
}
// convert the ArrayList to a Slice.
// this frees the ArrayList and returns a newly allocated []u8
// whoever get's content needs to free it
const content = try buf.toOwnedSlice(gpa);
// create a data struct with the resulting content
// this can be freed using the Data.deinit method
return Data{ .content = content };
}
In my test, I had this:
test "Bus error at address repro" {
const gpa = std.testing.allocator;
// create an expected Data instance with content as a constant
var expected = Data{ .content = "hheelllloo" };
// this below deinit call is bad and was causing the above error
defer expected.deinit(gpa); // <- this line should be removed
// we don't need to free expected.content
// trying to free this will lead to an error
// create the actual Data instance
var actual = try createData(gpa, "hello");
// free the actual data using the Data.deinit function defined above
defer actual.deinit(gpa);
try std.testing.expectEqualDeep(expected, actual);
}
Removing the call to my struct's deinit method fixed the error
Solution
In case you skipped to the end to find the answer, as I mentioned above - ensure that you're only freeing memory that you allocated directly - and double check and test structs that you're freeing.