将 uint8_t 缓冲区字节转换为另一种类型时如何绕过严格别名?

问题描述

我想定义一个结构来映射通过串行流接收的数据包的字节。数据包中的有效载荷字节数是可变的,有效载荷字节后有一个 16 位的 CRC。 (数据包字节布局,最后是 CRC,是遗留的,不能改变。)结构的全部意义在于使用这些数据包在数据包缓冲区中查找命名字段而不关心字节的源代码数据包每个部分的偏移量。由于 CRC 的偏移量取决于有效载荷数据的长度,我为结构编写了一个方法,该方法简单地计算适当的地址并将 uint16_t& 返回到 2 字节 CRC,使用的源代码从中它可以读或写CRC。

这是我编写的数据包结构的简化版本,以及 crc() 方法

#include <stdint.h>

struct SPacket
{
   uint8_t  startByte;  // always 0xEE
   uint8_t  identity;   // device identity byte
   uint8_t  ctrl_bits;
   uint8_t  seq_nbr;
   uint16_t length;     // length of data[] in bytes
   uint8_t  data[1];    // placeholder for 1 or more payload data bytes
   uint16_t &crc();     // returns a reference to the packet's 16-bit CRC following the data
};

uint16_t &SPacket::crc()
{
   return *(uint16_t*)(data + length);
}

但是当我用 GCC 编译它时,我得到这个错误

C:\Users\phonetagger> g++ --version
g++ (GCC) 4.8.1
copyright (C) 2013 Free Software Foundation,Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or fitness FOR A PARTIculaR PURPOSE.

C:\Users\phonetagger> g++ -o test.o -c -O3 -Wall test.cpp
C:\Users\phonetagger\test.cpp: In member function 'uint16_t& SPacket::crc()':
C:\Users\phonetagger\test.cpp:17:37: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
    return *(uint16_t*)(data + length);
                                     ^

顺便说一句,如果我删除 -O3 或 -Wall,警告就会消失,但我不能这样做;我的同事会冲我尖叫。有没有办法通过改变我的源代码来让 GCC 满意,但仍然有一个成员 crc() 方法,该方法uint16_t& 返回到数据包的可变长度 {{1} 之后的 2 字节 CRC }?

顺便说一句...我知道你们中的一些人认为我的解决方案在无法处理硬件中未对齐访问的系统上被破坏了。我也知道你们中的一些人认为我的解决方案在字节序与串行流的字节顺序不匹配的系统上被破坏了。感谢那些想到这一点的人。我的例子大大简化了。实际上,我的结构使用用户定义的类型 data[](无符号大端 16 位类型),但同样的问题也适用。我不想通过包含类型 ube16_t 的定义来使事情​​复杂化。

解决方法

好吧,Galik 建议提供两个单独的方法 uint16_t getCrc()void setCrc(uint16_t crc) 可能是解决此问题的更好方法,但我确实找到了解决严格别名警告的方法:

#include <stdint.h>
#include <stddef.h>

struct SPacket
{
   uint8_t  startByte;  // always 0xEE
   uint8_t  identity;   // device identity byte
   uint8_t  ctrl_bits;
   uint8_t  seq_nbr;
   uint16_t length;     // length of data[] in bytes
   uint8_t  data[1];    // placeholder for 1 or more data bytes
   uint16_t &crc();     // returns a reference to the packet's 16-bit CRC following the data
};

uint16_t &SPacket::crc()
{
   return *(uint16_t*)(((size_t)data) + length);
}

...或:

uint16_t &SPacket::crc()
{
   return *reinterpret_cast<uint16_t*>(reinterpret_cast<size_t>(data) + length);
}