本文将flexible array member翻译为弹性数组(成员),将介绍弹性数组的语法,好处与代价,以及扩展聊聊关于c语言操作内存灵活性方面的思考。

语法

struct Foo {
    int a;
    char b[]; // 有时也写成 char b[0];
};

如上面例子展示,将结构体的最后一个数据成员定义为不写长度(或长度为0)的数组即为弹性数组。

这种写法,b数据成员不占用大小,看如下代码:

struct Foo foo;
printf("%d %d\n", sizeof(foo), sizeof(foo.a));

在我的mac电脑上使用 clang-1100.0.33.12 编译,打印结果为 4 4 ,说明foo变量大小等于foo变量中a数据成员的大小。

使用如下方法为弹性数组分配内存会产生编译错误:

foo.b = malloc(128);

编译错误信息: error: array type 'char []' is not assignable

正确的使用方式是:

struct Foo *foo = malloc(sizeof(struct Foo) + 128);

该方式共申请了 4+128 字节大小的内存,该132字节内存是连续的,前4个字节分配给 foo->a ,后128字节可以通过 foo->b 访问。

好处与代价

一般来说,弹性数组用于元素个数在运行期动态决定的场景。你可能会说,为什么不直接用指针呢,就像下面这样:

struct Foo {
    int a;
    char *b;
};

上面这种写法确实可以实现同样的功能,但是存储相同大小的数据时,两种方式存在一些区别:

第一,使用这种写法,b指针变量本身要占用内存,注意,不管你是否为b指针分配内存,即使 b==NULL ,变量自身都需要占用内存。在64位系统,一个指针变量是8个字节,别小看这8个字节,在内存总量比较小的场景,或结构体变量非常多的场景还是很客观的。

第二,使用弹性数组,弹性数组的内存地址和它之前的数据成员的地址是连续的。访问时内存的空间局部性也更好些。

但是话说回来,使用弹性数组并不只有好处,它也有代价。

弹性数组的方式,由于结构体中的数据成员和后面挂着的这个数据成员是通过一个malloc申请的内存,这也意味:

第一,整个结构体都要分配在堆上。

第二,当需要对数组内存进行扩容时,你需要对整块内存realloc。

弹性数组是语法糖,有威力的是c语言操作内存的自由度

其实,我们不使用弹性数组也可以达到弹性数组的效果,如下面代码:

struct Foo {
    int a;
};

struct Foo *foo = malloc(sizeof(struct Foo) + 128);
char *b = (char *)foo + sizeof(struct Foo);

弹性数组只是一个语法糖,它在结构体最后增加一个成员变量,让我们可以使用 foo->b 这种方式,直接访问结构体之后的内存。事实上,你如果自己计算偏移量,也可以到达一样的效果。

这里要撇开弹性数组,聊聊闲篇。

在操作内存方面,c语言给它的使用者提供了非常高的自由度,它自身并不标记内存中存储的是什么类型的数据,使用者可以对内存地址做任意前后偏移,通过指针类型强转,解引用,可以把内存按任意类型解析,写入,读取。当然,前提是不要发生越界,并且读写一致,逻辑符合使用者的预期。

自由度越高,就越可以在更多的场景做更多的优化。但带来的代价,则是和高级语言相比,可读性差些,也容易写出bug。当然,这句话只对于同等水平的初中级程序员有效哈,高手可以无视。

语法补充

最后,对语法做些补充。

第一,在我的环境,如果使用 char b[] 这种写法,再使用 sizeof(foo.b) 获取b数据成员大小,将产生编译错误:

error: invalid application of 'sizeof' to an incomplete type 'char []'

第二,弹性数组一般作为结构体的最后一个数据成员出现,如下代码会产生编译错误:

struct Foo {
    char b[];
    int a;
};

编译错误信息: error: flexible array member 'b' with type 'char []' is not at the end of struct

原文链接: https://pengrl.com/p/20013/

原文出处: yoko blog ( https://pengrl.com )

原文作者: yoko ( https://github.com/q191201771 )

版权声明:本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。

相关文章