灵活的阵列成员

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);