Skip to content
Published at:

弹性数组成员

简介

弹性数组成员,英文叫Flexible array member。这个特性在C99之前,GCC和微软的编译器就已经实现了;在C99时才把它正式的标准化

定义规则:

  • 弹性数组成员必须是最后一个成员
  • 结构中必须至少有一个成员
  • 弹性数组成员的声明类型类似于普通数据,括号中的长度是空,有时候可能是0

简单示例:

c
// 弹性数组成员的结构体
struct foo {
    int64_t len;
    char    data[];  // 弹性数组成员,或者char arr[0]
};

另一个已知的知识点是:数组名就是数组的首地址,数组名和指针等效,下面看示例

示例

主要对比下面两个结构体的data成员

c
#include <stdio.h>
#include <stdlib.h>

// 弹性数组成员的结构体
struct foo {
    int64_t len;
    char    data[];  // or char arr[0]
};

// 普通结构体
struct bar {
    int64_t len;
    char*   data;
};

int main(int argc, char* argv[]) {
    // sizeof
    printf("sizeof(int32_t):                        %lu\n", sizeof(int64_t));       // => 8
    printf("sizeof(char*):                          %lu\n", sizeof(char*));         // => 8
    printf("struct with flexable array member:      %lu\n", sizeof(struct foo));    // => 8
    printf("struct without flexable array member:   %lu\n", sizeof(struct bar));    // => 16

    // use
    struct foo* f = malloc(sizeof(struct foo) + 128);
    struct bar  b = {
         .len  = 128,
         .data = malloc(128),
    };

    return 0;
}

从程序输出可以看出:弹性数组成员data不占空间。

对比弹性数组成员的结构体普通的结构体有弹性数组成员的结构体有以下特点:

  • 节省空间:是的,指针本身也需要存储空间,struct bar中的data成员是一个指针,而数组名就是数组的首地址
  • 成员内存空间上的连续性:弹性数组的内存地址和它之前的数据成员的地址是连续的
  • 访问高性:因为空间连续,访问时内存的空间局部性也更好些

另外一个在使用上的区别是:弹性数组成员的整个结构体都要分配在堆上

如何理解

那如何理解弹性数组成员( struct foo 中的 data成员)不占空间呢?正常来讲,这个已经超过了对内存结构的认知了。如果要用一个合理性的解释能解释通的话,就是:编译器的语法糖;与普通结构体相比,提供了访问data数据的方便性:

c
#include <stdio.h>
#include <stdlib.h>

// 弹性数组成员的结构体
struct foo {
    int64_t len;
    char    data[];  // or char arr[0]
};

// 普通结构体
struct bar {
    int64_t len;
};

int main(int argc, char* argv[]) {
    char* data = NULL;

    // 弹性数组成员的结构体
    struct foo* f = malloc(sizeof(struct foo) + 128);
    data          = f->data;
    // do something with data

    // 普通结构体
    struct bar* b = malloc(sizeof(struct bar) + 128);
    data          = (char*)b + sizeof(struct bar);
    // do something with data

    return 0;
}

应用场景

弹性数组成员在内存空间排布上是连续,内存尾部长度可以很灵活;也就是这个不定长的buffer给使用上带来的灵活性,会诞生一些应用场景,比如:

  • 日志:前面的格式都是固定的(时间、级别、文件名、行号),日志正文的msg是不定长的
  • 协议:前面的协议头可能是固定格式的,数据payload段可能是不定长的

Ref:

Updated at: