Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it never truly safe to reinterpret_cast input into std::unique_ptr?

When using various API's that have variable size structures (structures that must be allocated as byte[] and then cast into the struct), it would be nice if the unique_ptr holder could point to the structure, since that's what we'll be using.

Example:

std::unique_ptr<VARIABLE_SIZE_STRUCT[]> v; 
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new BYTE[bytesRequired]));

This allowed `v to provide the view to the structure itself, which is preferable because that we don't need a second variable and we don't care about the byte pointer except for deletion.

The problem comes in with the possibility of thunking the pointer on the cast (making it unsafe to free). I see no reasonable reason why the compiler would change the pointer value on cast (since there's no inheritance), but I hear that the standard reserves the right to thunk any pointer on any cast, so as far as standard-compliant coding goes, this approach is out the window, right? Or is there some reason it is safe? Is there a way to at least static_assert this, or some other way to make it safe or cleanly deal with this type of structure?

like image 890
VoidStar Avatar asked Mar 04 '15 06:03

VoidStar


People also ask

Is it safe to use reinterpret_cast?

reinterpret_cast is a very special and dangerous type of casting operator. And is suggested to use it using proper data type i.e., (pointer data type should be same as original data type). It can typecast any pointer to any other data type.

Can unique_ptr be copied?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.

What is std :: unique_ptr?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

Is reinterpret_cast portable?

Anyway, the consequence of this is, that reinterpret_cast<> is portable as long as you do not rely on the byte order in any way. Your example code does not rely on byte order, it treats all bytes the same (setting them to zero), so that code is portable.


2 Answers

  • your allocation may not have the alignment needed by VARIABLE_SIZE_STRUCT

  • the allocated memory has not had an object of VARIABLE_SIZE_STRUCT placement-newed in it - assuming you take care of that, the unique_ptr's default destructor logic should find the expected object instance to destruct, but the deallocation itself would then not be done with delete [] on a BYTE* - to have defined behaviour you'd have to customise the deleter to invoke first ~VARIABLE_SIZE_STRUCT() then delete[]...

If you're concerned about "thunking", you could do a check at run-time:

BYTE* p;
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(p = new BYTE[bytesRequired]));
assert(reinterpret_cast<BYTE*>(v.get()) == p);

The background on that - 5.2.10/7:

An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast<cvT*>(static_cast<cv void*>(v)). Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value.

So, if the alignment requirements for VARIABLE_SIZE_STRUCT are stricter than for BYTE, you're not guaranteed to retrieve the original pointer using reinterpret_cast<BYTE*>.

like image 99
Tony Delroy Avatar answered Oct 22 '22 03:10

Tony Delroy


You're correct, this is unsafe. It is possible to make it safe, however.

The standard guarantees that if you reinterpret_cast to a different type, then back to the original type, you get back the original value.

You can use this along with a custom deleter, to ensure your internal pointer is cast back to the type it was allocated as before releasing it.

auto deleter = [](VARIABLE_SIZE_STRUCT* ptr) 
{ 
    delete[] reinterpret_cast<uint8_t*>(ptr); 
}; 

std::unique_ptr<VARIABLE_SIZE_STRUCT, decltype(deleter)> v
    (reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new uint8_t[256]), deleter);

At this point, you're probably better off creating your own wrapper and not using unique_ptr.

like image 39
Collin Dauphinee Avatar answered Oct 22 '22 03:10

Collin Dauphinee