#include <stdlib.h>
#include <stdio.h>
void destroyer(int* val) {
printf("%d\n", *val);
free(val);
}
int main(int argc, char** argv) {
int* v = malloc(sizeof(int));
*v = 3;
destroyer(v);
printf("%d\n", *v);
return 0
}
Which compiles without warnings using Clang (unless -Weverything, and even then the warnings are not related to use-after-free), works "correctly" in O0 and O1 (prints "3" twice) then breaks starting at O2 (prints "3" then "0"). (note: it always prints "3" twice with GCC 4.8, showing how fun these things are)meanwhile the equivalent
fn main() {
let v = ~3;
destroyer(v);
println!("{}", *v)
}
fn destroyer(val: ~int) {
println!("{}", *val)
}
refuses to compile and explains why: test.rs:4:20: 4:21 error: use of moved value: `v`
test.rs:4 println!("{}", *v)
^
note: in expansion of format_args!
<std-macros>:224:8: 224:50 note: expansion site
<std-macros>:223:4: 225:6 note: in expansion of format!
<std-macros>:241:45: 241:63 note: expansion site
<std-macros>:240:4: 242:5 note: in expansion of println!
test.rs:4:4: 5:1 note: expansion site
test.rs:3:14: 3:15 note: `v` moved here because it has type `~int`, which is non-copyable (perhaps you meant to use clone()?)
test.rs:3 destroyer(v);
^
error: aborting due to previous error
which can be fixed either by explicitly cloning the value, or by altering the sub-function to not consider it owns the pointer (or by removing the `println!` call in `main()`, thus transferring the ownership of the pointer to the sub-function safely, of course)Another option is to say something like, "For the sake of brevity, this is a very simple and arguably obvious violation of safety. In practice, there are many subtle and hard-to-diagnose sources of unsafety in C++, even when you use safer abstractions like shared_ptr." This allows you to avoid getting sidetracked and losing your reader, while heading off skepticism of readers with more knowledge about C++.
#include <stdio.h>
#include <memory>
void destroyer(std::unique_ptr<int> x)
{
printf("%d\n", *x);
// x is deallocated here
}
int main()
{
auto x = std::make_unique<int>(3);
// destroyer(x); ERROR: ownership must be transferred explicitly
destroyer(move(x)); // Override with an explicit move
printf("%d\n", *x); // Guaranteed to segfault
return 0;
}
This is somewhat more helpful, but sadly the compiler cannot monitor the state of x at compile time, like Rust. The upside is that this bug is easy to catch since the invalid access is guaranteed to try to access a null pointer, and will crash instead of giving garbled results.GCC and LLVM optimize based on the assumption that null pointers are never dereferenced, so this is actually undefined behavior, no? Anything can happen.
main.c:14:20: warning: Use of memory after it is freed
printf("%d\n", *v);
^~http://bluishcoder.co.nz/2012/08/30/safer-handling-of-c-memo...
I think something similar for Rust would make for a great article subject too.