I remember reading that it’s OK to call free
on a slice that has not been allocated, as long as its .len == 0
, and indeed it is, but not when the slice is sentinel terminated:
var name: [:0]const u8 = &.{};
//var name: []const u8 = &.{}; // This works
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocr = gpa.allocator();
allocr.free(name); // This does NOT work with [:0] slice
name = allocr.dupeZ(u8, "My name") catch @panic("Out Of Memory");
//name = allocr.dupe(u8, "My name") catch @panic("Out Of Memory"); // This works
defer allocr.free(name);
std.debug.print("Name is \"{s}\"\n", .{ name } );
In std/mem/Allocator.zig, in pub fn free
I can see that for sentinel-terminated slices, the function does not short-circuit itself:
pub fn free(self: Allocator, memory: anytype) void {
const Slice = @typeInfo(@TypeOf(memory)).pointer;
const bytes = mem.sliceAsBytes(memory);
const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0;
if (bytes_len == 0) return;
const non_const_ptr = @constCast(bytes.ptr);
// TODO: https://github.com/ziglang/zig/issues/4298
@memset(non_const_ptr[0..bytes_len], undefined);
self.rawFree(non_const_ptr[0..bytes_len], log2a(Slice.alignment), @returnAddress());
}
In my case ([:0]const u8
), bytes_len
is 1 if I understand the logic correctly, and if (bytes_len == 0) return;
is not triggered.
Is this the intended behavior? Maybe I misunderstand the way sentinel-terminated slices work?
Update: just for the record - the obvious solution is to check slice len
before calling free
, this then works with sentinel-terminated and simple slices:
if (name.len > 0) // Sentinel-terminated slices are not safe to `free` willy-nilly
allocr.free(name); // This works with [:0] slice and with [] slices