【问题标题】:How to get a pointer to a containing struct from a pointer to a member?如何从指向成员的指针中获取指向包含结构的指针?
【发布时间】:2016-08-07 23:40:20
【问题描述】:

我有一个类型:

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

而我知道的一个指针是指向Foo 中的memberB 的指针:

p: *const Baz

获取指向原始结构 Foo 的新指针 p: *const 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 宏。

【问题讨论】:

  • 为什么不能直接将*const Foo 传递给C 代码?
  • 在这个特定的例子中,FFI 给了我一个*const Baz,我应该从中检索原始对象。如果我可以简单地传递原始对象,我会,但这不是一个选择。

标签: pointers rust


【解决方案1】:

解引用表达式产生一个左值,但该左值实际上不是 读取 的,我们只是对其进行指针数学运算,所以 理论上,它应该是定义明确。不过这只是我的解释。

我的解决方案涉及使用空指针来检索字段的偏移量,因此它比您的更简单,因为它避免了一个减法(我们将减去 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);
}

【讨论】:

【解决方案2】:

通过实现RFC 2582, raw reference MIR operator,现在可以在没有结构实例且不调用未定义行为的情况下获取结构中字段的地址。

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);
}

这个实现是棘手和微妙的。最好使用维护良好的 crate,例如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 源文件。但是,这将跨越多个编译器调用,因此您依赖的结构布局不会在这些调用之间发生变化。使用具有已知表示的东西会降低这种风险。

另见:

【讨论】:

    猜你喜欢
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-28
    • 1970-01-01
    • 2021-12-22
    • 2016-02-25
    • 1970-01-01
    相关资源
    最近更新 更多