我有一个类型:
struct Foo {
memberA: Bar,
memberB: Baz,
}
和我知道的指针是指向memberB
中的Foo
的指针:
p: *const Baz
获取指向原始结构p: *const Foo
的新指针Foo
的正确方法是什么?
我当前的实现如下,由于(p as *const Foo)
取消引用p
不是指向Foo
的指针,我非常肯定会调用未定义的行为:
let p2 = p as usize -
((&(*(p as *const Foo)).memberB as *const _ as usize) - (p as usize));
这是FFI的一部分 - 我无法轻松重构代码以避免需要执行此操作。
这与Get pointer to object from pointer to some member非常相似,但对于Rust,据我所知,它没有offsetof
宏。
答案 0 :(得分:7)
dereference表达式产生一个左值,但是左值实际上并不是 read ,我们只是在它上面做指针数学,所以理论上,它应该明确定义。这只是我的解释。
我的解决方案涉及使用空指针来检索字段的偏移量,因此它比你的更简单,因为它避免了一次减法(我们减去0)。我相信我看到了一些实现offsetof
的C编译器/标准库,它实际上是从空指针返回一个字段的地址,这就是以下解决方案的灵感来源。
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);
}
答案 1 :(得分:2)
通过实现RFC 2582, raw reference MIR operator,现在可以在没有结构实例的情况下获取结构中的字段的地址,而无需调用未定义的行为。
不幸的是,该能力从Rust 1.47开始不稳定:
#![feature(raw_ref_macros)]
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::raw_const!((*base_ptr).c);
(c as usize) - (base_ptr as usize)
};
println!("{}", offset);
}
此操作的实现是棘手且细微的。最好使用维护良好的板条箱,例如memoffset。您可以使用unstable_raw
功能标志来选择使用此不稳定功能。
在稳定此功能之前,您必须 具有该结构的有效实例。您可以使用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源文件。但是,这将跨越多个编译器调用,因此您所依赖的结构布局在这些调用之间没有变化。使用具有已知表示形式的东西可以减少这种风险。
另请参阅: