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

我试图弄清楚C中是否有一种变通方法,可以在结构中使用灵活的数组成员,这不是最后一种方法。例如,这会产生编译错误:

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 version
frame.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;