Stack Array with runtime determined size

Hi,

I want to write a function that takes an ArrayList(u8) and in order to work on it, a temporary copy of the entire array is required. Since the array is local and is discarded at the end of the function it seems logical to allocate it on the stack.
However, I don’t know how to do it. The following does not compile because the array size needs to be compile time known. But I need the size to be determined at runtime, depending on the size of the ArrayList.

pub fn foo(bar: *std.ArrayList(u8)) void {
    tmp: [bar.items.len]u8 =  undefined;
    ...
}

Is there a way to stackallocate a runtime sized array? I do not want to guess an oversized buffer.

Thanks

I think the short answer is no.
There used to be @alloca (as the C function), but it was removed ages ago.

If you want to stack allocate dynamic sized objects, you can create a std.heap.FixedBufferAllocator backed by a stack-allocated array. The program will give you an OutOfMemory error if you overun the buffer.

I think that’s the preferred way to write heap-free code.
I’ll admit, I haven’t done much of that in Zig.

What does foo do, though?

If it’s just a single array, then std.BoundedArray might be what OP is looking for. It will always allocate the maximum bound (as will the FixedBufferAllocator solution), but presents an interface like that of std.ArrayList and the length as seen by the user when accessing std.BoundedArray(T){}.items is dynamic.

2 Likes

thank you.

The function foo is not any particular function. I was interested in the concept, because I saw it in C. For a long time I was under the assumption that static arrays in C needed compile time known size, but that changed at some point. (boy, I’m getting old).
So I thought I try to figure out how to do this in zig.

You’re welcome :slight_smile:

For what it’s worth, one of the long term goals of Zig is eventually being able to provide an upper bound on the program stack size via static analysis, so I wouldn’t expect it to make it back into the language.

Interesting.
How can you determine a maximum stacksize if recursion exists and the depth is based on runtime values?

Here’s the issue tracking it:

When the compiler has the whole call graph it can look for recursive calls, which must be called with a builtin that takes either an explicit stack size or a maximum depth.

3 Likes

Recursion is not the only issue. Other issue is calling function using function pointer (since at compile time it is unknown which will be called). Ideas to solve this problem are pretty intresting Proposal: restricted function types · Issue #23367 · ziglang/zig · GitHub

3 Likes

It’s worth noting that both the std.BoundedArray and std.heap.FixedBufferAllocator solutions are just nice, dynamic-looking APIs around static buffers, and would still require “guessing an oversized buffer”. The options are to either guess an oversized buffer, or to just use the heap.

@SpinTensor given that you’re already most likely using the heap for the argument, I recommend just using the heap, defining tmp as an std.ArrayList(u8), using .initCapacity(bar.items.len) for the initial value, and using the .doSomethingAssumeCapacity() functions to operate on the ArrayList. This will avoid making unnecessary allocations (the entire list is preallocated) without needing to guess an oversized buffer.

3 Likes

There is one simple solution for this: Just make your own stack.

You can put a stack-based allocator into a threadlocal variable. This will give you almost the same performance as alloca, but with the advantage of being able to pass it to all of Zig’s functions. E.g. you can now even handle local data structures without any expensive heap allocation.

Zig sadly doesn’t have a stack-based allocator yet, but the fundamentals are quite simple to implement, you just need to increment the stack pointer on alloc, and decrement it on free (note that this only works if everything has the same alignment).

For a more sophisticated version you can also look at the implementation which I use in my game which allows for out-of-order frees and a backing allocator to make safe to use in all circumstances.

2 Likes