2

std.ArrayList我正在尝试使用多个s构建一片切片。

下面的代码有效,但内存分配器std.testing.allocator会警告我在将新元素附加到sublist.

const std = @import("std");
const mem = std.mem;

fn sliceOfSlices(allocator: *mem.Allocator) ![][]usize {
    var list = std.ArrayList([]usize).init(allocator);

    var i: usize = 0;
    while (i < 3) : (i += 1) {
        var sublist = std.ArrayList(usize).init(allocator);
        // errdefer sublist.deinit(); // here?
        var n: usize = 0;
        while (n < 5) : (n += 1) {
            try sublist.append(n); // leaks
            // errdefer sublist.deinit(); // here?
            // errdefer allocator.free(sublist.items);
        }
        try list.append(sublist.toOwnedSlice());
    }
    return list.toOwnedSlice();
}

const testing = std.testing;

test "memory leaks" {
    const slice = try sliceOfSlices(testing.allocator);
    testing.expectEqual(@intCast(usize, 3), slice.len);
    testing.expectEqual(@intCast(usize, 5), slice[0].len);
}

我尝试errdefer在几个地方使用来释放已分配的sublist,但没有奏效。从文档来看,这似乎是一个终身问题,但我不知道如何处理它。

std.ArrayList(T).items 切片的生命周期保持有效,直到下次调整列表大小时,例如通过附加新元素。— https://ziglang.org/documentation/master/#Lifetime-and-Ownership

list.append()失败时适当的错误处理是什么?

4

1 回答 1

2

我是 zig 的初学者,所以也许我在这里完全错了,但我认为你得到内存泄漏的原因不是因为失败了!

当您使用ArrayList它的内存时,它是通过分配器分配的,内存必须在使用结束时明确释放。对于一个ArrayList你可以简单地使用该deinit()功能。但是,当您的函数sliceOfSlices()将该ArrayList包装器转换为切片时,您必须使用testing.allocator.free(slice)它来摆脱该切片使用的内存。

但请注意:切片的每个元素本身就是一个切片(或指向它的指针)。也通过ArrayList.toOwnedSlice(). 因此,在释放包含切片之前,您还必须摆脱这些切片。

所以我会将您的测试更改为

test "memory leaks" {
    const slice = try sliceOfSlices(testing.allocator);
    defer {
        for (slice) |v| {
            testing.allocator.free(v);
        }
        testing.allocator.free(slice);
    }
    testing.expectEqual(@intCast(usize, 3), slice.len);
    testing.expectEqual(@intCast(usize, 5), slice[0].len);
}

现在不会再发生内存泄漏了。

也许有人知道更好的解决方案,但在这里缺乏经验,这将是要走的路,IMO。

经过一番思考,回答你的问题如果出现错误该怎么办,我会将你的函数重写sliceOfSlices()

fn sliceOfSlices(allocator: *mem.Allocator) ![][]usize {
    var list = std.ArrayList([]usize).init(allocator);
    errdefer {
        for (list.items) |slice| {
            allocator.free(slice);
        }
        list.deinit();
    }

    var i: usize = 0;
    while (i < 3) : (i += 1) {
        var sublist = std.ArrayList(usize).init(allocator);
        errdefer sublist.deinit();

        var n: usize = 0;
        while (n < 5) : (n += 1) {
            try sublist.append(n);
        }

        try list.append(sublist.toOwnedSlice());
    }

    return list.toOwnedSlice();
}

现在,如果您的函数中发生任何错误,都list应该sublist正确清理。尽管如此,如果函数没有返回错误,您的调用代码将负责清理以避免内存泄漏,就像在test上面的块中实现的那样。

于 2021-01-06T14:27:19.407 回答