11.1 柔性数组成员
柔性数组成员 (Flexible Array Member, FAM) 是 C99 引入的结构体能力,用于表达“头部固定、尾部可变长”的对象布局。它常用于网络报文、内存块头、字符串池等场景。
1. 基本规则
柔性数组成员必须是结构体的最后一个成员,类型写成不带长度的数组声明。该结构体必须至少还有一个具名成员,因此“只含柔性数组成员”不合法。
c
struct packet {
size_t len;
unsigned char data[];
};1
2
3
4
2
3
4
sizeof(struct packet) 不包含 data 的实际存储区;它只计算固定头部,并可能包含对齐填充。
2. 正确分配方式
柔性数组成员对象通常通过一次动态分配获得,分配大小 = 头部大小 + 尾部数据长度。推荐使用 offsetof 或 sizeof(struct_type) 计算头部,并在乘加前做溢出检查。
c
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
struct packet {
size_t len;
unsigned char data[];
};
struct packet *packet_create(const unsigned char *src, size_t n) {
if (n > SIZE_MAX - sizeof(struct packet)) {
return NULL;
}
struct packet *p = malloc(sizeof(struct packet) + n);
if (p == NULL) {
return NULL;
}
p->len = n;
if (n != 0) {
memcpy(p->data, src, n);
}
return p;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这种“一次分配”的布局比“头部 + 单独尾部指针”更紧凑,缓存局部性也更好。
3. 使用边界
柔性数组成员不是普通数组对象,不能单独赋值,也不能对“未分配尾部空间”的对象直接访问 data[i]。另外,包含柔性数组成员的结构体类型不应作为数组元素使用,这会破坏对象边界语义。
4. 赋值与复制语义边界
对“包含柔性数组成员的结构体对象”做赋值时,只会复制 sizeof(结构体类型) 这部分可见头部,不会自动复制动态尾部区域。也就是说,若你需要完整复制“头部 + 尾部数据”,应显式按总长度分配并调用拷贝逻辑,而不是依赖结构体赋值语句。
5. 嵌套与容器设计
标准对柔性数组成员类型有额外限制:这类结构体不应再作为其他结构体成员或数组元素使用。设计容器时,通常应把它放在最外层对象,并通过指针引用传递。这样既符合语义边界,也能避免对象布局在不同实现上出现歧义。