Search code examples
c++reinterpret-caststrict-aliasingtype-aliasstdany

Does C++ standard guarantee such kinds of indirect access well defined?


Below has 3 different styles for indirect accessing. I am trying to understand if they all have well-defined behavior, and can be safely used for cross-platform code.

#include <cstdint>
#include <iostream>
#include <any>

using namespace std;

#if 1
#define INLINE [[gnu::always_inline]] inline
#else
#define INLINE [[gnu::noinline]]
#endif

INLINE
void mod1(void *p) {
    *static_cast<int*>(p) = 10;
}

INLINE
void mod2(uintptr_t p) {
    *reinterpret_cast<int*>(p) = 12;
}

INLINE
void mod3(any p) {
    *any_cast<int*>(p) = 14;
}

int test1() {
    int a1 = 5;
    mod1(&a1);
    return a1;
}

int test2() {
    int a2 = 6;
    mod2(reinterpret_cast<uintptr_t>(&a2));
    return a2;
}

int test3() {
    int a3 = 6;
    mod3(&a3);
    return a3;
}

int main() {
    cout << test1() << "\n";
    cout << test2() << "\n";
    cout << test3() << "\n";
}

I tried inline and noinline, they all work as expected, output 10 12 14 so I think inlining does not matter here.

Are they all well-defined behavior by C++ standard? Any help would be greatly appreciated :)

[Edit] Even if we consider type-based alias analysis.


Solution

  • Technically the behavior of neither of these is specified by the standard, because you are using implementation-defined attributes which could have any effect. However practically speaking these attributes are irrelevant to the behavior here.

    That aside:

    test1 is correct and specified to work as expected. Casting an object pointer to (possibly cv-qualified) void* and back to the original type via static_cast or reinterpret_cast is always allowed and yields the original pointer value. However this round-trip through void* to the same type is the only sequence of such casts that has this guarantee in general.

    test2 is conditionally-supported. It is not guaranteed by the C++ standard that std::uintptr_t or any other type to which object pointers can be converted exists. If std::uintptr_t does exist, then the behavior is well-defined with the expected output. The mapping between integral and object pointer types is implementation-defined except that round-trip casting back to the original pointer type through an integral type of sufficient size yields the original pointer value.

    test3 is well-defined to give the expected result, except that it may (in contrast to the other two cases) fail by throwing std::bad_alloc. The whole point of std::any is that you can put any type (e.g. int*) into it and access it again in a type-safe manner with std::any_cast. It also requires RTTI which is sometimes disabled on purpose (in non-conformance to the standard).