前言
本篇将对C99标准中引入的新特性——柔性数组,进行讲解。并探讨柔性数组的优势,简单的介绍内存池的相关概念,来体会柔性数组的优点。
一、柔性数组介绍
📚 定义:柔性数组(Flexible Array),又称可变长数组。一般数组的长度是在编译时确定,而柔性数组对象的长度在运行时确定。在定义结构体时允许你创建一个空数组(例如:arr [ 0 ] ),该数组的大小可在程序运行过程中按照你的需求变动。
🔍 出处:柔性数组(Flexible Array),是在C语言的 C99 标准中,引入的新特性。结构中的最后一个元素的大小允许是未知的数组,即为柔性数组。
【百度百科】在 ANSI 的标准确立后,C语言的规范在一段时间内没有大的变动,然而C++在自己的标准化创建过程中继续发展壮大。《标准修正案一》在1994年为C语言创建了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广的国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。被称为C99,C99被ANSI于2000年3月采用。
💬 演示:
struct S { int n; int arr[]; // 👈 柔性数组成员 }; ❗ 部分编译器可能会报错,可以试着将 a [ 0 ] 改为 a [ ] : struct S { int n; int arr[]; // 👈 柔性数组成员 };
二、柔性数组的特点
💬 结构中的柔性数组成员的前面必须至少有一个其他成员:
typedef struct st_type { int i; // 👈 必须至少有一个其他成员 int a[0]; // 👈 柔性数组成员 } type_a;
💬 sizeof 计算这种结构的大小是不包含柔性数组成员的:
#include <stdio.h> struct S { int n; // 4 int arr[]; // 大小是未知的 }; int main() { struct S s = {0}; printf("%d\n", sizeof(s)); return 0; }
🚩 4
💬 包含柔性数组成员的结构,用 malloc 函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小:
#include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0]; }; int main() { // 期望arr的大小是10个整型 // 给n的 给arr[]的 // 👇 👇 struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(int)); // 后面+的大小就是给柔性数组准备的 return 0; }
💡 分析:
三、柔性数组的使用
💬 代码演示:
#include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0]; }; int main() { // 期望arr的大小是10个整型 struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(int)); ps->n = 10; // 使用 int i = 0; for (i = 0; i < 10; i++) { ps->arr[i]; } // 增容 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20*sizeof(int)); if (ptr != NULL) { ps = ptr; } // 再次使用 (略) // 释放 free(ps); ps = NULL; return 0; }
🐞 查看地址:
四、柔性数组的优势
💬 代码1:使用柔性数组
/* 代码1 */ #include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0]; }; int main() { // 期望arr的大小是10个整型 struct S* ps = (struct S*)malloc(sizeof(struct S) + 10*sizeof(int)); ps->n = 10; // 使用 int i = 0; for (i = 0; i < 10; i++) { ps->arr[i]; } // 增容 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20*sizeof(int)); if (ptr != NULL) { ps = ptr; } // 再次使用 (略) // 释放 free(ps); ps = NULL; return 0; }
💬 代码2:直接使用指针
想让n拥有自己的空间,其实不使用柔性数组也可以实现。
/* 代码2 */ #include <stdio.h> #include <stdlib.h> struct S { int n; int* arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) return 1; ps->n = 10; ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) return 1; // 使用 int i = 0; for (i = 0; i < 10; i++) { ps->arr[i]; } // 增容 int* ptr = (struct S*)realloc(ps->arr, 20 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } // 再次使用 (略) // 释放 free(ps->arr); // 先free第二块空间 ps->arr = NULL; free(ps); ps = NULL; return 0; }
❓ 上面的 代码1 和 代码2 可以完成同样的同能,哪个更好呢?
💡 显而易见, 代码1 更好:
① 第一个好处:有利于内存释放
虽然 代码2 实现了相应的功能,但是和 代码1 比还是有很多不足之处的。代码2 使用指针完成,进行了两次 malloc ,而两次 malloc 对应了两次 free ,相比于 代码1 更容易出错如果我们的代码是在一个给别人用的函数中,你在里面做了两次内存分配,并把整个结构体返回给用户。虽然用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free,所以你不能指望用户来发现这件事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好(而不是多次分配),并且返回给用户一个结构体指针,用户只需使用一次 free 就可以把所有的内存都给释放掉,可以间接地减少内存泄露的可能性。
② 第二个好处:有利于访问速度
连续内存多多少少有益于提高访问速度,还能减少内存碎片。malloc 的次数越多,产生的内存碎片就越多,这些内存碎片不大不小,再次被利用的可能性很低。内存碎片越多,内存的利用率就会降低。频繁的开辟空间效率会变低,碎片也会增加。
🔺 总结:因此,使用柔性数组,多多少少是有好处的。


