(C语言)动态内存分配


一、为什么存在动态内存分配

在我们没有学习动态内存之前我们所掌握的内存开辟的方式有:

int i=20;//在栈空间上开辟四个字节
char arr[20]={
    
    0};//在栈空间上开辟10个字节的连续空间

以上开辟内存空间的特点是:
1、空间开辟的大小是固定的
2、数组在申明的时候,必须指定数组的长度,它所需要的内存在编译的时候分配
通过观察这两种开辟内存的方式就会发现,这两种方式都存在局限性,不够灵活,所以我们就要使用动态内存分配空间
注意:这里的动态内存分配的空间是在堆区上申请的,而不是在栈区上申请的
在这里插入图片描述

二、动态内存函数

1.malloc和free

malloc是C语言提供的一个动态内存开辟函数:https://legacy.cplusplus.com/reference/cstdlib/malloc/?kw=malloc

  void* malloc (size_t size);

这个函数的功能是为大小为size字节的对象分配一块连续可用的空间,并返回指向这块空间的指针,此空间中的初始值不确定
如果开辟成功,则返回一个指向开辟好空间的指针
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查
返回值的类型为void*的,所以malloc函数并不知道开辟空间的类型,具体在使用者使用的时候决定
如果参数size为0,malloc的行为是标准未定义的,取决于编译器

此外C语言还提供了另外一个函数free,用来做动态内存的释放和回收的
https://legacy.cplusplus.com/reference/cstdlib/free/?kw=free

void free (void* ptr);

free函数是用来释放动态开辟的内存
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
如果参数ptr是NULL指针,则函数什么事都不做
注意:动态开辟的空间一定要记得释放,并且正确释放,如果不按照要求释放或者不释放都会造成内存泄漏的问题
malloc函数和free函数的使用(如下所示)

#include <stdio.h>
#include <stdlib.h>//malloc和free所需要的头文件
int main()
{
    
    
    //申请一块空间来存放十个整型
	int* p =(int*) malloc(10 * sizeof(int));
	if (p == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

2.calloc

calloc也是C语言提供的一个可以开辟动态内存的函数:
https://legacy.cplusplus.com/reference/cstdlib/calloc/?kw=calloc

void* calloc (size_t num, size_t size);

这个函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
calloc和malloc的区别是calloc会在返回地址之前把申请的空间的每个字节初始化为全0
在这里插入图片描述
calloc的使用(如下所示)

扫描二维码关注公众号,回复: 17183747 查看本文章
#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int* p =(int*) calloc(10 , sizeof(int));
	if (p == NULL)
	{
    
    
		perror("calloc");
		return;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

3.realloc

realloc函数是为了让动态内存管理更加灵活
有时我们会发现过去申请的空间太小了,有时候又会觉得申请的空间过大了,为了合理的申请内存,我们会对内存的大小做灵活的调整
realloc函数就可以做到对动态开辟内存大小的调整
https://legacy.cplusplus.com/reference/cstdlib/realloc/?kw=realloc

void* realloc (void* ptr, size_t size);

ptr是要调整内存的大小
size是调整之后内存的新大小
返回值为调整之后的内存起始位置
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
realloc在调整内存空间是存在两种情况:
情况1:原有空间之后有足够大的空间,直接在原有空间的内存之后追加空间,原来空间的数据不变
在这里插入图片描述
情况2:原有空间之后没有足够大的空间,在堆空间上另找一个合适大小
的连续空间来使用。
1.将旧空间中的数据,拷贝到新的空间
2.释放掉旧的空间
3.realloc返回新的空间的地址
在这里插入图片描述
realloc都使用(如下所示)

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
    
    
		perror("calloc");
		return;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", p[i]);
	}
	//空间不够,调整空间为10个整型
	int* ptr = (int*)realloc(p,10*sizeof(int));
	if (ptr == NULL)
	{
    
    
		p = ptr;
	}
	//使用


	//释放
	free(p);
	p = NULL;
	return 0;
}

三、柔性数组

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

1.柔性数组的特点

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

#include <stdio.h>
struct S
{
    
    
	char i;
	int a;
	int arr[];
};

int main()
{
    
    
	printf("%d\n", sizeof(struct S));
	return 0;
}

在这里插入图片描述

2.柔性数组的使用

代码1

#include <stdio.h>
struct S
{
    
    
	char c;//1
	//3
	int i;//4
	int arr[];//未知大小的数组 - 柔性数组成员
};

int main()
{
    
    
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);
	if (ps == NULL)
	{
    
    
		perror("malloc");
		return 1;
	}
	ps->c = 'w';
	ps->i = 100;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		ps->arr[i] = i;
	}
	//打印
	for (i = 0; i < 5; i++)
	{
    
    
		printf("%d ", ps->arr[i]);
	}
	//空间不够了
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S)+40);
	if (ptr != NULL)
	{
    
    
		ps = ptr;
	}
	else
	{
    
    
		perror("realloc");
		return 1;
	}
	//增容成功后,继续使用
	
	//释放
	free(ps);
	ps = NULL;

	return 0;
}

在这里插入图片描述
3.柔性数组的优势

#include <stdio.h>
struct S
{
    
    
	char c;
	int i;
	int* data;
};

int main()
{
    
    
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
    
    
		perror("malloc1");
		return 1;
	}
	ps->c = 'w';
	ps->i = 100;
	ps->data = (int*)malloc(20);
	if (ps->data == NULL)
	{
    
    
		perror("malloc2");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		ps->data[i] = i;
	}
	for (i = 0; i < 5; i++)
	{
    
    
		printf("%d ", ps->data[i]);
	}
	//空间不够了,增容
	int* ptr = (int*)realloc(ps->data, 40);
	if (ptr == NULL)
	{
    
    
		perror("realloc");
		return 1;
	}
	else
	{
    
    
		ps->data = ptr;
	}
	//增容成功就使用
	//...
	//释放
	free(ps->data);
	ps->data = NULL;
	free(ps);
	ps = NULL;

	return 0;
}

以上代码和代码1一样都可以实现相同功能,但是代码1的实现相比于以上代码要好一点:
1.方便内存的释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉
2.有利于访问速度的提升
连续的内存有益于提高访问速度,也有益于减少内存碎片

猜你喜欢

转载自blog.csdn.net/2301_78373304/article/details/133809852