Why does the object address change in different methods?
I have C code to go to. I decided to use mem::uninitialized
to first request memory, then call the C ( UserInit
) function to initialize, then use it (in UserDoSomething
).
The odd thing is that the object addresses differ in UserInit
and UserDoSomething
. Why does he behave this way?
Code 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 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
source to share
This is how Rusta works.
Moreover, this is how C works:
#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
Typically, you don't need the address of the container, just what it contains.
If I type in the selection
User
, the address doesn't change, but if I useBox
(let u = Box::new(User::new())
) it still changes.
The same thing happens in Rust and C. The address itself Box<User>
or User *
will change. The value (pointed thing) Box<User>
or User *
will remain unchanged.
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
C before it moves to Box
, then yes, the address will change when it moves to Box
. This would be equivalent to:
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 RVO to be executed. However, I believe that printing out the address would disqualify this optimization because the behavior would change if RVO was enabled. You will need to search in the debugger or in the generated assembly.
source to share