c++undefined-behaviorc++23reinterpret-cast

Obtaining an integer (long) value representing the address of the current object without UB


I'm trying to obtain the address of an object (the current one - *this) for use in seeding a random generator. I want to do this since I want to take advantage of ASLR for providing a bit of entropy. I then add this address, along with several other pseudo random sources, to a std::seed_seq which I then use to seed my generator.

My worry is that the way I obtain the address of *this might be undefined behaviour due to my use of reinterpret_cast.

Here is a small example that shows what I'm doing:

#include <iostream>
#include <memory>
#include <cstdint>

struct C {
    C() {
        std::cout << static_cast<long>(reinterpret_cast<intptr_t>(std::addressof(*this))) << '\n';
    }
};

int main() {
    C c;
}

When compiling this program like this:

g++ -std=c++23 mre.cc

and then running it 5 times on my Arch Linux system, I get values that seem reasonable and usable for my purpose:

$ ./a.out 
140735517651991
$ ./a.out 
140720816496503
$ ./a.out 
140721435514407
$ ./a.out 
140731546749575
$ ./a.out 
140737158244279

But, just because the numbers seem reasonable and there's no crash or other bad things happening does, of course, not tell me whether there's actually any UB here.

So, my question is: Is this code 100% valid and portable and free of UB?

If yes; I'd appreciate an explanation of why reinterpret_cast is valid here.

If no; I'd appreciate an explanation of why not and a suggestion of a better (non-UB) way of doing this.

Thanks.


Solution

  • From n4950:

    7.6.1.10 Reinterpret cast

    1. A pointer can be explicitly converted to any integral type large enough to hold all values of its type. The mapping function is implementation-defined.

    This says that the reinterpret_cast is fine if intptr_t is large enough.

    The static_cast from intptr_t to long follows normal truncation rules, but that's beside the question.

    17.4.1 Header <cstdint> synopsis

    1. using intptr_t = signed integer type ; // optional

    intptr_t is optional and only defined if the implementation has an integral type large enough to hold all values of a pointer type. If it's not defined, the program will not compile.

    So, while the program is not 100% portable because of the optional type used, as long as it compiles, it has defined behavior.