【发布时间】:2021-02-06 02:10:58
【问题描述】:
我想定义一个结构来映射通过串行流接收的数据包的字节。数据包中的有效载荷字节数是可变的,有效载荷字节后面有一个 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() 方法在数据包的可变长度 data[] 之后将 uint16_t& 返回到 2 字节 CRC?
顺便说一句...我知道你们中的一些人认为我的解决方案在无法处理硬件中未对齐访问的系统上被破坏了。我也知道你们中的一些人认为我的解决方案在字节序与串行流的字节顺序不匹配的系统上被破坏了。向那些想到这一点的人致敬。我的例子被大大简化了。实际上,我的结构使用用户定义类型 ube16_t(无符号大端 16 位类型),但同样的问题也适用。我不想通过包含ube16_t 类型的定义来使事情复杂化。
【问题讨论】:
-
@DavidC.Rankin 正如我所提到的,我的示例中的结构是从我实际使用的结构中简化的。在“真实事物”中,数据是大量有效载荷类型的匿名联合。但
data[1]是该联合的一部分,它是crc()方法用作有效负载基地址的名称。 -
是的,使用
data[1]struct-hack 很好,但是您必须声明一个指向 struct 的指针并为有效负载的大小分配额外的空间。但是,那么data[]需要是最后一个元素或您的结构。好的,所以如果我知道发生了什么,您正在为您的有效负载创建空间,它将分配比data更大的空间,以便保存crc(),然后用于查找变量数据的结尾包含在data[]是/否?还是crc()只是作为data中的第一个两个字节? -
@DavidC.Rankin 在接收方向上,数据包到达一个 UART 缓冲区(一个简单的
uint8_t数组),一旦它到达那里,我将第一个 0xEE 字节的地址投射到该缓冲区中为reinterpret_cast<SPacket*>,然后计算数据包的CRC,并确保它匹配。 (除了crc()方法,我还有一个computeCrc()方法来计算CRC 应该。)在传输方向,我使用另一个缓冲区,预先分配到最大值任何数据包的大小,并将该缓冲区的起始地址转换为<SPacket*>,填写数据包,然后发送。 -
@DavidC.Rankin -
data[]数组的长度实际上是可变的。在我的结构中,它是最后一个固定偏移成员,对于唯一的可变偏移成员(CRC),我计算它的地址并将其作为crc()方法中的引用返回。这样,结构的所有成员都被命名,即使是偏移量不固定的成员。 -
这就是我的想法,你为你的有效载荷分配。
data只是一个标记地址,crc只是你的uint16_t引用,所以你的数据是(data+3)[index]到最后。为什么不将有效负载引用为(data + 3)[length]并完全避免类型双关语? (尽管由于data不是SPacket的最后一个成员,你冒着被一两个填充字节搞砸的风险......)
标签: c++ gcc strict-aliasing