• 【C语言】柔性数组


    前言

    你是否听说过柔性数组呢?如果没有的话,就一起了解一下吧。

    (没有malloc free calloc realloc 四个函数的前置知识的朋友最好先阅读一下我的“动态内存管理”一文,因为下面会涉及到。)

    介绍

    C99中,结构中的最后一个元素允许是位置大小的数组,这就叫“柔性数组”成员。

    比如:

    1. struct st_type
    2. {
    3. int i;
    4. int a[0];//柔性数组成员
    5. };

    或者:

    1. struct st_type
    2. {
    3. int i;
    4. int a[];//柔性数组成员
    5. };

    有的编译器支持前一种写法,有的则支持后一种,vs则是两种写法都可以。 

    由此,我们可以看出柔性数组的定义。

    定义

    1.在结构体中

    2.最后一个成员

    3.未知大小的数组

     注意,柔性数组不是一个独立的数组,而是结构的一个成员。

    柔性数组的特点(要求):

    1.结构中的柔性数组成员前面必须至少一个成员。

            因为柔性数组的大小是未知的,如果前面没有成员,结构大小没法算。

    2.sizeof返回这种结构大小时不包括柔性数组的内存。(请看下面例子

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

    例子:

     

    可以看到我们的结构体大小果然只有4bytes(字节),柔性数组的大小确实没有被算在内。

    现在,我们再来解释比较重要的第三条特点:

    包含柔性数组的结构,用malloc()函数进行内存的动态分配

    如果我们现在想创建一个S类型的结构体变量,我们一般不会这么写:

    1. struct S
    2. {
    3. int n;
    4. int arr[0];
    5. };
    6. int main()
    7. {
    8. struct S s;
    9. return 0;
    10. }

    因为当我们这么写,s的大小就为4bytes,我们的arr就没有内存了:

     Plan A

    为了能够给arr开辟空间,我们需要这么写:

    1. //给arr开辟20个int型大小
    2. struct S* ps=(struct S*)malloc(sizeof(struct S)+20*sizeof(int));

    (头文件stdlib.h别忘了包含)

    此时我们的ps能访问s里的n和arr

    1. ps->n=100;
    2. int i=0;
    3. for(i=0;i<20;i++)
    4. {
    5. ps->arr[i]=i+1;
    6. }

    别忘了检查开辟是否成功:

    1. if(ps==NULL)
    2. return 1;

    现在我们也能用realloc来调整这块空间,柔性数组可以变大变小。(变长数组可做不到)

    假设我们现在将柔性数组大小调整为40个整型大小:

    我们只想调整arr的大小,但是我们的动态开辟是从n(也就是S型变量的起始地址开始的),所以我们的调整也得从这里开始

    1. struct S* ptr=(struct S*)realloc(ps,sizeof(struct S)+40*sizeof(int));
    2. //不能用ps直接接收因为可能开辟失败
    3. if(ptr!=NULL)
    4. {
    5. ps=ptr;
    6. ptr=NULL;//不用ptr,及时置为空指针,以防变为野指针
    7. }
    8. else
    9. {
    10. return 1;
    11. }

    最后别忘了将ps释放和置为空指针。

    Plan B

    其实上面这个对包含柔性数组的结构体分配内存的方案还有另一种同样效果的方案B,在这里可以做一种对比:

    你可能会想到,既然是柔性数组在变大变小,那何不改为:

    1. struct S
    2. {
    3. int n;
    4. int *arr;//改写成指针形式
    5. };

    然后传arr进行realloc呢?

    为了和Plan A达到相同效果,将n也动态内存开辟(让n也开辟在堆区上)

    那么我们就这么写:

    1. struct S
    2. {
    3. int n;
    4. int *arr;//这里如果写成arr[],下面用ps->arr=tmp会提示得是可修改的左值a
    5. };
    6. int main()
    7. {
    8. //让n的空间也在堆上,所以也用malloc
    9. struct S* ps = (struct S*)malloc(sizeof(struct S));
    10. if (ps == NULL)
    11. {
    12. perror("malloc");
    13. return 1;
    14. }
    15. return 0;
    16. }

    然后我们动态开辟柔性数组部分:

    1. int* tmp = (int*)malloc(20 * sizeof(int));
    2. if (tmp!= NULL)
    3. {
    4. ps->arr = tmp;
    5. }
    6. else
    7. {
    8. return 1;
    9. }

     然后我们可以对柔性数组扩容

    1. //调整空间
    2. tmp=(int*)realloc(ps->arr, 40 * sizeof(int));
    3. if (tmp != NULL)
    4. {
    5. ps->arr = tmp;
    6. }
    7. else//处理调整失败的情况
    8. {
    9. perror("realloc");
    10. return 1;
    11. }

    然后我们就可以使用这块空间。

    使用完同样记得释放,这个方案有两个开辟的起始地址,需要释放两次。

    1. free(ps->arr);
    2. //ps->arr = NULL;可以写,也可以不写,因为ps置为NULL,ps->arr也找不到地址了
    3. free(ps);
    4. ps = NULL;

    那么,我们可以对比这两种方案。前一种是只malloc了一次,后一种malloc了好几次,那么这会存在什么问题呢?

    多次malloc:malloc申请的空间是连续的,但一次malloc和另一次malloc申请的空间之间可能会有缝隙,而这些缝隙最后能否被利用上是不一定的。所以还是减少malloc的次数比较好。

    这些就是内存碎片,就像教室里坐着的学生,中间会有缝隙。malloc次数越多内存碎片就越多。

    总结

    柔性数组就是在结构体的最后有一个数组成员,且这个数组的大小可以调整。这样的结构体在开辟内存时需要动态开辟,才能正常使用柔性数组。

    到此,本文就结束了,祝阅读愉快^_^

  • 相关阅读:
    DIRECT_IO 配置参数(UNIX)
    C# RestoreFormer 图像修复
    【CMake】windows10下入门课程
    关于Python Ansible中 HOST_KEY_CHECKING不生效问题分析
    基于TF-IDF代码实战:文本实体关系抽取 有代码数据可直接运行
    通过@QConfigPropertySource为static属性赋值
    2024上半年热门巅峰AI工具大盘点,你绝对不能错过!
    git 对比两个分支差异
    Shell 脚本编程——变量和运算符
    很详细的系列Shell基础— Shell简介
  • 原文地址:https://blog.csdn.net/2301_82135086/article/details/139361146