Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
748 views
in Technique[技术] by (71.8m points)

rust - Why does the address of an object change across methods?

I have C code to bridge to. I chose to use mem::uninitialized to claim the memory first, then call the C function(UserInit) to do initialisation, then use it (in UserDoSomething).

The odd thing is the addresses of the object are different in UserInit and UserDoSomething. Why does it behave in this way?

The C code:

typedef struct {
    char* name;
    int32_t age;
} User;

void
UserInit(User* u){
    printf("in init: user addr: %p
", u);
}

void
UserDoSomething(User* u){
    printf("in do something user addr: %p
", u);
}

void
UserDestroy(User* u){
    free(u->name);
}

The 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 tests:

mod ffi;

use ffi::User;

fn main() {
    let mut u = User::new();
    u.do_something();
}

In theory, it should output the same address but it doesn't:

> cargo run
     Running `target/debug/learn`
in init: user addr: 0x7fff5b948b80
in do something user addr: 0x7fff5b948ba0
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

That's just how Rust works.

More than that, that's how C works too:

#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
", u);
}

void
UserDoSomething(User* u){
    printf("in do something user addr: %p
", 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

Generally, you don't care about the address of the container, just what it contains.

If I heap allocate User, the address won't change, but if I use Box (let u = Box::new(User::new())), it still changes.

The same thing happens in Rust and C. The address of the Box<User> or User * itself will change. The value (the pointed-at thing) of the Box<User> or User * will stay consistent.

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

If you pass a reference to User to C before it's moved into a Box, then yes, the address will change when it's moved into the Box. This would be the equivalent of:

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);
}

Note that Rust (and other languages) allow for RVO to be performed. However, I believe that printing out the address would disqualify this optimization because the behavior would change if RVO was enabled. You'd need to look in a debugger or at the generated assembly.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...