柔性數組成員 是 C99 標準引入的特性,允許結構體的最後一個成員聲明為不指定大小的數組:
struct Flexible {
size_t len;
char data[]; // 柔性數組成員,無大小
};
早期 GCC 支持 char data[0] 作為零長度數組擴展,C99 正式標準化為 char data[]。
| 規則 | 說明 |
|---|---|
| 位置固定 | 必須是結構體的最後一個成員 |
| 前置成員 | 結構體至少包含一個其他命名成員 |
| sizeof 行為 | sizeof(struct Flexible) 不包含 柔性數組大小,只計算固定部分(含對齊填充) |
| 不能單獨聲明 | int arr[0]; 或 int arr[]; 在塊作用域非法,只能在結構體中 |
| 不可用於嵌套 | 包含柔性數組的結構體不能作為數組元素、或作為另一結構體的非最後成員 |
struct Packet {
uint16_t id; // 偏移 0, 大小 2
uint16_t flags; // 偏移 2, 大小 2
char payload[]; // 偏移 4, 大小 0 (不計入 sizeof)
};
// sizeof(Packet) == 4 (假設無額外填充)
/* 動態分配 100 字節 payload */
struct Packet *p = malloc(sizeof(struct Packet) + 100);
p->id = 0x1234;
p->flags = 0x0001;
memcpy(p->payload, "data", 4); // 直接訪問柔性數組
關鍵優勢:payload 與結構體固定部分在內存中連續分佈,僅需一次 malloc/free,消除指針間接訪問,提升緩存局部性,減少碎片。
| 維度 | 柔性數組 char data[] | 指針 char *data |
|---|---|---|
| 內存分配次數 | 1 次 (整體) | 2 次 (結構體 + 數據) |
| 內存連續性 | 連續 | 通常不連續 |
| 釋放操作 | 1 次 free | 2 次 free (先數據後結構體) |
| sizeof 結構體 | 不含數據大小 | 含指針大小 (4/8 字節) |
| 賦值拷貝 | 淺拷貝危險 (需手動深拷貝) | 淺拷貝同樣危險 |
| 調試便利性 | 數據緊跟結構體,易觀察 | 需跟蹤指針 |
#pragma pack(push, 1)
struct MsgHeader {
uint32_t magic; // 0xDEADBEEF
uint16_t version;
uint16_t cmd_id;
uint32_t body_len; // body 長度
uint8_t body[]; // 柔性數組承載 body
};
#pragma pack(pop)
// 發送端
size_t total = sizeof(struct MsgHeader) + json_len;
struct MsgHeader *msg = malloc(total);
msg->magic = 0xDEADBEEF;
msg->body_len = json_len;
memcpy(msg->body, json_str, json_len);
send(fd, msg, total, 0);
free(msg);
// 接收端 (兩次讀取)
struct MsgHeader hdr;
recv(fd, &hdr, sizeof(hdr), MSG_WAITALL);
struct MsgHeader *full = malloc(sizeof(hdr) + hdr.body_len);
memcpy(full, &hdr, sizeof(hdr));
recv(fd, full->body, hdr.body_len, MSG_WAITALL);
process(full->body, hdr.body_len);
free(full);
struct SDS {
size_t len; // 已用長度
size_t free; // 剩餘空間
char buf[]; // 實際數據 + '\0'
};
// 創建
struct SDS *sds_new(const char *init) {
size_t len = strlen(init);
struct SDS *s = malloc(sizeof(struct SDS) + len + 1);
s->len = len;
s->free = 0;
memcpy(s->buf, init, len + 1);
return s;
}
// 擴容追加
void sds_cat(struct SDS **sp, const char *append) {
size_t add = strlen(append);
struct SDS *s = *sp;
if (s->free < add) {
size_t new_cap = (s->len + add) * 2;
s = realloc(s, sizeof(struct SDS) + new_cap + 1);
s->free = new_cap - s->len;
*sp = s;
}
memcpy(s->buf + s->len, append, add + 1);
s->len += add;
s->free -= add;
}
struct { int a; char flex[]; int b; } 編譯報錯或行為未定義。必須在最後。struct Flexible f; 非法,因為編譯器無法確定總大小。只能用指針 + malloc。void foo(struct Flexible f) 非法,同理。傳指針。struct Flexible arr[10]; 非法,元素大小不定。用指針數組。struct Flexible *p2 = p1; 導致雙重釋放。需手動深拷貝 或引用計數。若柔性數組需特定對齊 (如 SIMD 指令要求 32 字節對齊):
struct AlignedFlex {
size_t len;
alignas(32) char data[]; // C11 _Alignas(32) 或 C++11 alignas
};
// 分配時手動對齊
void *ptr = aligned_alloc(32, sizeof(struct AlignedFlex) + 1024);
柔性數組是 C 語言實現變長結構體的標準、高效、類型安全方案。它將「元數據」與「數據」融合在單一連續內存塊中,極大簡化了內存管理,廣泛應用於系統編程、網絡協議、數據庫存儲引擎、高性能庫開發。掌握其聲明規則、sizeof 語義、分配模式、拷貝語義,是進階 C 程序員的必修課。