Hello! I’m quite stumped on something and was hoping that someone here can help diagnose what’s going on. I’m doing some ptr arithmatic stuff, and something isn’t working as I was expecting it to, here’s an example test to reproduce what I’m seeing:
const OFFSET = 13;
fn getU64Ptr(data: *[1024]u8) *u64 {
var bytes align(@alignOf(u64)) = data[OFFSET .. OFFSET + @sizeOf(u64)];
const u64_ptr: *u64 = @ptrCast(&bytes);
return u64_ptr;
}
fn initU64Value(data: *[1024]u8) void {
const ptr = getU64Ptr(data);
std.debug.print("PTR 2 {d}\n", .{ptr});
ptr.* = 0;
std.debug.print("PTR 2 VAL {d}\n", .{ptr.*});
}
test "example" {
var data: [1024]u8 = undefined;
// set up some initial bytes
for (0..1024) |i| {
var intBytes: [@sizeOf(usize)]u8 = undefined;
mem.writePackedIntNative(usize, intBytes[0..], 0, i);
data[i] = intBytes[0];
}
const ptr = getU64Ptr(&data);
std.debug.print("PTR 1 {*}\n", .{ptr});
std.debug.print("PTR 1 VAL {d}\n", .{ptr.*});
initU64Value(&data);
std.debug.print("PTR 1 VAL {d}\n", .{ptr.*});
}
I really expected the last log to be 0, but it’s not. It seems that getU64Ptr doesn’t return the same pointer every time, which I really was not expecting. Can anyone explain why?
bytes
is a slice, &bytes
returns the address of the slice, you need bytes.ptr
.
bytes
must be a const
since it is not modified.
With the @ptrCast
you also need @alignCast
, and this is not going to work for OFFSET=13.
fn getU64Ptr(data: *[1024]u8) *u64 {
const bytes align(@alignOf(u64)) = data[OFFSET .. OFFSET + @sizeOf(u64)];
const u64_ptr: *u64 = @ptrCast(@alignCast(bytes.ptr));
return u64_ptr;
}
1 Like
Ah I see, makes sense thank you! I’m following along with some c code, the gist is that there’s a buffer with data adhering to a custom format like
| byte 0 | byte 1 | bytes n - n+8 |
| node_type | ... | u64 id. |
I’m trying to get a pointer to the u64 value so it can be read/updated. The c code I’m following gets a void ptr to that location, and it seems it automatically can be cast into a u64 ptr. Are you familiar with a way to do this in zig?
Do you have any alignment guarantees on that buffer? If you don’t, you’re not going to enjoy trying to cast that buffer.
Instead, I would go the opposite way:
- Make a value of type T
- Get a slice of bytes to memory of said value
@memcpy
the bytes from the buffer into the value
- use your value
Very safe bet, imo - no casting shenanigans to buffers that could be aligned to anything or everything.
Edited: Using @mnemnion 's flag value.
const std = @import("std");
pub fn main() !void {
// buffer to your source bytes
var buffer: [10]u8 = .{ 0 } ** 10;
// consider default value that can be used for debugging
var value: usize = 0xa5a5a5a5a5a5a5a5;
// cast to a slice of target bytes
const bytes = std.mem.asBytes(&value);
// copy your source to target
@memcpy(bytes, buffer[2..]);
// check that you get your result
std.debug.print("Should be zero: {}\n", .{ value });
}
3 Likes
Might be my paranoia speaking, but when doing weird casting stuff I like to ‘color’ all the bytes, usually like this:
// even more obvious flag
var value: usize = 0xa5a5a5a5a5a5a5a5;
Same principle though, just slightly higher odds of catching in a debugger if I clip some edge or other. I even know that decimal printing that value starts with 1193 as long as the high four is still there.
2 Likes
Ohh that should work for my needs! Awesome, thanks a bunch folks.
Just for my own edification, is doing a mem.asBytes and @memcpy similar in performance as writing to a pointer?
I’m new to this lower level programming, but seems like doing someT.* = newT
must be doing something similar to @memcpy(newTAsBytes, someTAsBytes) under the hood no?
1 Like
Give a fish or teach to fish… your choice, my friend
How to use Zig with Compiler Explorer: https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/
Compiler Explorer: godbolt.org
1 Like
Yeah - same, it’s 11:00am here… writing good code is an “after noon/coffee” thing haha… cheers…
2 Likes
Thanks for sharing! I’ll explore
1 Like