reinterpret_cast between integers and pointers (of same and of different sizes)
Asked Answered
C

1

7

There are some things that I do not understand about reinterpret_cast.

The following snippet from cppreference has this to say about casting between integral values and pointers.

  1. A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed; the same pointer may have multiple integer representations) The null pointer constant NULL or integer zero is not guaranteed to yield the null pointer value of the target type; static_cast or implicit conversion should be used for this purpose.

In particular, from what I understand about the part about the "round-trip conversion in the opposite direction", C++ doesn't guarantee anything about a cast like

uintptr_t x;
reinterpret_cast<uintptr_t>(reinterpret_cast<void *>(x)) // == x ?

If there is such a guarantee, it would be good to see the corresponding reference.

My second question regards the correctness of the following: Suppose int x; int y; and x == y is true, and that sizeof(int) is strictly smaller than sizeof(void *). Then is it guaranteed that reinterpret_cast<void *>(x) == reinterpret_cast<void *>(y) is true?

Intuitively, suppose in the physical memory x has some garbage bytes after it, and y also has some garbage bytes after it. Then by testing reinterpret_cast<void *>(x) == reinterpret_cast<void *>(y) we are doing the following numerical comparison:

x garbage_x
==
y garbage_y

To clarify, the x refers to the portion of memory with value x in the expression reinterpret_cast<void *>(x) and garbage_x refers to the remaining portion of memory.

As another special case, what is the behavior of this with literals, say reinterpret_cast<void *>(1) == reinterpret_cast<void *>(1)?

Calix answered 14/12, 2022 at 11:26 Comment(11)
In general it's very hard to prove a negative: there doesn't need to exist any reference to the lack of a guarantee. Even absent of such a reference, there are no guarantees (as far as I know).Agbogla
Per the second question, you are reinterpreting the value of x, not a pointer to it, so garbage neighbours should have no effect.Extraordinary
My thoughts : don't get to hung up on this. Why would you even want to do this? pointers are pointers and values are values. In my 30+ years of C++ experience I only once used this when passing pointers in windows messages (which in the end is was bad idea too, it did not survive the transition from 32 to 64 bit).Congener
@Extraordinary Let's say void * is 8 bytes and int is 4 bytes. Then comparing (void *)int1 and (void *)int2, the compiler might be issuing a comparison of the 8 bytes starting from base address of int1 and the base address of int2.Calix
@PepijnKramer I find this useful when passing integers directly to C functions, e.g. pthread starting functions which take a void * argument, as well as the returning an integer (instead of pointer to int) from a pthread_exit. For e.g. a pthread that is cancelled returns PTHREAD_CANCELED which is defined as ((void *)-1). In this case, it isn't even my choice to return an int directly, the pthread.h header file defines this.Calix
The answer to that is don't use pthreads, use std::async (or std::thread) and pass your parameters using lambda + capture. That way you can solve everything using standard typesafe C++ (en.cppreference.com/w/cpp/thread/async). So all in all you would still not need to cast between (void) pointers and values.Congener
@JiaChengSun No, no base addresses are involved, the cast will operate on a copy of x, not the memory location &x. In other words, first int& x is dereferenced, the value stored in a copy somewhere and then passed to the cast expression. Of course the copy might be elided or whatever but the garbage after x does not matter.Extraordinary
@PepijnKramer There are use cases when such conversions are useful. For instance, if we want to implement a tagged pointer, then we need to perform bitwise operations, and these are not available for pointers.Imprecate
@DanielLangr true, but these are the rare exceptions not the norm and I dont think they apply hereCongener
@Extraordinary Yep, I was inaccurate in what I commented. However, we can consider this copy of x. The temporary copy is still a value somewhere in memory, so we can still speak of garbage/indeterminate values after that (i.e. the temporary value of the casted expression that is ((void *)x))?Calix
@JiaChengSun Okay I understand what you mean, I cannot give you a good quote but I really do think language guarantees that uint8_t x=1; (void*)x really only reinterprets 1, not some additional garbage. That would break a lot of things I believe.Extraordinary
C
1

This must be a throwback to the original x86 segmented memory architecture, where pointers were represented as a segment and an offset, and multiple pointer values would map to the same, actual, byte of RAM.

A conversion from an integer to a pointer might use some method of calculating some arbitrary segment and offset, for the pointer, and then converting the pointer back to an integer will produce a completely different result.

Cheap answered 14/12, 2022 at 12:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.