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

时间:2016-07-11 08:22:29

标签: rust

我有C代码可以连接到。我选择使用mem::uninitialized首先声明内存,然后调用C函数(UserInit)进行初始化,然后使用它(在UserDoSomething中)。

奇怪的是UserInitUserDoSomething中对象的地址不同。为什么它以这种方式表现?

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

2 个答案:

答案 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,地址不会改变,但如果我使用Boxlet 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或原始指针。

相关问题