c++20strict-aliasing

Breaking strict aliasing when reinterpret_cast-ing pointer to an object vs directly casting the reference of the same object


There are multiple questions about the warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing], from when adding compiler options -Wstrict-aliasing=2 -fstrict-aliasing, and how to solve it.

For instance, the most upvoted answer to this Q states that,

(...) gcc assumes that your program will never access variables though pointers of different type. This assumption is called strict-aliasing and allows the compiler to make some optimizations (...)

When attempting to rewrite (read cast) a struct as an array (for use in an external API), I found that the warning disapeared when adding an intermediate step of creating a pointer to said struct object, and casting that pointer instead.

I want to know if this is (not safe but) safer than directly casting the struct object's reference (which prompts the warning), or have I just conceiled what I'm doing from the compiler in such a way that the compiler doesn't see the danger and doesn't produce the warning? If it is safer, why? I don't really understand the actual difference.

If it matters, the reinterpret_cast-ed data is never accessed: it is stored/transported and reinterpret_cast-ed back again to the original struct before being used.

#include <iostream>
#include <cstdint>

struct Container
{
    uint32_t x;
    uint32_t y;
};

// SomeApi could for instance have been (from <fcntl>),
// write(some_fd, data, size);
void SomeApi(uint32_t* data, size_t size)
{
    for (size_t i=0; i<size; i++)
        std::cout << (i==0?"{":", ") << static_cast<unsigned>(data[i]);
    std::cout << "}\n";
}

int main() {

    Container c{3,4};

    uint32_t* data = reinterpret_cast<uint32_t*>(&c);
    SomeApi(data, sizeof(c)/sizeof(uint32_t));
    // warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]

    Container* p_c = &c;
    uint32_t* p_data = reinterpret_cast<uint32_t*>(p_c);
    SomeApi(p_data, sizeof(c)/sizeof(uint32_t));
    // Seemingly without issues?

    return 0;
}

Solution

  • There is no difference, you have undefined behavior in the second loop iteration inside SomeApi when you do static_cast<unsigned>(data[i]) with i == 1 because data[i] will be the pointer one-past the object c.x which, as all one-past pointers, musn't be dereferenced, which data[i] however does.

    Tricking the compiler's detection of the issue doesn't change anything about it.

    Passing to write instead of SomeApi would be a different matter. Then you shouldn't doing any casting at all and the API is defined to access the underlying bytes of the object, not to access the object with a wrong type.