C语言——柔性数组

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、为什么柔性数组成员必须是结构体的最后一个成员

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

上一篇:【prometheus】k8s集群部署AlertManager实现邮件和钉钉告警-二、Alertmanager部署邮箱告警


下一篇:Android 版本号名称及SDK对应关系