写点什么

【C 语言内功修炼】柔性数组的奥秘

作者:Albert Edison
  • 2022 年 10 月 09 日
    四川
  • 本文字数:1893 字

    阅读完需:约 6 分钟

【C语言内功修炼】柔性数组的奥秘

1. 柔性数组的定义

也许你从来没有听说过 柔性数组flexible array)这个概念,但是它确实是存在的。


C99 中,结构中的最后一个元素允许是 未知大小 的数组,这就叫做『柔性数组』成员。


例如:


struct S1{  int n;  int arr[0]; // 柔性数组成员};
复制代码


上面这种写法,有些编译器会报错无法编译;


可以改成:


struct S2{  int n;  int arr[]; // 柔性数组成员};
复制代码

2. 柔性数组的特点

(1)结构中的柔性数组成员前面 必须至少一个其他成员


(2)sizeof 返回的这种结构大小 不包括柔性数组的内存


(3)包含柔性数组成员的结构用 malloc 函数进行内存的动态分配,并且分配的内存应该 大于 结构的大小,以适应柔性数组的预期大小。


📝 代码示例


#include <stdio.h>
struct S2{ int n; int arr[]; };
int main(){ printf("%d\n", sizeof(struct S1)); return 0;}
复制代码


🌟 运行结果



可以看到使用 sizeof 求结构体的大小,是不包括 柔性数组的内存

3. 柔性数组的使用

假设对包含柔性数组的结构体来开辟空间


📝 代码示例


#include <stdio.h>
struct S2{ int n; int arr[]; };
int main(){ struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);
return 0;}
复制代码


sizeof(struct S2) 开辟了 4 个字节,是给 n 的;后面的 40 是给 arr 开辟的;



现在我们来使用开辟的这 44 Byte 的空间


struct S2{  int n;  int arr[]; };
int main(){ struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);
// 使用n ps->n = 100; // 打印n printf("%d\n", ps->n);
// 使用arr for (int i = 0; i < 10; ++i) { ps->arr[i] = i; } // 打印arr for (int i = 0; i < 10; ++i) { printf("%d ", ps->arr[i]); }
// 释放 free(ps); ps = NULL; return 0;}
复制代码


🌟 运行结果



假设这块儿空间不够的话,还可以用 realloc 进行增容👇


#include <stdio.h>
int main(){ struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);
// 使用n ps->n = 100; // 打印n printf("%d\n", ps->n);
// 使用arr for (int i = 0; i < 10; ++i) { ps->arr[i] = i; } // 打印arr for (int i = 0; i < 10; ++i) { printf("%d ", ps->arr[i]); }
// 增容 struct S2* ptr = (struct S2*)realloc(ps, sizeof(struct S2) + 80); if (NULL == ptr) { return 0; } else { ps = ptr; }
// 释放 free(ps); ps = NULL; return 0;}
复制代码


现在就能理解:结构体的最后一位成员能进行变长或变短的可能性

4. 柔性数组的优势

这时候,有人可能会想到:我在结构体定义一个指针,对指针指向的空间进行动态开辟不就完事了吗?


📝 代码示例


#include <stdio.h>
int main(){ struct S* ps = (struct S*)malloc(sizeof(struct S));
ps->n = 100; ps->arr = (int*)malloc(40);
// 使用...
// 增容...
// 释放arr free(ps->arr); ps->arr = NULL;
// 释放ps free(ps); ps = NULL;
return 0;}
复制代码


我们来看一下这段代码的内存布局:


首先 malloc堆区 上申请了一块儿空间,里面有整型 n 和指针 arr,指针 arr 使用 malloc 开辟的空间也在 堆区 上;



那么再对比一下 柔性数组 的布局图,到底应该用哪一种呢?



其实 柔性数组 的方案聪设计上来说更 一些,虽然使用起来比较陌生;


为什么呢?


首先包含 柔性数组成员的结构体 如果用 malloc 来维护空间的话,只需要用一次 malloc 就可以把 narr 都开辟出来了,用 free 释放的话,也只需要一次就够了;


而对于 指针 的话,mallocfree 都使用了两次,开辟和释放的次数多,容易出错;


还有就是 malloc 开辟的次数过多,容易导致 内存碎片的问题


假设有下面这样一块内存空间,如果我开辟了一次,就使用了一整块;



如果我开辟两次的话,空间与空间就会形成缝隙;



如果我多次在内存中开辟的话,空间与空间就会形成更多的缝隙;



这种缝隙如果不利用的话,就被称为 内存碎片,你开辟的次数越多,那么内存碎片就越多,内存碎片的利用率不高的话,那么内存的利用率也会不高;


总结:


(1)优点一:方便内存释放


如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。


用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free,所以你不能指望用户来发现这个事。


如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 free 就可以把所有的内存也给释放掉。


(2)优点二:有利于访问速度


连续的内存有益于提高访问速度,也有益于减少内存碎片。

发布于: 刚刚阅读数: 5
用户头像

Albert Edison

关注

目前在某大厂担任后端开发,欢迎交流🤝 2022.03.08 加入

🏅️平台:InfoQ 签约作者、阿里云 专家博主、CSDN 优质创作者 🛫领域:专注于C语言、数据结构与算法、C++、Linux、MySQL、云原生的研究 ✨成就:2021年CSDN博客新星Top9,算法领域优质创作者,全网累计粉丝4W+

评论

发布
暂无评论
【C语言内功修炼】柔性数组的奥秘_数组_Albert Edison_InfoQ写作社区