Is reintrepret_cast to char* well defined when the char array is modified?

Viewed 191

Consider the following example when receiving an expected message from a socket:

struct myData {
    uint8_t type;
    int value;
}

myData readFromSocket(int socketFD) {
    myData data{};
    ssize_t bytes = recv(socketFD, reinterpret_cast<char*>(&data), sizeof(myData), 0);
    if(bytes == sizeof(myData))
        return data;
    return myData{};
}

In this example it is unclear to me if the behavior is well defined.

According to reintrpret_cast on cppreference.com the behavior is well defined for examination since the alignment of char is less strict then that of myData and since the cast specifically is to a char pointer. It is unclear to me if examination is exclusive to reads or if it includes writes to the casted pointer.

5) under explanation:

Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below)

and the third point of type aliasing:

AliasedType is std::byte (since C++17), char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

I have tested similar code to the above without any problems, however since this all comes down to what optimizations the compiler does I find it hard to give an exact example of where this may fail.

This article mentions that casting in the other direction, i.e. from char* to myData is subject to undefined behavior and recommends the use of memcpy(). My assumption is that this conclusion was reached since the cast is not covered by the type aliasing rules.

However this mail thread gives doubt that memcpy(), according to the standard, should give that guarantee (see quote below) and without having read the standard I'm inclined to agree since it looks like the same cast is done to memcpy() as to recv().

In the C++ community, the current thinking is that memcpy allows to type pun, but IIRC the C++ standard is actually not even clear as for why this is the case, and in its current writing it might be that there is actually no way.

In any case, if anyone knows anything about this and could shed some light I would appreciate it. My interest in this matter is more academic then practical. I've tagged this with c++17 since that's what I'm working in, insights to other standards are welcome.

1 Answers

C++ Standard specifies aliasing rules in [basic.lval]/p8:

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
. . .
— a char, unsigned char, or std​::​byte type.

The term access is specified in [defns.access]:

⟨execution-time action⟩ to read or modify the value of an object

Besides that there is no other rule that states that aliasing works one-way.

So we can conclude that aliasing works both ways and the code is OK.

Note however, that if the object being overwritten contains const members, then you should std::launder it after overwriting, otherwise the compiler is allowed to assume const members to never change.

Related