C语言——柔性数组

ops/2024/9/23 9:23:22/

1、柔性数组是什么

在C语言中,柔性数组成员(Flexible Array Member,简称FAM)是C99标准中引入的一种结构体成员,用于表示一个大小可变的数组。它是结构体的最后一个成员,不像普通的数组,没有固定的长度。这使得结构体能够以一种非常灵活的方式来处理可变长度的数组数据。

含有柔性数组成员的结构体的声明方式:

typedef struct Example {int length;int data[0];
}flexible_array;

或者

typedef struct Example {int length;int data[];
}flexible_array;

第一种方式有的编译器可能报错。

2、柔性数组成员的特点

  • 必须是结构体的最后一个成员。
  • 柔性数组成员之前必须有至少一个其他成员。
  • 在结构体定义时,柔性数组成员不占用内存空间(其大小被声明为零或为空维度的数组)。
#include <stdio.h>typedef struct Example {int length;int data[];
}flexible_array;int main()
{printf("%zu\n", sizeof(flexible_array));return 0;
}

运行结果:

可以看到这里的结构体大小只有一个整形的大小,这时表明在结构体定义时,柔性数组是不占用内存空间的。

  • 实际的数组大小是在运行时决定的,包含柔性数组的结构体在使用时使用动态分配,在分配时应大于结构体的大小,以适应该柔性数组的预期大小。

3、使用示例

使用malloc函数给柔性数组元素分配空间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>typedef struct Example {int length;int data[]; // 柔性数组成员
} flex_array;int main()
{// 创建一个长度为10的柔性数组int n = 10;flex_array* array = (flex_array*)malloc(sizeof(flex_array) + sizeof(int) * n);if (array == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}array->length = n;// 使用柔性数组for (int i = 0; i < array->length; i++){array->data[i] = i;}// 打印数据for (int i = 0; i < array->length; i++){printf("%d ", array->data[i]);}printf("\n");// 释放内存free(array);//指着置空array = NULL;return 0;
}

运行结果:

在这个例子中,flex_array 结构体中有一个长度为length的柔性数组 data。分配给这个结构体的内存比其静态部分更大,足以容纳lengthint类型的元素。使用malloc时,我们需要计算出足够的空间来存储结构体的固定部分(这里是int length)加上柔性数组需要的空间(这里是sizeof(int) * n)。

使用realloc函数重新给柔性数组元素分配空间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>typedef struct Example {int length;int data[]; // 柔性数组成员
} flex_array;int main()
{// 创建一个长度为10的柔性数组int n = 10;flex_array* array = (flex_array*)malloc(sizeof(flex_array) + sizeof(int) * n);if (array == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}array->length = n;// 使用柔性数组for (int i = 0; i < array->length; i++){array->data[i] = i;}// 打印数据for (int i = 0; i < array->length; i++){printf("%d ", array->data[i]);}printf("\n");//扩容n = 20;flex_array* temp = (flex_array*)realloc(array, sizeof(int) + n * sizeof(int));//使用临时的指针变量,防止内存泄露if (temp == NULL){printf("%s\n", strerror(errno));return EXIT_FAILURE;}array = temp;//开辟成功则赋值给之前的指针temp = NULL;array->length = n;//更新数组长度元素for (int i = 10; i < array->length; i++){array->data[i] = i;}for (int i = 0; i < array->length; i++){printf("%d ", array->data[i]);}printf("\n");// 释放内存free(array);//指着置空array = NULL;return 0;
}

运行结果:

4、与含有指针的结构体的比较

1)含有指针的结构体

我们发现,如果目的只是想让结构体中多一个大小可以变化的数组,为什么不是在结构体中加一个指针,然后将动态内存分配的内存块的指针赋值给这个指针来使用呢,就像下面这样:

i)方式1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>typedef struct Example {int length;int* data;
}Example;int main()
{//在堆区创建和初始化结构体变量,因为整形指针指向的空间在堆区,为了保证这些变量都在堆区Example* array = (Example*)malloc(sizeof(Example));if (array == NULL)//未开辟成功的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//在对结构体变量开辟成功后,再对整型指针指向的空间进行开辟array->data = (int*)malloc(10 * sizeof(int));if (array->data == NULL)//未开辟成功的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//开辟成功array->length = 10;for (int i = 0; i < array->length; i++){array->data[i] = i;}for (int i = 0; i < array->length; i++){printf("%d ", array->data[i]);}printf("\n");//释放空间//先对整形指针指向的内存块释放,然后将整型指针置空free(array->data);array->data = NULL;//后对结构体变量进行释放,然后将指针置空free(array);array = NULL;return 0;
}

运行结果:

ii)方式2

这里也可以不将结构体变量在堆区中开辟:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>typedef struct Example {int length;int* data;
}Example;int main()
{Example array = {0,NULL};//不在堆中开辟,而是在作为局部变量在栈中开辟//对内存块进行开辟array.data = (int*)malloc(10 * sizeof(int));if (array.data == NULL)//未开辟成功的情况{printf("%s\n", strerror(errno));return EXIT_FAILURE;}//开辟成功array.length = 10;for (int i = 0; i < array.length; i++){array.data[i] = i;}for (int i = 0; i < array.length; i++){printf("%d ", array.data[i]);}printf("\n");//释放空间free(array.data);array.data = NULL;return 0;
}

运行结果:

这两种方式同样可以实现用realloc改变数组的大小。

2)两者有什么不同

i)内存分配效率

使用柔性数组,你只需要进行一次内存分配。结构体和数据都在一个连续的内存块中。如果使用指针,你通常需要两次分配:一次用于结构体本身,另一次用于数组。这不仅涉及两个独立的内存操作,还可能导致额外的内存碎片。

对于柔性数组:
typedef struct Example {int length;int data[]; // 柔性数组成员
} flex_array;

可以发现这里的空间是连续的,在释放时只需一次释放。

对于包含指针的结构体:
typedef struct Example {int length;int* data;
}Example;

对于第一种方式:

可以发现这里的空间是不连续的,在释放时需要两次释放才能完成(对于结构体变量也在堆区中开辟的情况)。

对于第二种方式:

ii)空间效率

柔性数组不需要存储数组数据的指针,因此它节省了存储指针本身所需的空间。这在结构体实例很多时尤其重要。

5、为什么柔性数组成员必须是结构体的最后一个成员

在结构体内部,所有成员都有固定的偏移量。如果柔性数组不是最后一个成员,那么在它之后的任何成员的位置将无法确定,因为柔性数组的大小在编译时是未知的。放在最后确保所有其他成员都有固定的偏移。


http://www.ppmy.cn/ops/23348.html

相关文章

纯IP地址可以申请SSL证书实现HTTPS访问吗?

IP地址申请SSL证书实现HTTPS访问&#xff0c;可以按照以下简单的步骤进行&#xff1a; 步骤一&#xff1a;准备所需材料 1. 确定IP地址&#xff1a;明确你要为哪个公网IP地址申请SSL证书。这通常是你服务器的公网IP&#xff0c;用户将通过这个IP地址访问你的服务。 2. 选择证书…

SpringCloud之负载均衡Ribbon

Ribbon 是一个客户端负载均衡工具&#xff0c;主要功能是将面向服务的Rest模板&#xff08;RestTemplate&#xff09;请求转换成客户端负载均衡的服务调用。通过Ribbon&#xff0c;开发人员可以在客户端实现请求的负载均衡&#xff0c;而无需单独部署负载均衡器。Ribbon支持多…

QT学习之QFileDialog

打开一个文件夹 m_dirXML QFileDialog::getExistingDirectory(this, tr("打开XML所在文件夹"), "D:/", QFileDialog::ShowDirsOnly|QFileDialog::DontResolveSymlinks); ui.xmlDri->setText(m_dirXML);选择一个文件&#xff1a; scriptPath QFileDia…

2024年北京高校数学建模校际联赛竞赛B题

B题 铁道线路动态检测数据分析 铁道线路设备是铁路运输业的基础设备&#xff0c;它常年裸露在大自然中&#xff0c;经受着风雨冻融和列车荷载的作用&#xff0c;轨道几何尺寸不断变化&#xff0c;路基及道床不断产生变形&#xff0c;钢轨、联结零件及轨枕不断磨损&#xff0c…

快速排序思想及实现

思想(从小到大排序) 总体 对数组进行分区&#xff0c;选定一个元素作为基准&#xff0c;然后将小于基准的元素放在基准的左边&#xff0c;将大于基准的元素放在基准右边&#xff0c;分区完成之后再对左边的元素和右边的元素分别进行分区&#xff0c;直到左右区间不可再分。这…

【设计模式】12、observer 观察者模式

文章目录 十二、observer 观察者模式12.1 subscriber12.1.1 broker_test.go12.2.2 broker.go12.2.3 client.go 十二、observer 观察者模式 https://refactoringguru.cn/design-patterns/observer 发布订阅模式, client 都可以向 broker 注册, broker 管理所有 connection, 当…

MySQL面试题入门:四大范式、SQL生命周期、SQL六大语言、索引、最左匹配原则....

1、数据库四大范式&#xff1f; 第一范式&#xff1a;属性不可分割&#xff0c;即每个属性都是不可分割的原子项。(实体的属性即表中的列) 第二范式&#xff1a;满足第一范式&#xff1b;且不存在部分依赖&#xff0c;即非主属性必须完全依赖于主属性。(主属性即主键&#xf…

如何删除.gitignore文件中指定的所有被忽略的文件

要删除.gitignore文件中指定的所有被忽略的文件&#xff0c;你可以使用git rm命令结合-r选项。以下是一些步骤&#xff1a; 查看将要删除的文件&#xff1a;首先&#xff0c;你可以使用git ls-files命令来列出被git忽略的文件&#xff0c;以确保你想要删除的文件列表是正确的。…