靈活的陣列成員

Version >= C99

輸入宣告

具有至少一個構件的結構可以另外在結構的末端包含未指定長度的單個陣列構件。這稱為靈活的陣列成員:

struct ex1 
{
    size_t foo;
    int flex[];
};

struct ex2_header 
{
    int foo;
    char bar;
};

struct ex2 
{
    struct ex2_header hdr;
    int flex[];
};

/* Merged ex2_header and ex2 structures. */
struct ex3 
{
    int foo;
    char bar;
    int flex[];
};

對尺寸和填充的影響

在計算結構的大小時,靈活的陣列成員被視為沒有大小,儘管該成員與結構的前一個成員之間的填充可能仍然存在:

/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));

/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));

/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));

靈活的陣列成員被認為具有不完整的陣列型別,因此無法使用 sizeof 計算其大小。

用法

你可以使用包含靈活陣列成員的結構型別宣告和初始化物件,但是你不能嘗試初始化靈活陣列成員,因為它被視為不存在。禁止嘗試執行此操作,否則將導致編譯錯誤。

類似地,在以這種方式宣告結構時,不應嘗試為靈活陣列成員的任何元素賦值,因為在結構的末尾可能沒有足夠的填充以允許靈活陣列成員所需的任何物件。但是,編譯器不一定會阻止你這樣做,因此這可能導致未定義的行為。

/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};

e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */

你可以選擇使用 malloccallocrealloc 來分配具有額外儲存空間的結構,然後將其釋放,這樣你就可以根據需要使用靈活的陣列成員:

/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));

/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));

/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));

pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */

Version < C99

‘結構黑客’

在 C99 之前不存在靈活的陣列成員,並將其視為錯誤。一個常見的解決方法是宣告一個長度為 1 的陣列,這種技術稱為 struct hack

struct ex1 
{
    size_t foo;
    int flex[1];
};

但是,這會影響結構的大小,而不像真正的靈活陣列成員:

/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));

要使用 flex 成員作為靈活的陣列成員,你可以使用 malloc 分配它,如上所示,除了 sizeof(*pe1)(或等效的 sizeof(struct ex1))將替換為 offsetof(struct ex1, flex) 或更長的,型別不可知的表示式 sizeof(*pe1)-sizeof(pe1->flex)。或者,你可以從靈活陣列的所需長度中減去 1,因為它已經包含在結構大小中,假設所需長度大於 0.相同的邏輯可以應用於其他用法示例。

相容性

如果需要與不支援靈活陣列成員的編譯器相容,可以使用如下 FLEXMEMB_SIZE 定義的巨集:

#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif

struct ex1 
{
    size_t foo;
    int flex[FLEXMEMB_SIZE];
};

在分配物件時,你應該使用 offsetof(struct ex1, flex) 表單來引用結構大小(不包括靈活的陣列成員),因為它是唯一一個在支援靈活陣列成員的編譯器和不支援靈活陣列的編譯器之間保持一致的表示式:

struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));

另一種方法是使用前處理器有條件地從指定長度中減去 1。由於此形式中不一致和一般人為錯誤的可能性增加,我將邏輯移動到一個單獨的函式中:

struct ex1 *ex1_alloc(size_t n)
{
    struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
    if (n != 0)
        n--;
#endif
    return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...

/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);