我有C代码可以连接到。我选择使用mem::uninitialized
首先声明内存,然后调用C函数(UserInit
)进行初始化,然后使用它(在UserDoSomething
中)。
奇怪的是UserInit
和UserDoSomething
中对象的地址不同。为什么它以这种方式表现?
C代码:
typedef struct {
char* name;
int32_t age;
} User;
void
UserInit(User* u){
printf("in init: user addr: %p\n", u);
}
void
UserDoSomething(User* u){
printf("in do something user addr: %p\n", u);
}
void
UserDestroy(User* u){
free(u->name);
}
Rust FFI:
use std::mem;
use std::os::raw::c_char;
use std::ffi::CString;
#[repr(C)]
pub struct User{
pub name: *const c_char,
pub age: i32,
}
impl User {
pub fn new()-> User {
let ret: User = unsafe { mem::uninitialized() };
unsafe {
UserInit(&mut ret as *mut User)
}
ret
}
pub fn do_something(&mut self){
unsafe {
UserDoSomething(self as *mut User)
}
}
}
extern "C" {
pub fn UserInit(u:*mut User);
pub fn UserDoSomething(u:*mut User);
pub fn UserDestroy(u:*mut User);
}
Rust测试:
mod ffi;
use ffi::User;
fn main() {
let mut u = User::new();
u.do_something();
}
理论上,它应该输出相同的地址,但它不会:
> cargo run
Running `target/debug/learn`
in init: user addr: 0x7fff5b948b80
in do something user addr: 0x7fff5b948ba0
答案 0 :(得分:5)
这就是Rust的工作方式。
不仅如此, C的工作原理:
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct {
char* name;
int32_t age;
} User;
void
UserInit(User* u){
printf("in init: user addr: %p\n", u);
}
void
UserDoSomething(User* u){
printf("in do something user addr: %p\n", u);
}
void
UserDestroy(User* u){
free(u->name);
}
User rust_like_new(void) {
User u;
UserInit(&u);
return u;
}
int main(int argc, char *argv[]) {
User u = rust_like_new();
UserDoSomething(&u);
}
in init: user addr: 0x7fff506c1600
in do something user addr: 0x7fff506c1630
通常,您不关心容器的地址,只关心容器的地址。
如果我堆分配
User
,地址不会改变,但如果我使用Box
(let u = Box::new(User::new())
),它仍然会改变。
在Rust和C中也会发生同样的事情。Box<User>
或User *
本身的地址会发生变化。 Box<User>
或User *
的值(指向的东西)将保持一致。
mod ffi {
use std::mem;
use std::os::raw::c_char;
#[repr(C)]
pub struct User {
pub name: *const c_char,
pub age: i32,
}
impl User {
pub fn new() -> Box<User> {
let mut ret: Box<User> = Box::new(unsafe { mem::uninitialized() });
unsafe { UserInit(&mut *ret) }
ret
}
pub fn do_something(&mut self) {
unsafe { UserDoSomething(self) }
}
}
extern "C" {
pub fn UserInit(u: *mut User);
pub fn UserDoSomething(u: *mut User);
}
}
use ffi::User;
fn main() {
let mut u = User::new();
u.do_something();
}
in init: user addr: 0x10da17000
in do something user addr: 0x10da17000
如果您将<{1}}的引用传递给C 之前,则会将其移至User
,然后是,当地址移入{{1}时,地址会发生变化}。这相当于:
Box
请注意,Rust(和其他语言)允许执行RVO。但是,我认为打印出地址将取消此优化的资格,因为如果启用了RVO,行为将会改变。您需要查看调试器或生成的程序集。
答案 1 :(得分:3)
这就是Rust的工作原理。物体没有身份;将它们移出函数(例如User::new
)会将它们移动到新位置,从而更改它们的地址。
如果您的C代码需要稳定的地址,则需要使您的Rust User
包含Box
或原始指针。