动态内存管理2之柔性数组

动态内存管理之柔性数组

  • 1、柔性数组
    • 1.1 柔性数组的特点
    • 1.2 柔性数组的使用
    • 1.3 柔性数组的优势

1、柔性数组

c99中,结构体中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

struct S1
{
	int num;
	double d;
	int arr[]; //柔性数组成员
};

struct S2
{
	int num;
	double d;
	int arr[0]; //柔性数组成员
};
  1. int arr[]; 与int arr[0];这两种情况都属于未知大小的数组。
  2. 未知大小的数组是结构体中最后一个成员,那么这个数组称为柔性数组。

1.1 柔性数组的特点

  • 结构体中柔性数组成员前面必须至少有一个其他成员。
  • sizeof返回的这种结构体大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
#define _CRT_SECURE_NO_WARNINGS 1
#include 

struct S3
{
	int num; //4个字节
	int arr[]; //柔性数组成员
};
int main()
{
	printf("%d\n", sizeof(struct S3));  //四个字节,柔性数组不计算在内
	return 0;
}

打印结果
动态内存管理2之柔性数组_第1张图片

  1. 结构体中柔性数组成员前面必须至少有一个其他成员。
  2. 用sizeof计算这种结构体大小,不包括柔性数组的内存。算出的结果只有另一个整型成员的大小,四个字节。

1.2 柔性数组的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 

struct S3
{
int num; //4个字节
int arr[]; //柔性数组成员
};
int main()
{
	struct S3* ps = (struct S3*)malloc(sizeof(struct S3) + 40);  //一共开辟了44个字节,后面40个字节是预留给柔性数组的
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}

	ps->num = 100; //访问num

	//访问数组
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");

	//扩容
	struct S3* ptr = (struct S3*)realloc(ps, sizeof(struct S3) + 80);
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		ps = ptr;
	}
	for (i = 10; i < 20; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 10; i < 20; i++)
	{
		printf("%d ", ps->arr[i] = i);
	}
	//使用完释放
	free(ps);
	ps = NULL;
	return 0;
}

打印结果
动态内存管理2之柔性数组_第2张图片

  1. 这个结构体的大小我们已经知道是4个字节,当包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。所以开辟了40个自己给柔性数组,40>4个字节。
  2. 为柔性数组动态开辟内存的空间如代码所示是如何使用的。
  3. 当我们为柔性数组用动态开辟内存的空间不够用时,可以用realloc函数继续为柔性数组动态开辟内存。
  4. 为柔性数组动态开辟的内存使用完毕后,用free释放,并及时置空。

1.3 柔性数组的优势

动态内存管理2之柔性数组_第3张图片
动态内存管理2之柔性数组_第4张图片
图示第一种方式是用柔性数组的方式,第二种方式没有用柔性数组,但是最终达到的效果都是一样的,为什么还有柔性数组这种东西。


展示结构体中为非柔性数组动态开辟一块空间

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 

struct S4
{
	int num;
	int* arr;
};

int main()
{
	//先给结构体动态内存开辟一个空间
	struct S4* ps = (struct S4*)malloc(sizeof(struct S4));
	if (ps == NULL)
	{
		return 1;
	}
	//再给结构体中的成员动态内存开辟一个空间
	ps->arr = (int*)malloc(40);
	if (ps->arr = NULL)
	{
		free(ps);
		ps = NULL;
		return 1;
	}

	//使用

	//释放
	free(ps->arr);  //先释放结构体中为成员动态内存开辟的空间
	ps->arr = NULL;
	free(ps);       //再释放为结构体动态内存开辟的空间
	ps = NULL;
	return 0;
}
  1. 先用动态开辟内存的方式为结构体开辟一块空间。
  2. 这个结构体中的成员有一个整型指针,现在用动态开辟内存一块整型空间,并用结构体中的整型指针成员指向这块成员。
  3. 使用完毕后,先将结构体整型指针成员指向的空间释放置零,再将结构体指针指向的空间释放置零。
  4. 在这段代码中,让结构体中的成员指向的空间内存开辟动态就好了,为什么结构体本身也用了动态开辟内存的方式?
    答:动态内存开辟的空间是在堆区上开辟的,正常开辟的局部变量(指创建的结构体类型)是在栈区上开辟的,为了结构体本身与结构体中动态开辟的成员保持在同一块内存区域中,结构体类型本身也用了动态开辟内存的方式。

上述代码没有用到柔性数组,柔性数组与之相比,柔性数组的优点在哪?

  1. 第一个好处:方便释放
    如果我们的代码是在一个给别人用的函数中,上述代码做了二次内存分配(即在结构体内部再次用来动态内存分配函数),并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员的内存一次性分配好了,并返回给用户一个结构体指针,用户调用一次free就可以把所有的内存都给释放掉。
  2. 第二个好处:这样有益于访问速度
  • 连续的内存有益于提高访问速度,也有益于减少内存碎片。
  • 柔性数组的方式用动态内存一次性开辟一块空间,而非柔性数组的方式用了两次动态开辟内存,多次动态开辟的内存空间之间不一定是连续的,也是因为多次动态开辟的空间之间的不连续会造成内存碎片。
  • 同时,cpu在访问数据时,先访问寄存器,寄存器拿不到想要的数据的话,访问缓存区域,缓存区域没有再访问内存。如果内存是连续的话,如图示第一种方式,当访问这段内存中的靠前数据时(num),会将这段内存中后面的数据(arr)提前加载到寄存器中去,这就是连续的内存会提高访问速度的原因。
  • 以图示第二种方式造成两次动态开辟的内存空间之间不连续,当访问 num 时,并不会将 arr*提前加载到寄存器中去,访问速度没有第一种以柔性数组的方式快。

你可能感兴趣的:(c语言,c语言,算法,c++)