为什么对象的地址会跨方法发生变化?

wan*_*gii 3 rust

我有C代码桥接到.我选择先使用mem::uninitialized声明内存,然后调用C函数(UserInit)进行初始化,然后使用它(in 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);
}
Run Code Online (Sandbox Code Playgroud)

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

锈测试:

mod ffi;

use ffi::User;

fn main() {
    let mut u = User::new();
    u.do_something();
}
Run Code Online (Sandbox Code Playgroud)

从理论上讲,它应该输出相同的地址,但它不会:

> cargo run
     Running `target/debug/learn`
in init: user addr: 0x7fff5b948b80
in do something user addr: 0x7fff5b948ba0
Run Code Online (Sandbox Code Playgroud)

She*_*ter 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);
}
Run Code Online (Sandbox Code Playgroud)
in init: user addr:        0x7fff506c1600
in do something user addr: 0x7fff506c1630
Run Code Online (Sandbox Code Playgroud)

通常,您不关心容器的地址,只关心它包含的内容.

如果我堆分配User,地址不会改变,但如果我使用Box(let u = Box::new(User::new())),它仍然会改变.

同样的事情发生在锈病和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();
}
Run Code Online (Sandbox Code Playgroud)
in init: user addr:        0x10da17000
in do something user addr: 0x10da17000
Run Code Online (Sandbox Code Playgroud)

如果你在移动到a 之前将引用传递User给C ,那么是的,地址将在移动到地址时改变.这相当于:BoxBox

User rust_like_new(void) {
    User u;
    UserInit(&u);
    return u;
}

int main(int argc, char *argv[]) {
    User u = rust_like_new();
    User *u2 = malloc(sizeof(User));
    *u2 = u;
    UserDoSomething(u2);
}
Run Code Online (Sandbox Code Playgroud)

请注意,Rust(和其他语言)允许执行RVO.但是,我认为打印出地址会取消此优化的资格,因为如果启用了RVO,行为将会改变.您需要查看调试器或生成的程序集.