#When to use C pointers

1 messages · Page 1 of 1 (latest)

crystal elm
#

I'm working on my first zig program, and it uses a mixture of zig and C.
I have a C function of the form:
int generate_array(MyType** ptr);
It creates an array of MyType, assigns the provided pointer to point to it and returns the size of the array.
After some playing around with a simple case (using typedef int MyType) I found the following zig code worked:

    var ptr: [*c]c_int = null;
    const sz = cMyCFile.generate_array(&ptr);
    defer {
        // Free the allocated memory
        if (ptr != null) {
            c.free(ptr);
        }
    }

    try stdout.print("generated array size {d}\n", .{sz});
    for (0..@intCast(sz)) |i| {
        var new_int: c_int = ptr[i];
        try stdout.print("  {d}\n", .{new_int});
    }

While trying to find this I came across c-pointers: https://zig.guide/working-with-c/c-pointers

Outside of automatically translated C code, the usage of [*c] is almost always a bad idea, and should almost never be used.
But my zig code is not automatically translated from C (though I'm guessing my C code counts as automatically translated code in this context), so would there be anyway for me to do the above without using a [*c]?

Have I taken the right approach here? This is just a warm-up to the real problem which involves much more complicated C types, and I basically want to write a function to copy the data into zig native types

Up until now, we have used the following kinds of pointers:

boreal merlin
#

[*c] only exists because translate-c does not have enough information about a pointer to effectively choose a zig pointer type. If you're the one writing code, you can pick the correct pointer type, which is never [*c]

#

if it's an array with an unknown length: [*]T, if it's an array with an unknown length that ends in a 0 element: [*:0]T, if it's a pointer to an array of comptime-time known length: *[N]T, if it's a pointer to a single item: *T, etc

#

[*c] is an amalgamation of all of them, because translate-c has to use them, and doesn't know which type it needs to choose

crystal elm
#

ok, in that case I should use [*]T, right?
But then what do I assign the variable when I declare it. If I leave it as is then I get the following build error:

src/main.zig:36:25: error: expected type '[*]c_int', found '@TypeOf(null)'
    var ptr: [*]c_int = null;
boreal merlin
#

zig pointers are not nullable, if you want an optional pointer, it needs to be an optional type: ?T

crystal elm
#

Thanks.
So I declare my ptr as

var ptr: ?[*]c_int = null;

But how do I then access the contents? The build now complains

src/main.zig:47:33: error: type '?[*]c_int' does not support indexing
        var new_int: c_int = ptr[i];
boreal merlin
#

you need to unwrap the possibility that the pointer is null, ptr.? and then access it

#

or, more ideally: check if ptr is null after generate_array; then store the unwrapped pointer type for the rest of the function, which will just be a [*]T

#

the ideal version of this would look something like

var ptr_optional: ?[*]c_int = null;
const sz = cMyCFile.generate_array(&ptr_optional);
defer c.free(ptr);

const ptr: [*]c_int = ptr_optional orelse return;

try stdout.print("generated array size {d}\n", .{sz});
for (0..@intCast(sz)) |i| {
    var new_int: c_int = ptr[i];
    try stdout.print("  {d}\n", .{new_int});
}
crystal elm
#

Ah, great thanks. I just got to something similar:

    var ptr: ?[*]c_int = null;
    const sz = cXrandr.generate_array(&ptr);
    if (ptr) |newPtr| {
        defer {
            // Free the allocated memory
            c.free(newPtr);
        }

        try stdout.print("generated array size {d}\n", .{sz});
        for (0..@intCast(sz)) |i| {
            var new_int: c_int = newPtr[i];
            try stdout.print("  {d}\n", .{new_int});
        }
    }
#

Thanks again @boreal merlin . You've been a great help, I've learnt a lot.

boreal merlin
#

you can call free on null, no reason to hide it behind a null check

crystal elm
#

Good point

obsidian fiber