Reading https://en.cppreference.com/w/cpp/language/reinterpret_cast I wonder what are use-cases of reinterpret_cast
that are not UB and are used in practice?
The above description contains many cases where it is legal to convert a pointer to some other type and then back, which is legal. But that seems of less practical use. Accessing an object through a reinterpret_cast
pointer is mostly UB due to violations of strict-aliasing (and/or alignment), except accessing through a char*
/byte*
-pointer.
One helpful exception is casting an integer-constant to a pointer and accessing the target object, which is useful for manipulation of HW-registers (in µC).
Can anyone tell some real use-cases of relevance of reinterpret_cast that are used in practice?
Some examples that come to mind:
Reading/writing the object representation of a trivially-copyable object, for example to write the byte representation of the object to a file and read it back:
// T must be trivially-copyable object type!
T obj;
//...
std::ofstream file(/*...*/);
file.write(reinterpret_cast<char*>(&obj), sizeof(obj));
//...
std::ifstream file(/*...*/);
file.read(reinterpret_cast<char*>(&obj), sizeof(obj));
Technically it is currently not really specified how accessing the object representation should work aside from directly passing on the pointer to memcpy
et. al, but there is a current proposal for the standard to clarify at least how reading (but not writing) individual bytes in the object representation should work, see https://github.com/cplusplus/papers/issues/592.
Reinterpreting between signed and unsigned variants of the same integral type, especially char
and unsigned char
for strings, which may be useful if an API expects an unsigned string.
auto str = "hello world!";
auto unsigned_str = reinterpret_cast<const unsigned char*>(str);
While this is allowed by the aliasing rules, technically pointer arithmetic on the resulting unsigned_str
pointer is currently not defined by the standard. But I don't really see why it isn't.
Accessing objects nested within a byte buffer (especially on the stack):
alignas(T) std::byte buf[42*sizeof(T)];
new(buf+sizeof(T)) T;
// later
auto ptr = std::launder(reinterpret_cast<T*>(buf + sizeof(T)));
This works as long as the address buf + sizeof(T)
is suitably aligned for T
, the buffer has type std::byte
or unsigned char
, and obviously is of sufficient size. The new
expression also returns a pointer to the object, but one might not want to store that for each object. If all objects stored in the buffer are the same type, it would also be fine to use pointer arithmetic on a single such pointer.
Obtaining a pointer to a specific memory address. Whether and for which address values this is possible is implementation-defined, as is any possible use of the resulting pointer:
auto ptr = reinterpret_cast<void*>(0x12345678);
Casting a void*
returned by dlsym
(or a similar function) to the actual type of a function located at that address. Whether this is possible and what exactly the semantics are is again implementation-defined:
// my_func is a C linkage function with type `void()` in `my_lib.so`
// error checking omitted!
auto lib = dlopen("my_lib.so", RTLD_LAZY);
auto my_func = reinterpret_cast<void(*)()>(dlsym(lib, "my_func");
my_func();
Various round-trip casts may be useful to store pointer values or for type erasure.
Round-trip of an object pointer through void*
requires only static_cast
on both sides and reinterpret_cast
on object pointers is defined in terms of a two-step static_cast
through (cv-qualified)void*
anyway.
Round-trip of an object pointer through std::uintptr_t
, std::intptr_t
, or another integral type large enough to hold all pointer values may be useful for having a representation of the pointer value that can be serialized (although I am not sure how often that really is useful). It is however implementation-defined whether any of these types exist. Typically they will, but exotic platforms where memory addresses cannot be represented as single integer values or all integer types are too small to cover the address space are permitted by the standard. I would also be vary of pointer analysis of the compiler causing issues depending on how you use this, see e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65752 as just the first bug report I found. The standard isn't particularly clear on how the integer -> pointer cast is supposed to work especially when considering pointer provenance. See for example https://open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2318r1.pdf and other documents linked therein.
Round-trip of a function pointer through any arbitrary function pointer type (likely void(*)()
) may be useful to erase the type from arbitrary functions, although again I am not sure how often that really is useful. void*
type-erased arguments are common in C APIs when a function just passes through data, but type-erased function pointers like that are less common.
A round-trip cast of a function pointer through void*
may be used in a similar way as above, as dlsym
essentially does with the additional dynamic library complication. This is conditionally-supported only, although it is effectively required for POSIX systems. (It is not generally supported, because object and function pointer values may have distinct representations, size, alignment etc. on some more exotic platforms.)