Memory leak and slice ownership

Hi everyone, I came across this memory leak and I can’t figure how to fix. the code is the following:

const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer std.debug.assert(gpa.deinit() == .ok);

    var final = ArrayList(u8).init(allocator);
    defer final.deinit();
    var tmp = ArrayList(u8).init(allocator);

    try tmp.appendSlice("First slice pushed");
    try final.appendSlice(try tmp.toOwnedSlice());

    print("Final: {s}\n", .{final.items});
}

When I print items of final array list, I see First slice pushed so I expect that the memory is now owned but the final array list. But calling deinit() on final results on a memory leak regarding the first slice pushed to tmp:

error(gpa): memory address 0x1fcd9670000 leaked: 
C:\SC\Zig\zig-windows-x86_64-0.14.0-dev.224+95d9292a7\lib\std\array_list.zig:457:67: 0x516476 in ensureTotalCapacityPrecise (array_list_leak.exe.obj)
                const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
                                                                  ^
C:\SC\Zig\zig-windows-x86_64-0.14.0-dev.224+95d9292a7\lib\std\array_list.zig:434:51: 0x507f62 in ensureTotalCapacity (array_list_leak.exe.obj)
            return self.ensureTotalCapacityPrecise(better_capacity);
                                                  ^
C:\SC\Zig\zig-windows-x86_64-0.14.0-dev.224+95d9292a7\lib\std\array_list.zig:468:44: 0x4d41ed in ensureUnusedCapacity (array_list_leak.exe.obj)
            return self.ensureTotalCapacity(try addOrOom(self.items.len, additional_count));
                                           ^
C:\SC\Zig\zig-windows-x86_64-0.14.0-dev.224+95d9292a7\lib\std\array_list.zig:305:42: 0x4d160b in appendSlice (array_list_leak.exe.obj)
            try self.ensureUnusedCapacity(items.len);
                                         ^
D:\mvachero\Documents\TaffDivers\Zig\Tests\dag\array_list_leak.zig:15:24: 0x4d13b0 in main (array_list_leak.exe.obj)
    try tmp.appendSlice("First slice pushed");

As a workaround, I use the writer interface of ArrayList and deinit() both lists:

const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer std.debug.assert(gpa.deinit() == .ok);

    var final = ArrayList(u8).init(allocator);
    defer final.deinit();
    var tmp = ArrayList(u8).init(allocator);
    defer tmp.deinit();

    try tmp.appendSlice("First slice pushed");
    var writer = final.writer();
    try writer.print("{s}", .{tmp.items});

    print("Final: {s}\n", .{final.items});
}

The doc says that when using toOwnedSlice() there is no need to call deinit() and indeed, it doesn’t change anything when doing so.
How does the ownership is transferred? How to manage such memory?

Thanks!

Calling final.appendSlice(...) does not transfer ownership of the passed slice to final. Instead final allocates new memory as necessary and performes a @memcpy. After that you still have to deallocate the slice you get from tmp.toOwnedSlice() yourself.

4 Likes

Indeed it makes sense and it works. Thanks :slight_smile:

2 Likes