19

聊聊c语言的flexible array member | yoko blog

 4 years ago
source link: https://pengrl.com/p/20013/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

聊聊c语言的flexible array member

2020-01-22 | c/c++
| 1.2k

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

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

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

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

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

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

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

1
foo.b = malloc(128);

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

正确的使用方式是:

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

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

好处与代价

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

1
2
3
4
struct Foo {
int a;
char *b;
};

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

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

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

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

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

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

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

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

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

1
2
3
4
5
6
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 []'

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

1
2
3
4
struct Foo {
char b[];
int a;
};

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

本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20013/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK