I'm new to C and smart pointers, especially the behavior of unique_ptr. Below is a piece of code that I'm experimenting:
unique_ptr<int> u1 = make_unique<int>(2);
unique_ptr<int> u2 = make_unique<int>();
u2.reset(u1.get());
unique_ptr, by definition, is a kind of smart pointer which doesn't share the ownership of the object that it is pointing to with other smart pointers. However, why doesn't the above code return an error? In fact, if I try to print the value of u1 and u2 out, they turn out to indeed point to the same memory address:
cout<<u1.get()<<endl;
cout<<u2.get()<<endl;
Show these on the Console:
0x55800839ceb0
0x55800839ceb0
free(): double free detected in tcache 2 // finally the error appears at the end of the program's execution
But if I say:
cout<<(*u1)<<endl;
(*u1)=5;
cout<<(*u2)<<endl;
The change doesn't affect (*u2), as if they are in different memory addresses.
Any help would be appreciated! Thank you for your time!
CodePudding user response:
As soon as you call get()
, you have a raw pointer. It's just a pointer, and it carries no significant ownership semantics that the standard library or language can preserve or provide.
You then use it to create a second unique pointer, which "thinks" that it owns the object being pointed to. This is perfectly fine, as long as that unique pointer is the only thing owning that object.
Unfortunately, the first unique pointer still owns the very same object, and there will be a double-free when both unique pointers go out of scope and are destructed. This is undefined behavior, and a clear error message is not guaranteed. You may have subtle corruption, a silent crash, or the program may silently succeed by chance alone.
However, a sanitizer can show a clear error at runtime more reliably when it's enabled, since it's actively on the lookout for certain types of undefined behavior. Here's a demo showing ASan (the Address Sanitizer) detecting this double-free as it is designed to do.
The change doesn't affect (*u2), as if they are in different memory addresses.
It does; see the above demo which prints 2 2 2 5 (u1, u2, u1 before updating, u2 after (*u1)
is updated.
CodePudding user response:
However, why doesn't the above code return an error?
Because compilers are not magical.
unique_ptr::reset
is a legitimate function, allowing a unique_ptr
to adopt a new, potentially unowned pointer. unique_ptr::get
is a legitimate function, allowing a user to access the pointer being managed.
The fact that calling both in succession is almost certainly a bug is not something a compiler can diagnose. And even if they did, all you'd need to cause the same problem is to hide the get
from the reset
. A function between them that passes through its parameter would be enough.
Even having something like unowned_ptr<T>
, a "smart" pointer that just wraps the T*
, returned by get
wouldn't solve it, as you have to be able to access the T*
if you want to do anything with that unowned_ptr<T>
.
At some point, programmers have to take responsibility for the correctness of their programs. Tools like unique_ptr
can help, but eventually, a user's judgment must be trusted.