我有一个类型:
struct Foo {
    memberA: Bar,
    memberB: Baz,
}
和指针,我知道是一个指针memberB在Foo:
p: *const Baz
获取指向p: *const Foo原始结构的新指针的正确方法是什么Foo?
我当前的实现如下,我很确定调用未定义的行为,因为取消引用(p as *const Foo)where p不是指向a的指针Foo:
let p2 = p as usize -
    ((&(*(p as *const Foo)).memberB as *const _ as usize) - (p as usize));
这是FFI的一部分 - 我不能轻易地重构代码以避免需要执行此操作.
这与指向某个成员的指针的Get指针非常类似,但对于Rust,据我所知,它没有offsetof宏.
dereference表达式产生一个左值,但是左值实际上并没有从中读取,我们只是对它进行指针数学运算,因此从理论上讲,它应该被很好地定义.这只是我的解释.
我的解决方案涉及使用空指针来检索字段的偏移量,因此它比你的更简单,因为它避免了一次减法(我们将减去0).我相信我看到一些C编译器/标准库offsetof通过从空指针返回一个字段的地址来实现,这是以下解决方案的灵感来源.
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);
}
我们还可以定义自己的offset_of!宏:
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);
}
随着实施RFC 2582,原始参考MIR运营商,现在可以得到一个字段的一个结构的地址,而不该结构的一个实例,而无需调用未定义的行为。
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);
}
这的实现是棘手和微妙的。最好使用维护良好的板条箱,例如memoffset。
在稳定此功能之前,您必须拥有该结构的有效实例。您可以使用类似工具once_cell来最小化您需要创建的虚拟值的开销:
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);
}
如果您必须在编译时使用它,您可以将类似的代码放入构建脚本中,并用偏移量写出 Rust 源文件。但是,这将跨越多个编译器调用,因此您依赖于在这些调用之间不会更改的结构布局。使用具有已知表示的东西会降低这种风险。
也可以看看: