Search code examples
c++language-lawyerreinterpret-caststrict-aliasing

Second reinterpret_cast and strict aliasing


This is a classical example of strict aliasing violation:

std::uint32_t foo(float* f, std::uint32_t* i) {
    *i = 1;
    *f = 2;
    return *i;
}

int main() {
    std::uint32_t i = 3;
    foo(reinterpret_cast<float*>(&i), &i);
}

But suppose we add the second reinterpret_cast:

static_assert(alignof(float) == alignof(std::uint32_t));

std::uint32_t foo(float* f, std::uint32_t* i) {
    *i = 1;
    *reinterpret_cast<std::uint32_t*>(f) = 2;
    return *i;
}

int main() {
    std::uint32_t i = 3;
    std::uint32_t j = foo(reinterpret_cast<float*>(&i), &i);
    assert(j == 2);
}

Is this code correct (doesn't invoke undefined behaviour)?

The standard [expr.reinterpret.cast] reads:

Note: Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value.

We use the original pointer value of type std::uint32_t* to access the lvalue of type std::uint32_t.

Both GCC and Clang generate correct assembly code when optimization is turned on:

foo(float*, unsigned int*):
        mov     dword ptr [rsi], 1
        mov     dword ptr [rdi], 2
        mov     eax, dword ptr [rsi]
        ret

Solution

  • Here's the corresponding normative text, expr.static_cast/13:

    A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

    (This text relates, because in this case, the result of reinterpret_cast<T*> is static_cast<T *>(static_cast<void *>(...)))

    So, if alignment requirements are fulfilled, then none of the conversions (neither uint32_t->float nor float->uint32_t) change the pointer value. And you access the object as its type. There is no UB here.