C 灵活的数组成员,不必是最后一个
我试图弄清楚C中是否有一种变通方法,可以在结构中使用灵活的数组成员,这不是最后一种方法。例如,这会产生编译错误:C 灵活的数组成员,不必是最后一个,c,struct,c99,c11,flexible-array-member,C,Struct,C99,C11,Flexible Array Member,我试图弄清楚C中是否有一种变通方法,可以在结构中使用灵活的数组成员,这不是最后一种方法。例如,这会产生编译错误: typedef struct __attribute__((__packed__)) { uint8_t slaveAddr; /*!< The slave address byte */ uint8_t data[]; /*!< Modbus frame data (Flexible Array
typedef struct __attribute__((__packed__))
{
uint8_t slaveAddr; /*!< The slave address byte */
uint8_t data[]; /*!< Modbus frame data (Flexible Array
Member) */
uint16_t crc; /*!< Error check value */
} rtuHead_t;
typedef struct\uuuuu attribute\uuuuuuuu((\uuuuu packed\uuuuuu))
{
uint8\u t slaveAddr;/*!<从机地址字节*/
uint8_t data[];/*!
这不会产生错误:
typedef struct __attribute__((__packed__))
{
uint8_t slaveAddr; /*!< The slave address byte */
uint8_t data[]; /*!< Modbus frame data (Flexible Array
Member) */
} rtuHead_t;
typedef struct __attribute__((__packed__))
{
rtuHead_t head; /*!< RTU Slave addr + data */
uint16_t crc; /*!< Error check value */
} rtu_t;
typedef struct\uuuuu attribute\uuuuuuuu((\uuuuu packed\uuuuuu))
{
uint8\u t slaveAddr;/*!<从机地址字节*/
uint8_t data[];/*!
但是不起作用。如果我有一个字节数组:data[6]={1,2,3,4,5,6}
并将其强制转换为rtu_t
,则crc
成员将等于0x0302
,而不是0x0605
在结构的中间(或结构中的结构)中有没有使用灵活数组成员的方法?
< P>一个灵活的数组成员<强>必须< /St>是结构的最后一个成员,包含一个灵活数组成员的结构可能不是数组或另一个结构的成员。 这种结构的预期用途是动态分配它,为其他成员加上柔性成员的0个或更多元素留出足够的空间 您试图做的是将一个结构覆盖到一个内存缓冲区上,该缓冲区包含只需访问成员即可解析的数据包数据。在这种情况下,这是不可能的,一般来说,由于对齐和填充问题,这样做不是一个好主意正确的方法是编写一个函数,将数据包一次反序列化一个字段,并将结果放入用户定义的结构中。这不能在ISO C中完成。但是 GCC有一个扩展,允许在结构中定义可变修改的类型。所以你可以这样定义:
#include <stddef.h>
#include <stdio.h>
int main() {
int n = 8, m = 20;
struct A {
int a;
char data1[n];
int b;
float data2[m];
int c;
} p;
printf("offset(a) = %zi\n", offsetof(struct A, a));
printf("offset(data1) = %zi\n", offsetof(struct A, data1));
printf("offset(b) = %zi\n", offsetof(struct A, b));
printf("offset(data2) = %zi\n", offsetof(struct A, data2));
printf("offset(c) = %zi\n", offsetof(struct A, c));
return 0;
}
问题是该类型只能在函数体范围内定义,因此不能用于将参数传递给其他函数
但是,它可以传递给嵌套函数,这是另一个GCC扩展。例如:
int main() {
... same as above
// nested function
int fun(struct A *a) {
return a->c;
}
return fun(&p);
}
灵活数组成员只能放置在结构的末尾。这就是C标准6.7.2.1对它们的定义: 作为特例,具有多个命名成员的结构的最后一个元素可以 具有不完整的数组类型;这称为灵活数组成员 但就具体情况而言,它们也是错误问题的错误解决方案。错误的问题是“如何在C结构中存储可变大小的Modbus数据协议帧”<代码>结构通常首先最好避免。不幸的是,我们C程序员几乎被洗脑,在每一种情况下都使用
struct
,以至于我们不经再三考虑就声明了一个
结构有各种各样的问题,最明显的是对齐/填充问题,它只能通过非标准扩展来解决,如gcc\uuuu属性((\uuu packed\uuuu))
或\pragma pack(1)
。但即使你使用了这些,你最终也会得到一个块,编译器仍然可以访问未对齐的块——你只告诉它去掉填充“我知道我在做什么”。但是,如果你继续访问该内存,它可能是一个未对齐的访问
然后是可变大小协议的问题。根据接收到的数据量反复调整内存块的大小,实际上除了膨胀和程序执行开销之外,并没有什么效果。这样做可以节省多少内存?大约10到100字节?这在低端MCU中也算不了什么。因为您只需要在RAM中同时保留几个帧
事实证明,您必须分配足够的内存来存储出现过的最大帧,因为您的程序必须处理最坏的情况。然后,您还可以静态地分配这么多内存。更快、更安全、更确定
还有另一个你似乎没有解决的问题,那就是网络持久性。Modbus使用big-endian,CRC以big-endian计算。因此,结构末尾的uint16\t
成员只是坐在那里制造问题。即使您决定使用一些非标准GNU VLA扩展来调整每个帧的大小
我建议你忘掉这些结构
快速、便携和安全的解决方案是简单地使用uint8_t帧[MAX]
其中MAX
是帧可能具有的最大字节大小。使用struct只是为帧中的一个特定字节指定一个变量名,实际上并不会在其本身添加任何内容。您真正想要的是让可读代码轻松解释每个字节的作用,而不是原始数据的匿名缓冲区
这也可以在访问该uint8\u t
数组时使用该数组的命名索引(例如enum
)来完成。struct versionframe.slave\u addr=x之间生成的可读性、用途或机器代码没有差异代码>和数组版本帧[slave_addr]=x代码>。(除前者可能导致机器代码访问不对齐外。)
无论如何,您都需要逐个字节地访问CRC,因为您首先需要使用CPU endianess计算它,然后将其转换为网络endianess。例如:
frame[fcs_high] = checksum >> 8;
frame[fcs_low] = checksum & 0xFF;
与结构不同,该代码不依赖于CPU端性,而结构只在big-endian上按预期工作
frame[fcs_high] = checksum >> 8;
frame[fcs_low] = checksum & 0xFF;