Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a pointer to a containing struct from a pointer to a member?

Tags:

pointers

rust

I have a type:

struct Foo {
    memberA: Bar,
    memberB: Baz,
}

and a pointer which I know is a pointer to memberB in Foo:

p: *const Baz

What is the correct way to get a new pointer p: *const Foo which points to the original struct Foo?

My current implementation is the following, which I'm pretty sure invokes undefined behavior due to the dereference of (p as *const Foo) where p is not a pointer to a Foo:

let p2 = p as usize -
    ((&(*(p as *const Foo)).memberB as *const _ as usize) - (p as usize));

This is part of FFI - I can't easily restructure the code to avoid needing to perform this operation.

This is very similar to Get pointer to object from pointer to some member but for Rust, which as far as I know has no offsetof macro.

like image 684
Mystor Avatar asked Aug 07 '16 23:08

Mystor


People also ask

Can a pointer be a member of a structure in C?

A pointer could be a member of structure, but you should be careful before creating the pointer as a member of structure in C. Generally we take a pointer as a member when we don’t know the length of the data which need to store. The above structure “Info” contains two members, integer variable (iLen) and a pointer to the character (pcName).

What is a pointer in data structure?

Pointer to structure holds the address of an entire structure. Mainly, these are used to create the complex data structures such as linked lists, trees, graphs and so on.

What is a pointer to a struct in go?

Structs are the building blocks of data structures in Go. In this post, we are going to explore pointers to a struct in GoLang. The syntax of a pointer to a struct is just like any other pointer. It is of the same form as others. Here is the syntax in use. Accessing the fields through the pointers is really easy and very straightforward.

How do you create a pointer variable in a struct?

Structures can be created and accessed using pointers. A pointer variable of a structure can be created as below: struct name { member1; member2; . . }; int main() { struct name *ptr; }. Here, the pointer variable of type struct name is created.


2 Answers

The dereference expression produces an lvalue, but that lvalue is not actually read from, we're just doing pointer math on it, so in theory, it should be well defined. That's just my interpretation though.

My solution involves using a null pointer to retrieve the offset to the field, so it's a bit simpler than yours as it avoids one subtraction (we'd be subtracting 0). I believe I saw some C compilers/standard libraries implementing offsetof by essentially returning the address of a field from a null pointer, which is what inspired the following solution.

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

We can also define our own offset_of! macro:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        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);
}
like image 131
Francis Gagné Avatar answered Oct 19 '22 16:10

Francis Gagné


With the implementation of RFC 2582, raw reference MIR operator, it is now possible to get the address of a field in a struct without an instance of the struct and without invoking undefined behavior.

use std::{mem::MaybeUninit, ptr};

struct Example {
    a: i32,
    b: u8,
    c: bool,
}

fn main() {
    let offset = unsafe {
        let base = MaybeUninit::<Example>::uninit();
        let base_ptr = base.as_ptr();
        let c = ptr::addr_of!((*base_ptr).c);
        (c as usize) - (base_ptr as usize)
    };
    println!("{}", offset);
}

The implementation of this is tricky and nuanced. It is best to use a crate that is well-maintained, such as memoffset.


Before this functionality was stabilized, you must have a valid instance of the struct. You can use tools like once_cell to minimize the overhead of the dummy value that you need to create:

use once_cell::sync::Lazy; // 1.4.1

struct Example {
    a: i32,
    b: u8,
    c: bool,
}

static DUMMY: Lazy<Example> = Lazy::new(|| Example {
    a: 0,
    b: 0,
    c: false,
});

static OFFSET_C: Lazy<usize> = Lazy::new(|| {
    let base: *const Example = &*DUMMY;
    let c: *const bool = &DUMMY.c;
    (c as usize) - (base as usize)
});

fn main() {
    println!("{}", *OFFSET_C);
}

If you must have this at compile time, you can place similar code into a build script and write out a Rust source file with the offsets. However, that will span multiple compiler invocations, so you are relying on the struct layout not changing between those invocations. Using something with a known representation would reduce that risk.

See also:

  • How do I create a global, mutable singleton?
  • How to create a static string at compile time
like image 45
Shepmaster Avatar answered Oct 19 '22 16:10

Shepmaster