【问题标题】:Create *mut *mut to a struct为结构创建 *mut *mut
【发布时间】:2023-03-13 00:36:01
【问题描述】:

我正在尝试使用指向我的结构的指针调用pthread_join,以便 C 线程可以将结构填充到我指向的内存中。 (是的,我知道这是非常不安全的..)

pthread_join的函数签名:

pub unsafe extern fn pthread_join(native: pthread_t,
                                  value: *mut *mut c_void)
                                  -> c_int

我这样做是为了将 C 代码从一本书移植到 Rust。 C代码:

pthread_t   tid1;
struct foo  *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
err = pthread_join(tid1, (void *)&fp);

我想出了这个代码:

extern crate libc;
use libc::{pthread_t, pthread_join};

struct Foo {}

fn main() {
    let tid1:pthread_t = std::mem::uninitialized();
    let mut fp:Box<Foo> = std::mem::uninitialized();
    let value = &mut fp;
    pthread_join(tid1, &mut value);
}

但我看到的错误是:

error[E0308]: mismatched types
  --> src/bin/11-threads/f04-bogus-pthread-exit.rs:51:24
   |
51 |     pthread_join(tid1, &mut value);
   |                        ^^^^^^^^^^ expected *-ptr, found mutable reference
   |
   = note: expected type `*mut *mut libc::c_void`
              found type `&mut &mut std::boxed::Box<Foo>`

是否有可能仅使用强制转换来实现这一点,还是我需要转化?

【问题讨论】:

标签: rust ffi


【解决方案1】:

这里有几个问题:

  • Box 是指向堆分配资源的指针,您可以使用 Box::into_raw(some_box) 提取指针本身,
  • 引用不会被强制转换为指针(即使它们具有相同的表示),您需要显式强制转换,
  • 您需要将具体类型转换为c_void,类型推断或许可以做到这一点
  • 你有一个对一个指针的引用,你需要一个指向指针的指针;你的间接层级太多了。

让我们开始吧:

// pthread interface, reduced
struct Void;

fn sample(_: *mut *mut Void) {}

// actual code
struct Foo {}

fn main() {
    let mut p = Box::into_raw(Box::new(Foo{})) as *mut Void;
    sample(&mut p as *mut _);
}

请注意,这是内存泄漏(into_raw 的结果),通常应该将内存推回到带有from_rawBox 中,以便调用Foo 的析构函数和要调用的内存释放。

【讨论】:

  • “内存泄漏”是什么意思?并且:Box::new 不是在完成into_raw 部分并导致悬空指针后立即丢弃吗?
  • @hansaplast:如果是这样,into_raw 的意义何在?你读过我链接到的文档吗?它准确地解释了函数的行为。
  • 我没有看到链接。非常感谢您提供的文档,它回答了我上面的问题。我把它塞回盒子里,但如果我不这样做会怎样?当 main 结束并且 p 被丢弃时,内存不是被释放了吗?
  • @hansaplast: p 是一个原始指针,原始指针不表示所有权(或缺乏)。 Rust 不知道sample 现在是否负责删除内存,所以它不会做任何事情。如果你不把它塞进Box,它就会泄漏。请注意,泄漏是安全的,少量泄漏是无害的(大量泄漏会出现问题,但是 D 或 Clang 编译器不会释放内存以提高速度)。
  • 应用于@hansaplast的问题,这个解决方案无论如何都会泄漏内存,因为into_raw返回的指针被pthread_join无条件覆盖,所以原来的Box是完全失去了。如果您确实p 的(新)值转回(新)Box,除非它首先来自 Rust(这似乎不太可能,因为您正在呼吁C 来获取它),它可能会在分配器最终被丢弃时崩溃或损坏。
【解决方案2】:

代码不能像写的那样工作;那是因为 C 线程并没有真正在您指向的内存中“填充结构”。它负责分配自己的内存(或预先从另一个线程接收)并填充它。 C 线程“返回”的唯一东西是单个地址,而这个地址被pthread_join 拾取。

这就是为什么pthread_join 会收到void **,即指向void * 的指针。这种输出参数使pthread_join能够存储(返回)由新完成的线程提供的void *指针。线程可以通过将指针传递给pthread_exit 或从传递给pthread_createstart_routine 返回指针来提供指针。在 Rust 中,可以使用如下代码接收原始指针:

let mut c_result: *mut libc::c_void = ptr::null_mut();
libc::pthread_join(tid1, &mut c_result as *mut _);
// C_RESULT now contains the raw pointer returned by the worker's
// start routine, or passed to pthread_exit()

返回的指针指向的内存的内容和大小是正在加入的线程和正在加入它的线程之间的合同问题。如果工作线程是用 C 实现的,并且设计为由其他 C 代码调用,那么一个明显的选择是它为结果结构分配内存,填充它,并提供指向已分配内存的指针。例如:

struct ThreadResult { ... };

...
ThreadResult *result = malloc(sizeof(struct ThreadResult));
result->field1 = value1;
...
pthread_exit(result);

在这种情况下,加入线程的 Rust 代码可以通过复制 C 结构并获取其所有权来解释结果:

// obtain a raw-pointer c_result through pthread_join as 
// shown above:
let mut c_result = ...;
libc::pthread_join(tid1, &mut c_result as *mut _);

#[repr(C)]
struct ThreadResult { ... } // fields copy-pasted from C

unsafe {
    // convert the raw pointer to a Rust reference, so that we may
    // inspect its contents
    let result = &mut *(c_result as *mut ThreadResult);

    // ... inspect result.field1, etc ...

    // free the memory allocated in the thread
    libc::free(c_result);
    // RESULT is no longer usable
}

【讨论】:

  • IMO 指针指向的内存需要已经分配(我在上面的示例中有这个错误,我想它需要是std::mem::zeroed())。来自pthread_join 的手册页:“从使用非 NULL value_ptr 参数的成功 pthread_join() 调用返回时,终止线程传递给 pthread_exit() 的值存储在 value_ptr 引用的位置”
  • @hansaplast 您只需要为指针对指针分配存储空间。您以这种方式获得的指针将指向什么内存是任何人的猜测。它甚至不需要是一个有效的地址——例如,如果线程被取消,result_ptr 将包含libc::PTHREAD_CANCELED(在 Linux 上)。
  • 啊哈,libc::PTHREAD_CANCELED被写入第一个指针指向的位置,所以它不再是指针指向的指针。但是当线程将值传递给pthread_exit 时,另一种情况呢?该值不是复制到指针指向的位置吗?至少我是如何理解手册页中的解释的
  • @hansaplast 确实“值”被复制了——但“值”只是指针本身的值,即地址。 (更准确地说,它是一个指针大小的整数,可能包含也可能不包含实际的可解引用地址 - 例如也可以为 NULL。)标准没有指定此指针是指向堆分配的数据,还是指向静态分配的数据数据,或者它是否指向任何东西(如果它是 NULL 或转换为void * 的任意整数)。这完全是调用pthread_exit 的代码和调用pthread_join 的代码之间的约定问题。
  • @hansaplast 堆栈分配的结构无法工作,因为堆栈是每个线程的,因此退出线程会破坏其堆栈。手册页是正确的,只要您了解被复制的“值”只是提供给pthread_exit 的指针。如果您考虑到 C 没有复制构造函数的概念,并且库函数(如 pthread_exit)不知道传递给它的结构的大小,那么很明显,更复杂的数据复制已经结束的问题。
猜你喜欢
  • 2022-11-23
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多