Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The right way to work with network buffer in modern GCC/C++ without breaking strict-aliasing rules

The program - some sort of old-school network messaging:

// Common header for all network messages.
struct __attribute__((packed)) MsgHeader {
    uint32_t msgType;
};
// One of network messages.
struct __attribute__((packed)) Msg1 {
    MsgHeader header;
    uint32_t field1;
};

// Network receive buffer.
uint8_t rxBuffer[MAX_MSG_SIZE];

// Receive handler. The received message is already in the rxBuffer.
void onRxMessage() {
    // Detect message type
    if ( ((const MsgHeader*)rxBuffer)->msgType == MESSAGE1 ) { // Breaks strict-aliasing!
        // Process Msg1 message.
        const Msg1* msg1 = (const Msg1*)rxBuffer;
        if ( msg1->field1 == 0 ) { // Breaks strict-aliasing!
            // Some code here;
        }
        return;
    }
    // Process other message types.
}

This code violates strict-aliasing in modern GCC (and falls down to unspecified behaviour in modern C++). What is the correct way to solve the problem (to make the code that doesn't throw the "strict-aliasing" warning)?

P.S. If rxBuffer is defined as:

union __attribute__((packed)) {
  uint8_t[MAX_MSG_SIZE] rawData;
} rxBuffer;

and then I cast &rxBuffer to other pointers it doesn't cause any warnings. But is it safe, right and portable way?

like image 888
Sap Avatar asked Jun 05 '15 12:06

Sap


2 Answers

Define rxBuffer as a pointer to a union of uint8_t[MAX_SIZE], MsgHeader, Msg1 and whatever type you plan to cast to. Note that this would still break the strict aliasing rules, but in GCC it it guaranteed to work as non-standard extension.

EDIT: if such a method would lead to a too complicated declaration, a fully portable (if slower) way is to keep the buffer as a simple uint8_t[] and memcpy it to the opportune message struct as soon as it has to be reinterpreted. The feasability of this method obviously depends on your performance and efficiency needs.

EDIT 2: a third solution (if you are working on "normal" architectures) is to use char or unsigned char instead of uint8_t. Such types are guaranteed to alias everything. Not valid because the conversion to the message type might not work, see here

like image 148
Alberto M Avatar answered Oct 06 '22 01:10

Alberto M


By working with the individual bytes, you can avoid all pointer casting and eliminate portability issues with endianness and alignment:

uint32_t decodeUInt32(uint8_t *p) {
    // Decode big-endian, which is network byte order.
    return (uint32_t(p[0])<<24) |
           (uint32_t(p[1])<<16) |
           (uint32_t(p[2])<< 8) |
           (uint32_t(p[3])    );
}

void onRxMessage() {
    // Detect message type
    if ( decodeUInt32(rxBuffer) == MESSAGE1 ) {
        // Process Msg1 message.
        if ( decodeUInt32(rxBuffer+4) == 0 ) {
            // Some code here;
        }
        return;
    }
    // Process other message types.
}
like image 39
Vaughn Cato Avatar answered Oct 06 '22 00:10

Vaughn Cato