Zig NEWS

loading...
Cover image for Using Zig and Translate-C to understand weird C code

Using Zig and Translate-C to understand weird C code

Sobeston
・2 min read

void f(int *a) {<br>
  void *p = &a;<br>
  ***(int *(*)[])p = 1;<br>
}

@rep_stosq_void on Twitter posted this strange sample of C code, and I wanted to show my process of understanding this contrived C code.
https://twitter.com/rep_stosq_void/status/1446504706319294475

After running zig translate-c x.c > x.zig, I got the following output:

pub export fn f(arg_a: [*c]c_int) void {
    var a = arg_a;
    var p: ?*c_void = @ptrCast(?*c_void, &a);
    @ptrCast(
        [*c][*c]c_int,
        @alignCast(@import("std").meta.alignment([*c]c_int), &@ptrCast(
            [*c][*c][*c]c_int,
            @alignCast(@import("std").meta.alignment([*c][*c]c_int), p),
        ).*),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

From here I removed the unnecessary calls to std.meta.alignment by adding alignment data to p. I also converted [*c]T pointers to [*]T and *T, depending on my assumptions of how the code works. I also removed the optional from p, and stripped away arg_a.

Now we've got something a bit more faithful to the original.

fn f(a: *c_int) void {
    const p = @ptrCast(*align(@alignOf(usize)) const c_void, &a);
    @ptrCast(
        *const [*]c_int,
        &@ptrCast(*const *[*]c_int, p).*,
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

We can now transform &@ptrCast(*const *[*]c_int, p).* into @ptrCast(*const *[*]c_int, p), as &x.* is equivalent to x.

fn g(a: *c_int) void {
    const p = @ptrCast(*align(@alignOf(usize)) const c_void, &a);
    @ptrCast(
        *const [*]c_int,
        @ptrCast(*const *[*]c_int, p),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

As we can see p is a pointer to the argument a, we can change its ptrCast to the more appropriate type *const *i32. The const is needed as arguments are immutable.

fn h(a: *c_int) void {
    const p = @ptrCast(*const *i32, &a);
    @ptrCast(
        *const [*]c_int,
        @ptrCast(*const *[*]c_int, p),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

After substituting in p.

fn i(a: *c_int) void {
    @ptrCast(
        *const [*]c_int,
        @ptrCast(
            *const *[*]c_int,
            @ptrCast(*const *i32, &a),
        ),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

There's some many-pointers ([*]T) here, which we should replace with single pointers (*T). This is because I can tell that there aren't really any arrays at play here.

fn j(a: *c_int) void {
    @ptrCast(
        *const *c_int,
        @ptrCast(
            *const **c_int,
            @ptrCast(*const *i32, &a),
        ),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

Here we can remove the unnecessary ptrCast around &a, as &a is already of type *const *i32.

fn k(a: *c_int) void {
    @ptrCast(
        *const *c_int,
        @ptrCast(
            *const **c_int,
            &a,
        ),
    ).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

Here we can see a ptrCast from *const *i32 to *const **c_int, back to *const *c_int. Let's remove that.

fn l(a: *c_int) void {
    (&a).*.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can transform (&x).* into x.

fn m(a: *c_int) void {
    a.* = 1;
}
Enter fullscreen mode Exit fullscreen mode

Perhaps the answer is disappointing.

Discussion (1)

Collapse
kristoff profile image
Loris Cro

Perhaps the answer is disappointing

Kinda, yeah, lol.