How to get pointer offset in bytes?
Asked Answered
P

3

8

While raw pointers in Rust have the offset method, this only increments by the size of the pointer. How can I get access to the pointer in bytes?

Something like this in C:

var_offset = (typeof(var))((char *)(var) + offset);
Pittel answered 28/10, 2016 at 17:40 Comment(4)
Have you tried converting the pointer to a byte pointer?Poss
tried but cast isn't allowed - casting `&MyStruct` as `*const u8` is invalid.Pittel
Glad to know I am not the only one who tried :DPoss
If you want to convert a borrowed pointer to a raw byte pointer, you have to do two casts in a row: let x: &T; x as *const T as *const u8.Cavil
P
11

TL;DR: This answer invokes Undefined Behavior, according to RFC-2582.

In particular, references must be aligned and dereferencable, even when they are created and never used.

There are also discussions that field accesses themselves impose extra requirements not solved by the proposed &raw, due to usage of getelementptr inbounds, see offsetof woes at the bottom of the RFC.


From the answer I linked to your previous question:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        //  Undefined Behavior: dereferences a null pointer.
        //  Undefined Behavior: accesses field outside of valid memory area.
        unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
    }
}

fn main() {
    let p: *const Baz = 0x1248 as *const _;
    let p2: *const Foo = ((p as usize) - offset_of!(Foo, memberB)) as *const _;
    println!("{:p}", p2);
}

We can see on the computation of p2 that a pointer can be converted painless to an integer (usize here), on which arithmetic is performed, and then the result is cast back to a pointer.

isize and usize are the universal byte-sized pointer types :)


Were RFC-2582 to be accepted, this implementation of offset_of! is my best shot:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        unsafe {
            //  Create correctly sized storage.
            //
            //  Note: `let zeroed: $ty = ::std::mem::zeroed();` is incorrect,
            //        a zero pattern is not always a valid value.
            let buffer = ::std::mem::MaybeUninit::<$ty>::uninit();

            //  Create a Raw reference to the storage:
            //  - Alignment does not matter, though is correct here.
            //  - It safely refers to uninitialized storage.
            //
            //  Note: using `&raw const *(&buffer as *const _ as *const $ty)`
            //        is incorrect, it creates a temporary non-raw reference.
            let uninit: &raw const $ty = ::std::mem::transmute(&buffer);

            //  Create a Raw reference to the field:
            //  - Alignment does not matter, though is correct here.
            //  - It points within the memory area.
            //  - It safely refers to uninitialized storage.
            let field = &raw const uninit.$field;

            //  Compute the difference between pointers.
            (field as *const _ as usize) - (uninit as *const_ as usize)
        }
    }
}

I have commented each step with the reasons I believe they are sound, and why some alternatives are not -- something I encourage heavily in unsafe code -- and hopefully not missed anything.

Poss answered 28/10, 2016 at 18:7 Comment(4)
This is wrong; taking a reference of a value derived from a null pointer is undefined behavior: references must be aligned and dereferencable, even when they are created and never used. (From Kaz Wesley)Balsam
In that case, does a 'right' answer exist? Or is the only solution 'technically undefined', but 'working in practice'.Pittel
@Pittel that's correct, but there's support in nightly for doing it The Right Way (I'm hoping that the non-answer answer below is updated to show that).Balsam
@Shepmaster: Ah! Seems like the rules are shifting, or more accurately that uncertain code patterns are now ruled out. I've put a disclaimer while I think about it.Poss
P
1

Thanks to @Matthieu M.'s answer, this can be done using pointer offsets, heres a reusable macro:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        &(*(0 as *const $ty)).$field as *const _ as usize
    }
}

macro_rules! check_type_pair {
    ($a:expr, $b:expr) => {
        if false {
            let _type_check = if false {$a} else {$b};
        }
    }
}

macro_rules! parent_of_mut {
    ($child:expr, $ty:ty, $field:ident) => {
        {
            check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
            let offset = offset_of!($ty, $field);
            &mut *(((($child as *mut _) as usize) - offset) as *mut $ty)
        }
    }
}

macro_rules! parent_of {
    ($child:expr, $ty:ty, $field:ident) => {
        {
            check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
            let offset = offset_of!($ty, $field);
            &*(((($child as *const _) as usize) - offset) as *const $ty)
        }
    }
}

This way, when we have a field in a struct, we can get the parent struct like this:

fn some_method(&self) {
    // Where 'self' is ParentStruct.field,
    // access ParentStruct instance.
    let parent = unsafe { parent_of!(self, ParentStruct, field) };
}

The macro check_type_pair helps avoid simple mistakes where self and ParentStruct.field aren't the same type. However its not foolproof when two different members in a struct have the same type.

Pittel answered 28/10, 2016 at 18:20 Comment(0)
S
1

As of Rust 1.62.0, the previous answers may produce the error reference to packed field is unaligned if the struct is #[repr(packed)].

The following offset_of! implementation uses addr_of! to avoid unaligned field references:

macro_rules! offset_of {
    ($type:ty, $field:tt) => ({
        let dummy = ::core::mem::MaybeUninit::<$type>::uninit();

        let dummy_ptr = dummy.as_ptr();
        let member_ptr = unsafe{ ::core::ptr::addr_of!((*dummy_ptr).$field) };
        
        member_ptr as usize - dummy_ptr as usize
    })
}

Source: https://internals.rust-lang.org/t/get-the-offset-of-a-field-from-the-base-of-a-struct/14163/4

Selfanalysis answered 15/2, 2023 at 4:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.