Assuming userdata
is never dereferenced, why is the following undefined behaviour?
std::list::iterator iter = [ ... ];
auto userdata = std::bit_cast<void *>(iter);
c_api_set_void_ptr(userdata);
auto iter = std::bit_cast<std::list::iterator>(c_api_get_void_ptr(userdata));
auto elem = *iter;
bit_cast
checks the sizes and triviality, leaving the possibility of producing an invalid value of one of the types. Are there restrictions on values of void *
? Does the situation change when limited to x86-64?
Assuming libstdc++/libc++/MSVC STL:
If any debug mode was enabled that made the list iterator hold more members, this would be ill-formed and fail to compile because the sizeof(iterator)
would be too large.
Otherwise, in all three of these implementations, there is simply a pointer to an internal list node. On x86-64, all pointers have the same value representation and size, so this bit cast would be valid and produce a valid pointer (to the list node). Since there are no padding bits on an x86-64 void*
pointer, no bits should be changed when this void*
pointer is copied all over the place, giving you a void*
pointer with the same value, which when bit_cast
back gives you an equivalent iterator.
On a different standard library implementation, a std::list<T>::iterator
might hold something besides a pointer that still has the same size. Or on a different platform the list node pointers might have a different layout than void*
pointers. If the bit_cast
does compile, an invalid pointer value might be produced.
Copying an invalid pointer has implementation defined behaviour. On an x86-64, copies will be fine and preserve all the bits, and this program will still be well formed. On a platform with extra pointer safety instructions, this might mangle the pointer value (or segfault or something). This is still implementation defined behaviour.
So your program is either ill-formed at compile time (because the bit_cast
fails to compile) or implementation defined. There is no outright undefined behaviour.