#Struggling to understand allocation

1 messages · Page 1 of 1 (latest)

elder tundra
#

Hello,

I'm currently translating some C code to Zig, as an exercise.

I wanted to translate these lines:

uint32_t* pixels = malloc(width * height * sizeof(pixels[0]));
float* depth = malloc(width * height * sizeof(depth[0]));

At first, I thought that I could do this:

var g_pixels: ?*u32 = null;
var g_depth: ?*f32 = null;

and use the std.heap.FixedBufferAllocator.

But it turns out that std.heap.FixedBufferAllocator can only allocate buffer of type u8. So I didn't understood how to convert that to u32 and f32.

As I've been tinkering around, I've ended up with the following monstrosity:

const k_screen_width: i32 = 320;
const k_screen_height: i32 = 200;
const k_screen_pixels: i32 = k_screen_width * k_screen_height;

var g_pixels: []u8 = undefined;
var g_depth: []u8 = undefined;
var g_pixels_fba: std.heap.FixedBufferAllocator = undefined;
var g_depth_fba: std.heap.FixedBufferAllocator = undefined;

...

fn game_memory_init() void {
    g_pixels_fba = std.heap.FixedBufferAllocator.init(g_pixels);
    const pixels_allocator = g_pixels_fba.allocator();
    const pixels_memory: error{OutOfMemory} = try pixels_allocator.alloc(u32, k_screen_pixels);
    try pixels_memory.len == k_screen_pixels;
    try @TypeOf(pixels_memory) == []u32;

   ...
}

Which, of course, doesn't work, because it simply shows that I don't understand what I'm doing.

As I struggle with the new concepts of allocation, and with the intricaties of the Zig syntax, I would like to know what is the Zig way to convert this C code:

uint32_t* pixels = malloc(width * height * sizeof(pixels[0]));
float* depth = malloc(width * height * sizeof(depth[0]));

Thank you very much for your attention.

rough smelt
#
const pixels = std.heap.c_allocator.alloc(u32, width * height);
const depth = std.heap.c_allocator.alloc(f32, width * height);

is more or less the direct translation but since it uses the C allocator it has a dependency on linking libc

#

the FixedBufferAllocator isnt limited to allocating u8 buffers, its limited to being backed by them. the FBA basically takes a byte array and uses it as an allocator interface rather than making syscalls to allocate on the heap dynamically. its mainly used with stack allocated arrays so you can avoid the heap entirely

#
const k_screen_width: i32 = 320;
const k_screen_height: i32 = 200;
const k_screen_pixels: i32 = k_screen_width * k_screen_height;

...

fn game_memory_init(allocator: std.mem.Allocator) !void {
  const pixels = try allocator.alloc(u32, k_screen_pixels);
  const depth = try allocator.alloc(f32, k_screen_pixels);
  ...
  // probably return the slices or make it part of a struct somewhere, globals arent too common
}

idiomatic zig takes an allocator as a function argument to anything that allocates

elder tundra
#

Thank you for your answer. I'll go with the C allocator for this one, as pixels and depth must be passed to a C function.

elder tundra
#

@rough smelt actually, the c_allocator worked, as long as I wasn't calling it…

I get the following compilation error, using the latest zig dev bin.

Build Summary: 0/3 steps succeeded; 1 failed (disable with --summary none)
install transitive failure
└─ install rockdodge transitive failure
   └─ zig build-exe rockdodge Debug native 1 errors
/usr/lib/zig/lib/std/heap.zig:75:33: error: comptime call of extern function
            if (c.posix_memalign(&aligned_ptr, eff_alignment, len) != 0)

/usr/lib/zig/lib/std/heap.zig:120:28: note: called from here
        return alignedAlloc(len, log2_align);

/usr/lib/zig/lib/std/mem/Allocator.zig:86:29: note: called from here
    return self.vtable.alloc(self.ptr, len, ptr_align, ret_addr);

/usr/lib/zig/lib/std/mem/Allocator.zig:225:35: note: called from here
    const byte_ptr = self.rawAlloc(byte_count, log2a(alignment), return_address) orelse return Error.OutOfMemory;

/usr/lib/zig/lib/std/mem/Allocator.zig:211:40: note: called from here
    return self.allocBytesWithAlignment(alignment, byte_count, return_address);

/usr/lib/zig/lib/std/mem/Allocator.zig:205:75: note: called from here
    const ptr: [*]align(a) T = @ptrCast(try self.allocWithSizeAndAlignment(@sizeOf(T), a, n, return_address));

/usr/lib/zig/lib/std/mem/Allocator.zig:129:41: note: called from here
    return self.allocAdvancedWithRetAddr(T, null, n, @returnAddress());

src/main.zig:51:51: note: called from here
const g_pixels: ?*u32 = std.heap.c_allocator.alloc(u32, k_screen_width * k_screen_height);

Please forgive me for not being more helpful, but I'm quite lost on this one.

hushed ermine
#

Checkout this answer and see if it helps:
#1152185687190274069 message

elder tundra
#

Thank you for the answer @hushed ermine.

#

I've set the initialization somewhere else, and got a different error.

#
   └─ zig build-exe rockdodge Debug native 1 errors
src/main.zig:194:42: error: expected type '?*u32', found 'error{OutOfMemory}![]u32'
    g_pixels = std.heap.c_allocator.alloc(u32, k_screen_width * k_screen_height);
               ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

The code is the following:

var g_pixels: ?*u32 = null;

...

fn game_init() void {
  g_pixels = std.heap.c_allocator.alloc(u32, k_screen_width * k_screen_height);
}
#

For a second, I thought that I could fix it by doing

var g_pixels: !?*u32 = null;
hushed ermine
#

The error is saying that you have either catch or pass the error up. Try modifying your game_init as below:

fn game_init() !void {
  g_pixels = try std.heap.c_allocator.alloc(
    u32, k_screen_width * k_screen_height
  );
}

You can ignore my formatting; the key focus areas are the edits to the return type and the try before alloc.

elder tundra
#

Alright, I start to understand the use of try. Thank you @hushed ermine.
However I have one last error.

src/main.zig:194:16: error: expected type '?*u32', found '[]u32'
    g_pixels = try std.heap.c_allocator.alloc(u32, k_screen_width * k_screen_height);
#

That's also a mystery for me, how can a pointer and an array not be compatible.

hushed ermine
#

Zig is pretty good with type inference. As I am learning zig I let it guide me by allowing it to do type inference on assignment for me. Translated what that means is I avoid typing my variables. In your case just declare g_pixels to be the same type as what alloc claims to return.

It took me a while to wrap my head around pointers and slices. This link may help...
https://zig.news/toxi/typepointer-cheatsheet-3ne2

Zig NEWS

Hello (Zig) World! since I'm still not automatically remembering all the various combinatorial...

tame halo
#

this part of the zig manual is helpful if you are confused by the pointer types

elder tundra
elder tundra
# tame halo []u32 is a slice to zero or more u32, which in c would be a pointer and a length...

The problem I have, is that I'm allocating an array to pass it to a C function.

If I change ?*u32 to []32, I'll get the following error:

src/main.zig:197:16: error: expected type '[*c]u32', found '[]u32'
    c.b3d_init(g_pixels, g_depth, k_screen_width, k_screen_height, 90.0);
               ^~~~~~~~
zig-cache/o/1ae97091353a8d060eebb859c03f96c9/cimport.zig:23214:39: note: parameter type declared here
pub extern fn b3d_init(pixel_buffer: [*c]u32, depth_buffer: [*c]f32, w: c_int, h: c_int, fov: f32) void;
                                     ~^~~~~~

So, naturally, I'll change my []u32 to a [*c]u32 as the compiler ask.
But then I get bat to square one with this error:

src/main.zig:194:16: error: expected type '[*c]u32', found '[]u32'
    g_pixels = try std.heap.c_allocator.alloc(u32, k_screen_width * k_screen_height);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tame halo
#

change it back to a slice but do this for passing it to the c function

#

g_pixels.ptr

hushed ermine
shy kayak