在閱讀 Linux 內核或開源專案代碼時,你或許會遇到一種奇特的結構體定義:char data[0]。乍看之下,數組長度為零似乎違背常理,但這卻是 C 語言中實現「柔性數組」的經典技巧。本文將深入剖析這種設計的原理、記憶體佈局差異,並透過實際代碼示範其在動態資料封裝中的優勢。
典型的應用場景是網路封包、訊息佇列或任意長度的資料載荷。考慮以下結構體:
struct buffer {
int data_len; // 資料長度
char data[0]; // 起始位址,不佔用空間
};
此處 data[0] 不佔用任何記憶體空間,它只是一個符號標記,指向緊隨結構體之後的記憶體區塊。當我們分配記憶體時,只需在 malloc 中加上額外需要的資料長度:
struct buffer *buf = malloc(sizeof(struct buffer) + payload_len);
這樣,buf->data 便直接指向載荷資料的起始位址,實現了結構體與變長資料的連續儲存,避免了額外指標間接存取的開銷,也減少了記憶體碎片。
為了釐清 data[0]、data[] 與 char *data 的差異,我們編寫測試程式:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
typedef struct { int data_len; char data[0]; } buff_st_1;
typedef struct { int data_len; char *data; } buff_st_2;
typedef struct { int data_len; char data[]; } buff_st_3;
int main() {
printf("sizeof(buff_st_1)=%zu\n", sizeof(buff_st_1));
printf("sizeof(buff_st_2)=%zu\n", sizeof(buff_st_2));
printf("sizeof(buff_st_3)=%zu\n", sizeof(buff_st_3));
buff_st_1 b1; buff_st_2 b2; buff_st_3 b3;
printf("b1: %p, data_len: %p, data: %p\n", &b1, &b1.data_len, b1.data);
printf("b2: %p, data_len: %p, data: %p\n", &b2, &b2.data_len, b2.data);
printf("b3: %p, data_len: %p, data: %p\n", &b3, &b3.data_len, b3.data);
return 0;
}
結果分析:
buff_st_1 與 buff_st_3 大小相同(僅含 int),data 位址緊貼 data_len 之後。buff_st_2 多出一個指標大小(32 位元系統為 4 Bytes),data 指向獨立分配的記憶體,非連續分佈。結論:data[0] 與 data[] 皆為 C99 柔性數組成員,不佔空間且位址連續;char *data 則需額外 malloc,管理成本較高。
以下範例示範如何將 student_st 結構體動態封裝進緩衝區:
typedef struct { uint32_t id; uint32_t age; } student_st;
void print_stu(const student_st *s) {
printf("id:%u, age:%u\n", s->id, s->age);
}
int main() {
student_st *stu = malloc(sizeof(student_st));
stu->id = 100; stu->age = 23;
// 方案一:柔性數組 data[0]
buff_st_1 *b1 = malloc(sizeof(buff_st_1) + sizeof(student_st));
b1->data_len = sizeof(student_st);
memcpy(b1->data, stu, b1->data_len);
print_stu((student_st *)b1->data);
free(b1); // 一次釋放
// 方案二:指標 char *data
buff_st_2 *b2 = malloc(sizeof(buff_st_2));
b2->data_len = sizeof(student_st);
b2->data = malloc(b2->data_len);
memcpy(b2->data, stu, b2->data_len);
print_stu((student_st *)b2->data);
free(b2->data); free(b2); // 兩次釋放
// 方案三:柔性數組 data[] (C99 標準寫法)
buff_st_3 *b3 = malloc(sizeof(buff_st_3) + sizeof(student_st));
b3->data_len = sizeof(student_st);
memcpy(b3->data, stu, b3->data_len);
print_stu((student_st *)b3->data);
free(b3); // 一次釋放
free(stu);
return 0;
}
關鍵優勢總結:
memcpy/網路發送極其高效。malloc/free,無記憶體洩漏風險。data 位址可直接強轉為任意結構體指標,實現多態載荷。C99 標準化了 char data[] 寫法,語意更清晰,建議新代碼採用 data[];data[0] 為 GCC 擴展,雖廣泛相容但屬非標準。若需支援極舊編譯器,data[0] 仍是不二之選。