弹性数组成员
简介
弹性数组成员,英文叫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
段可能是不定长的