为结构创建*mut*mut

han*_*ast 5 ffi rust

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

功能签名pthread_join:

pub unsafe extern fn pthread_join(native: pthread_t,
                                  value: *mut *mut c_void)
                                  -> c_int
Run Code Online (Sandbox Code Playgroud)

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

pthread_t   tid1;
struct foo  *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
err = pthread_join(tid1, (void *)&fp);
Run Code Online (Sandbox Code Playgroud)

我想出了这段代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

但我看到的错误是:

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>`
Run Code Online (Sandbox Code Playgroud)

甚至可以使用演员表实现这一点,还是我需要转换?

use*_*342 5

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

这就是为什么pthread_join接收 a void **,即指向 a 的指针void *。这种输出参数能够pthread_join存储(返回)void *由新完成的线程提供的指针。线程可以通过将指针传递给pthread_exit或从start_routine传递给返回指针来提供指针pthread_create。在 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()
Run Code Online (Sandbox Code Playgroud)

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

struct ThreadResult { ... };

...
ThreadResult *result = malloc(sizeof(struct ThreadResult));
result->field1 = value1;
...
pthread_exit(result);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,加入线程的 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
}
Run Code Online (Sandbox Code Playgroud)

  • @hansaplast 确实复制了“值”-但“值”只是指针本身的值,即地址。(更准确地说,它是一个指针大小的整数,可能包含也可能不包含实际的可取消引用地址——例如,它也可以是 NULL。)标准没有指定这个指针是指向堆分配的数据还是静态分配的数据,或者它是否指向任何东西(如果它是 NULL 或任意整数转换为 `void *`)。这完全是调用 `pthread_exit` 的代码和调用 `pthread_join` 的代码之间的契约问题。 (2认同)

Mat*_* M. 5

这里有几个问题:

  • 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 _);
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是泄漏内存(由于into_raw),通常应该将内存推回Boxwithfrom_raw以便Foo调用析构函数并释放内存。

  • @hansaplast:`p` 是一个原始指针,原始指针不表示所有权(或缺乏)。Rust 不知道 `sample` 现在是否负责删除内存,所以它不会做任何事情。如果你不把它塞进‘盒子’,它就会泄漏。请注意,泄漏是安全的,少量是无害的(在大量时,您会遇到问题,但是 D 或 Clang 编译器例如不会释放它们的内存以使其更快)。 (2认同)