在不破坏严格别名规则的情况下,在现代GCC/C++中使用网络缓冲区的正确方法

Sap*_*Sap 7 c++ gcc strict-aliasing c++11

该计划 - 某种旧式网络消息传递:

// 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.
}
Run Code Online (Sandbox Code Playgroud)

此代码违反了现代GCC中的严格别名(并归结为现代C++中未指定的行为).解决问题的正确方法是什么(使代码不会引发"严格别名"警告)?

PS如果rxBuffer定义为:

union __attribute__((packed)) {
  uint8_t[MAX_MSG_SIZE] rawData;
} rxBuffer;
Run Code Online (Sandbox Code Playgroud)

然后我将&rxBuffer转换为其他指针它不会引起任何警告.但它是安全,正确和便携的方式吗?

Alb*_*o M 5

定义rxBuffer为一个指向unionuint8_t[MAX_SIZE],MsgHeader,Msg1和任何类型的你打算投给.请注意,这仍然会破坏严格的别名规则,但在GCC中它保证可以作为非标准扩展.

编辑:如果这样的方法会导致过于复杂的声明,那么完全可移植(如果较慢)的方法是将缓冲区保持为简单状态uint8_t[],memcpy并在必须重新解释时立即将其保存到适当的消息结构中.这种方法的可行性显然取决于您的性能和效率需求.

编辑2:第三种解决方案(如果您正在研究"正常"架构)是使用charunsigned char代替uint8_t.这些类型保证为所有内容添加别名.无效,因为转换为消息类型可能不起作用,请参见此处