Zig NEWS

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

Posted on

Using Zig and Translate-C to understand weird C code

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.

Top comments (3)

Collapse
 
kristoff profile image
Loris Cro

Perhaps the answer is disappointing

Kinda, yeah, lol.

Collapse
 
vadavaski profile image
vadavaskki

You translate 1 line of C mess to 7 lines of Zig mess which are then manually simplified through transforms. Why not just apply transforms to the original C code?

Collapse
 
sobeston profile image
Sobeston