0%

C 迷你系列(二)柔性数组

引言

当我们阅读 Redis 源码的时候,发现有些结构体会有如下一个数组元素:

1
2
3
4
5
struct sdshdr5 
{
unsigned char flags;
char buf[];
};

思考一下的话,你会发现,我们在声明数组的时候如果不指定大小就需要声明的时候就得初始化,否则就会编译告警。但是恰恰上图所示的数组确可以编译通过。行吧,那么接下来要怎么赋值呢?
假如我们定义如下结构体:

1
2
3
4
5
6
struct People 
{
char *username;
int age;
char hobbies[];
};
  • 法一 直接赋值
1
2
3
4
5
6
7
8
9
10
struct People 
{
char *username;
char hobbies[];
} P = {
"jack",
{
"PlayGames"
}
};
  • 法二 变量赋值
1
2
3
4
5
6
7
8
9
10
int main()
{
struct People p = {
"jack",
{
"playGames"
}
};
return 0;
}

方法二编译会报错 Initialization of flexible array member is not allowed ,也就是说是不运行这样赋值的。那么也就是说只能按照法一来赋值了吗?但是看 Redis 时并没有采用上述的方式,那么到底要如何使用呢?

柔性数组

以上数组就是柔性数组,什么是柔性数组?用最简单的话说就是不指定长度的数组,而且该数组只能作为结构体的最后一个成员且该成员不能是结构体的唯一成员。

好处

  • 它可以按需分配内存
  • 不占内存空间
  • 内存连续
  • 防止内存碎片

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
// 我们声明一个指针变量
struct People *p;
// 打印其大小
printf("size of p is %lu\n", sizeof *p);
// 为结构体中的数组分配 10 字节大小的空间
p = (struct People *) malloc(sizeof(struct People) + 10 * sizeof(char));
// 打印内存
printf("size of p is %lu\n", sizeof *p);
char *hobby = "PlayGame";
// 为数组填充元素
strncpy(p->hobbies, hobby, strlen(hobby));
// 再次打印结构大小
printf("size of p is %lu\n", sizeof *p);
// 查看元素是否填充
printf("%s\n", p->hobbies);

return 0;
}

大家可以思考一下输出。。。

1
2
3
4
size of p is 8
size of p is 8
size of p is 8
PlayGame

我们发现,p 指向的结构体内存始终是 8 字节,而这个 8 字节就是 username 指针占用的内存大小,也就是说柔性数组不占内存,即使为它分配好内存。

对比指针

将 hobbies 换成指针不照样也可以嘛,如:

1
2
3
4
5
struct People
{
char *username;
char *hobbies;
};

确实,但是相比柔性数组,指针的方式最大的弊端就是内存释放。也就是说,在释放结构体指针之前要先释放 hobbies 指针,否则就会引起内存泄露。除此之外,由于指针的方式声明的内存与结构体内存并不连续,一方面会造成内存碎片,一方面在访问结构体成员时也不十分便捷,所以才会有柔性数组的出现。