Question about pointer arithmetic

I am learning Zig by following along with the Handmade Hero series and Casey does a lot of pointer arithmetic. Since Zig only allows unsigned ints for incrementing pointers I find myself doing this type of thing when dealing with computed offsets:

    const offset: i32 = @intCast(y * bitmap.pitch + x * BITMAP_BYTES_PER_PIXEL);
    var source: [*]u8 = @ptrCast(bitmap.memory);
    if (offset >= 0) {
        source += @as(usize, @intCast(offset));
    } else {
        source -= @as(usize, @intCast(-offset));
    }

Is there some other way of dealing with this situation that I am missing?

Using a ternary style if-statement can make it more concise, but I tend to find it less readable. I’m also experimenting with a helper function along the lines of this, but it needs some work to be more flexible:

pub fn incrementPointer(comptime T: type, pointer: [*]void, offset: i32) T {
    if (offset >= 0) {
        return @ptrCast(@alignCast(pointer + @as(usize, @intCast(offset))));
    } else {
        return @ptrCast(@alignCast(pointer - @as(usize, @intCast(-offset))));
    }
}

Looking forward to hearing your suggestions.

I think what you are probably looking for is @ptrFromInt() builtin to easily cast a ptr into it’s numerical representation. And @intFromptr() to get it back to it’s original type

also just a side note in C while it’s very common to do pointer arithmetic I don’t find myself missing it at all with Zig’s type system, so it might be worthwhile to translate it into actually using the type system instead of pointer arithmetic. :slight_smile:

5 Likes

Another way is to use slices (for positive offsets only):

var slice: []const u8 = source;
slice = slice[offset..];
2 Likes

I usually try to avoid pointer arithmetic, in cases where I use it, I try to avoid negative offsets and instead place everything in the positive direction only, relative to some base address which then can be a [*]T, I also use a T different from u8 when I have a more specific granularity, than just bytes.

1 Like

I absolutely agree with your assessment. Generally it’s much easier to work with actual memory addresses in situations where you need to. C pointer arithmetic is very low bang for the buck. It adds 4 when I use ++. Yey!

As Yoda said, “Do or do not”. If you’re working with memory addresses then work with memory addresses.

P.S. Memory addresses are easy to work with these days thanks to the flat memory model. That used to be considered the Holy Grail. We’ve reached it and now young people just take it for granted. You suck!

1 Like

One trick I’ve used for negative offsets is to creat a temporary and index into it.

var stack_top:  [*]u8 = ...;
var slot =  stack_top - offset;
slot[0] = some_val;

I think this is what you wanted:

pub fn incrementPointer(pointer: anytype, offset: i32) @TypeOf(pointer) {
    return if (offset >= 0) 
        pointer + @as(usize, offset)
    else
        pointer - @abs(offset);
}

The -offset is tricky because it can overflow if offset is minInt(i32). @abs does the negation and the casting all at once, therefore avoiding this problem.
Taking the pointer as anytype makes this work for both const and mutable pointers, otherwise you’d have to have two versions of this function.

2 Likes

Great suggestions! I’m definitely trying to use Zig approaches where reasonable, but I also need to stay relatively close to his implementation to avoid diverging too much for future lessons. The reason I need this right now is likely temporary in the codebase, so I won’t worry too much about it.

But I am curious about how to use the Zig type system to do this in a more idiomatic way. The current approach is to load the data and pack four values (alpha, red, green, blue) into a u32 per pixel using bit shifting. Then when rendering the u32 is broken back down to it’s individual values. It’s stored as raw data using [*]void and traversed using pointer arithmetic. What would a more Zig way of doing things be for storing and traversing bitmap data like this?

2 Likes

I’ve recently translated a C school project where I needed to make a 3D renderer using a terrible but mandatory wrapper around X11 provided by the school. Through this project, I gained some experience translating from C to Zig.

In my implementation, I used an ArrayList of colors, which were bit-shifted u32 values containing ARGB. Once the ArrayList was filled, I called the toOwnedSlice method on the ArrayList to obtain a []u32. Then, for indexing, I used the y * width + x approach. But if you want you can also have an ArrayList of []u32, insert slices from the first Arraylist to get a very cache friendly [][]u32

I don’t think this will cause much performance loss because the Zig standard library contains many well-made data structures, and comptime allows for easy precomputation. My C implementation rendered at about 200 FPS on the CPU only, while the Zig implementation easily achieved 500 FPS. Although part of this difference is due to not optimizing either implementation, but it also showcases that a simple implementation in Zig can be more optimized than a similar one in C. And I think part of that is due to the better semantic and the fact that my code wasn’t null checking everything every 5 seconds, also being able to avoid plenty of syscalls thanks to the arena and everything was an added bonus

2 Likes

There are a few ways to do it, but I think the simplest is to have a []u32 and to iterate over it with a regular for loop.

Another option would be to have a packed struct and have a slice of those:

// Regual u32 way
var buffer: []u32 = ...;
for (buffer) |pixel| {
   // do something with the u32 pixel here
}

// Packed struct
const Pixel = packed struct {
    alpha: u8,
    red: u8,
    green: u8,
   blue: u8,
};

var buffer: []Pixel = ...;
for (buffer) |pixel| {
   // do something iwth Pixel
}
3 Likes

This is what I would suggest. Something to know: packed structs are defined from the least significant bit (the native endianness doesn’t matter here), so if your conventional u32 is packed ARGB, you’ll want a layout like this:

const Pixel = packed struct(u32) {
      blue: u8,
      green: u8,
      red: u8,
      alpha: u8, 
};

If the order doesn’t matter, then it doesn’t matter, but knowing this will mean that you can use @bitCast to get the raw u32 back in its expected value, if you need to. I’m replying to share the layout rule, not to suggest a particular layout, to be clear.

One advantage of a packed struct, besides the compiler taking care of all the shifting logic for you, is that you can change the bitwise layout to whatever makes sense, if you need to, because the packing isn’t baked in to the source code.

3 Likes

Thanks for the detailed suggestions! I particularly like the idea of using packed structs for the reasons @mnemnion mentioned. There is a fair bit of code just doing the bit shifting so adjusting it in any way is more work than it has to be. At the same time the point of Handmade Hero is to build as much as possible by yourself, so it has been helpful to see the bit shifting done by hand.

2 Likes

To help you out (and for future people who have questions about this), I spent about an hour this morning writing a small class that enables pointer arithmetic on raw addresses based on a type signature GitHub - andrewCodeDev/random_access

5 Likes