Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active September 13, 2018 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/099cd7d88938ad8840dc98e376a8da29 to your computer and use it in GitHub Desktop.
Save ChunMinChang/099cd7d88938ad8840dc98e376a8da29 to your computer and use it in GitHub Desktop.
Don't use the pointers of the instances allocated in functions stack

Don't misuse the pointers of the instances allocated in functions stack

struct Foo { ... }

fn create_foo() -> Foo {
    let foo = Foo { ... };
    foo
}

let f = create_foo(); // Perform `memcpy`!

The addresses of f and foo are different since let f = create_foo(); performs memcpy. The whole data of foo will be copied to a new place pointed by f and then the memory of foo in stack will be abandoned. That's how normal function calls works, if it doesn't use any variable in heap.

struct Foo { ... }

fn create_foo() -> Foo {
    let foo = Foo { ... };
    register_pointer(&foo); // Cast to *const Foo automatically.
    foo
}

fn register_pointer(ptr: *const Foo) {
    // Save the `ptr` to somewhere and
    // use this `ptr` to get a Foo instance later.
}

fn get_foo_ref() -> &Foo {
    // Convert the `ptr` saved in register_pointer
    // to a reference of the Foo instance.
}

let f = create_foo(); // Perform `memcpy`!
let f_ref = get_foo_ref(); // Likely to cause a segmentation fault!

Therefore, the above code is very likely to cause a segmentation fault since get_foo_ref() will try to convert a invalid pointer pointing a memory that was marked unused after executing let f = create_foo(); to a Foo instance.

The runnable sample code for this problem is init_in_new_wrong.rs. The solution for init_in_new_wrong.rs is init_in_new.rs. Clone this gist repo, go to the repo folder and run ```$ make`` to see the results.

Sample code

  • makefile: Commands to run the examples.
  • new.rs: Prove the idea above with struct method.
  • new.c: Compare results of new.rs with how similar code works in C.
  • new.cpp: Compare results of new.rs how similar code works in C++.
  • init_in_new_wrong.rs: The sample code to demonstrate the idea above.
  • init_in_new.rs: The solution for init_in_new_wrong.rs.

Real case study: Registering callback with pointer to external library

The reason I create this gist repo is to simplify what the problem I have when I misuse the ::new(...) struct method in my strcut. I have a strcut and it has a struct method called ::new(...). In the ::new(...), I create a struct instance and use the pointer of this created instance as the callback target whose type is *const c_void to an external C library, then the created struct instance is returned outside. When callback is fired from the external C library, it will convert the registered target pointer to a struct instance so that it knows which variable calls it. It looks logically reasonable, but it doesn't work. It causes a memory error when converting the registered target pointer to a struct instance. The memory pointed by the registered target pointer is abandoned after finishing executing ::new(...). That's why it doesn't work.

The sample code itself explains this problem well. See the code here.

use std::ptr;
use std::f64::consts::PI;
struct Foo {
value: f64,
func: fn(f64),
}
impl Foo {
fn new(value: f64, func: fn(f64)) -> Self {
let f = Foo {
value,
func
};
println!("(Foo::new) f @ {:p}", &f);
println!("(Foo::new) f.value @ {:p}", &f.value);
println!("(Foo::new) f.func @ {:p}", &f.func);
f
}
fn init(&self) {
println!("(Foo::init) self @ {:p}", self);
unsafe { FOO_MIRROR.set(self); }
}
fn get_value(&self) -> f64 {
println!("(Foo::get_value) self @ {:p}", self);
println!("(Foo::get_value) self.value @ {:p}", &self.value);
self.value
}
fn invoke_func(&self) {
println!("(Foo::invoke_func) self @ {:p}", self);
println!("(Foo::invoke_func) self.func @ {:p}", &self.func);
(self.func)(self.value)
}
}
struct FooMirror {
foo_ptr: *const Foo,
}
impl FooMirror {
fn set(&mut self, foo_ref: &Foo) {
self.foo_ptr = foo_ref;
println!("(FooMirror::set) set foo_ptr @ {:p}", self.foo_ptr);
}
fn get(&self) -> &Foo {
println!("(FooMirror::get) get foo_ptr @ {:p}", self.foo_ptr);
unsafe { &(*self.foo_ptr) }
}
}
static mut FOO_MIRROR: FooMirror = FooMirror { foo_ptr: ptr::null() };
fn show_value(value: f64) {
println!("(show_value) value is {}", value);
}
fn main() {
let foo = Foo::new(PI, show_value);
println!("(main) foo @ {:p}", &foo);
println!("(main) foo.value @ {:p}", &foo.value);
println!("(main) foo.func @ {:p}", &foo.func);
foo.init();
let foo_mirror = unsafe { FOO_MIRROR.get() };
println!("(main) foo_mirror.value: {}", foo_mirror.get_value());
foo_mirror.invoke_func();
}
use std::ptr;
use std::f64::consts::PI;
struct Foo {
value: f64,
func: fn(f64),
}
impl Foo {
fn new(value: f64, func: fn(f64)) -> Self {
let f = Foo {
value,
func
};
println!("(Foo::new) f @ {:p}", &f);
println!("(Foo::new) f.value @ {:p}", &f.value);
println!("(Foo::new) f.func @ {:p}", &f.func);
// Assigning `FOO_MIRROR.foo_ptr` to the address of `f` will cause to a
// memory eror since `f` will be copied and abandoned after being returned.
// The `FOO_MIRROR.foo_ptr` then will point to a invalid memory address.
// When we dereference the `FOO_MIRROR.foo_ptr`, it's very likely to cause
// a `Segmentation fault: 11`.
f.init();
f
}
fn init(&self) {
println!("(Foo::init) self @ {:p}", self);
unsafe { FOO_MIRROR.set(self); }
}
fn get_value(&self) -> f64 {
println!("(Foo::get_value) self @ {:p}", self);
println!("(Foo::get_value) self.value @ {:p}", &self.value);
self.value
}
fn invoke_func(&self) {
println!("(Foo::invoke_func) self @ {:p}", self);
println!("(Foo::invoke_func) self.func @ {:p}", &self.func);
(self.func)(self.value)
}
}
struct FooMirror {
foo_ptr: *const Foo,
}
impl FooMirror {
fn set(&mut self, foo_ref: &Foo) {
self.foo_ptr = foo_ref;
println!("(FooMirror::set) set foo_ptr @ {:p}", self.foo_ptr);
}
fn get(&self) -> &Foo {
println!("(FooMirror::get) get foo_ptr @ {:p}", self.foo_ptr);
unsafe { &(*self.foo_ptr) }
}
}
static mut FOO_MIRROR: FooMirror = FooMirror { foo_ptr: ptr::null() };
fn show_value(value: f64) {
println!("(show_value) value is {}", value);
}
fn main() {
let foo = Foo::new(PI, show_value);
println!("(main) foo @ {:p}", &foo);
println!("(main) foo.value @ {:p}", &foo.value);
println!("(main) foo.func @ {:p}", &foo.func);
let foo_mirror = unsafe { FOO_MIRROR.get() };
println!("(main) foo_mirror.value: {}", foo_mirror.get_value());
foo_mirror.invoke_func();
}
all:
g++ new.cpp -o new-cpp
gcc new.c -o new-c
rustc new.rs
rustc init_in_new.rs
rustc init_in_new_wrong.rs
./new
./new-c
./new-cpp
./init_in_new
./init_in_new_wrong
clean:
rm new
rm new-c
rm new-cpp
rm init_in_new
rm init_in_new_wrong
#include <math.h>
#include <stdio.h>
typedef struct {
double value;
} Foo;
Foo create(double v) {
Foo f = { v };
printf("(create) f @ %p\n", (void*)&f);
return f;
}
int main() {
Foo foo = create(M_PI);
printf("(main) foo @ %p\n", (void*)&foo);
return 0;
}
#include <cmath>
#include <iostream>
using std::cout;
using std::endl;
struct Foo {
double value;
static Foo create(double v) {
Foo f = { v };
cout << "(Foo::create) f @ " << &f << endl;
return f;
}
};
struct Bar {
double value;
Bar(double v): value(v) {
cout << "(Bar::Bar) this @ " << this << endl;
}
};
// class Bar {
// public:
// Bar(double v): value(v) {
// cout << "(Bar::Bar) this @ " << this << endl;
// }
// private:
// double value;
// };
int main() {
Foo foo = Foo::create(M_PI);
cout << "(main) foo @ " << &foo << endl;
Bar bar(M_PI);
cout << "(main) bar @ " << &bar << endl;
// Bar* bar = new Bar(M_PI);
// cout << "(main) bar @ " << bar << endl;
return 0;
}
use std::f64::consts::PI;
struct Foo {
value: f64,
}
impl Foo {
fn new(value: f64) -> Self {
let f = Foo {
value
};
println!("(Foo::new) f @ {:p}", &f);
f
}
}
fn main() {
let foo = Foo::new(PI);
println!("(main) foo @ {:p}", &foo);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment