C语言——柔性数组

1、柔性数组是什么

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

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

cpp 复制代码
typedef struct Example {
	int length;
	int data[0];
}flexible_array;

或者

cpp 复制代码
typedef struct Example {
	int length;
	int data[];
}flexible_array;

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

2、柔性数组成员的特点

  • 必须是结构体的最后一个成员。
  • 柔性数组成员之前必须有至少一个其他成员。
  • 在结构体定义时,柔性数组成员不占用内存空间(其大小被声明为零或为空维度的数组)。
cpp 复制代码
#include <stdio.h>

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

int main()
{
	printf("%zu\n", sizeof(flexible_array));
	return 0;
}

运行结果:

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

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

3、使用示例

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

cpp 复制代码
#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函数重新给柔性数组元素分配空间:

cpp 复制代码
#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

cpp 复制代码
#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

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

cpp 复制代码
#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)内存分配效率

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

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

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

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

对于第一种方式:

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

对于第二种方式:

ii)空间效率

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

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

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

相关推荐
唐 城17 分钟前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
嵌入式科普39 分钟前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
码银2 小时前
【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割
开发语言·python
从善若水2 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
lqqjuly2 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++
2401_858286113 小时前
115.【C语言】数据结构之排序(希尔排序)
c语言·开发语言·数据结构·算法·排序算法
Jelena技术达人3 小时前
Java爬虫获取1688关键字 item_search接口返回值详细解析
java·开发语言·爬虫
数据小爬虫@3 小时前
Java爬虫:速卖通(AliExpress)商品评论获取指南
java·开发语言
waterme1onY3 小时前
Spring AOP 中记录日志
java·开发语言·笔记·后端
2401_879103683 小时前
24.12.25 AOP
java·开发语言·笔记