I was looking at a snippet of code that adds items to an ArrayList. The new item is first created on the stack and then passed over to the ArrayList.
Isn’t this inefficient? What if the item is very large? Maybe it’s not even possible to have a copy of the item on the stack, because it is too large.
I’ve created a small toy example. The first test “intoListFromStack” shows the code I’ve been using so far to populate an ArrayList. But the struct LargeStruct
is not compatible with this code, it’s too large for the stack:
const SmallStruct = struct {
data: usize,
};
const LargeStruct = struct {
data: [1000000]i128,
};
test "intoListFromStack" {
var list = std.ArrayList(SmallStruct).init(std.testing.allocator);
defer list.deinit();
const N = 3;
for (0..N) |_| {
var small = SmallStruct{ .data = 0 };
try list.append(small);
}
// the following crashes, the struct is too large for the stack
//var big = LargeStruct{ .data = undefined };
}
It is possible to allocate LargeStruct
on the heap:
test "simpleAlloc" {
// but it is possible to allocate on the heap
var big = try std.testing.allocator.alloc(LargeStruct, 1);
defer std.testing.allocator.free(big);
std.debug.print("big: {d}\n", .{big[0].data[0]});
}
So now I’d like to create an ArrayList with pointers to LargeStruct
. But looking at the ArrayList source code, I’m a bit confused how that would be possible.
I think the relevant line is this, a new item is added to the list:
I think new_item_ptr.* = item
means that the value of item is copied to new_item_ptr.*
.
So if item
itself is a pointer, how can that work? Is there some automatic dereferentiation?
I’ve created another test which tries to populate an ArrayList with items created on the heap.
But this doesn’t work.
The ArrayList doesn’t take ownership of the pointer, so I still need to clean it up.
And when it runs out of scope (and its memory is freed) the ArrayList ends up pointing to freed memory, leading to a segfault:
test "intoListFromHeap" {
// attempt 2 at creating an ArrayList of LargeStructs
// this time I'm keeping pointers, to heap allocated memory
var list = std.ArrayList(*LargeStruct).init(std.testing.allocator);
defer list.deinit();
const N = 3;
for (0..N) |_| {
// allocating a new entry on the heap
var large = try std.testing.allocator.create(LargeStruct);
// the following line confuses me
// I would have expected, that the ArrayList takes ownership of the heap allocated memory
// but if I remove the following line, the memory is leaked
defer std.testing.allocator.destroy(large);
try list.append(large);
}
}
How does this work? Can I create an ArrayList of LargeStruct
?