【数据结构】动态内存管理函数

动态内存管理

为什么存在动态内存管理

在此之前,我们开辟内存空间有两种方式。一种是创建一个已知类型的变量。

比如说:

c 复制代码
int a=10;  //在栈空间上开辟4个字节

向系统申请了4个字节的内存空间。(对于 int型,4个字节它是固定的。)

还有一种是,创建一个数组。

比如说:

c 复制代码
int arr[10]; //在栈空间上开辟40个字节的连续空间。

向系统申请了40个字节的内存空间。当这个数组开辟好了空间,没有办法改变它的大小。

对于数组的创建,它的内存开辟方式是比较死板的。

c 复制代码
int arr1[10];

int arr2[100];

我们创建数组时,在一开始时就会指定数组的大小。arr1的内存空间为40个字节,可以存放10个整型元素。arr2的内存空间为400个字节,可以存放100个整型元素。

但有可能我们在使用数组arr1的时候,需要存放11个数组元素,而没有办法把它边长

我们可能为了尽可能满足很多情况,而创建一个很大的数组arr2,但在实际使用过程中,我们可能只会存放20个元素,而导致了内存空间的浪费

所以这样的内存开辟方式它是固定的,是不够灵活的。 不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,那么数组在编译时开辟空间的方式就不能满足了。 所以,我们需要学会开辟动态内存

动态内存函数的介绍

malloc
free
calloc
realloc

🎊malloc

malloc函数的原型:

c 复制代码
void* malloc(size_t size);

malloc声明在stdlib.h头文件中。
功能:

这个函数向内存申请一块连续可用 的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回指向这块空间的指针。
  • 如果开辟失败,则返回一个NULL指针。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

因此,malloc函数的返回值一定要做检查。

举个例子:

原来,我们用数组栈区开辟内存空间:

现在,我们在堆区动态开辟同样大小的内存空间:

根据malloc函数的原型,我们需要传递一个参数,以字节为单位的内存。

c 复制代码
malloc(40)//即开辟了40个字节的内存空间

然后,我们需要一个指针p来指向这块儿开辟好的连续的 内存空间

但由于 malloc函数的返回值为 void*,即无类型指针,所以我们需要先进行强制转换,将无类型指针转换为整型指针

因此,

c 复制代码
int* p=(int*)malloc(40);

此时我们,开辟的空间在内存中的堆区的空间,但是指向这块空间的指针是放在栈中的,也就是上面例子中的p指针。

如下图所示。

但是,正如我们上面所提到的,我们只是用malloc函数向内存申请开辟40个自己的连续空间,不一定开辟成功。所以我们需要利用指针p进行进一步检验

若开辟成功,进行访问:

malloc函数 申请的内存空间,但程序退出时,不会主动释放的,需要使用free函数来释放。

补充:perror函数

perror函数(忘的打印输出函数)

来自这篇博客:C语言perror函数详解

🎊free

C语言提供free函数,专门用来做动态内存释放和回收的。

free函数原型:

c 复制代码
void free(void *ptr);

free函数也是声明在头文件<stdlib.h>中的。

free函数用来释放动态开辟的内存。

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。

🎊calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。

calloc函数的原型:

c 复制代码
void* calloc(size_t num,size_t size);

calloc函数的功能为,为num个大小为size的元素开辟一块空间,并且把每个字节初始化为0。
与函数malloc函数的区别只在于calloc会在返回地址之前把申请的每个字节初始化为0。

即:

c 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));//开辟10个大小为sizeof(int),即4个字节 的空间
	//判断是否开辟成功
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d\n", *(p + i));
	return 0;
}

🎊realloc

  • realloc函数的出现让动态内存管理更加灵活
    有时候我们发现之前申请的内存过小了,有时候我们发现我们申请的内存过大了,所以,在一些时候,我们需要对内存的大小做灵活的调整。那么realloc函数就可以做到对动态开辟内存大小的调整
    realloc函数的原型如下:
c 复制代码
void* realloc(void* ptr,size_t size);

其中,ptr是要调整的内存的地址

size是调整之后的新的内存的大小。

返回值是调整之后内存的起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。

举个例子:

我们首先使用malloc函数开辟40个字节的空间:

c 复制代码
//malloc函数申请空间
int* p = (int*)malloc(40);
if (p == NULL)
{
	perror("malloc");
	return 1;
}
//初始化
int i = 0;
for (i = 0; i < 10; i++)
	*(p + i) = i;

我们想要将这个空间扩大,扩大为80个字节的空间。

于是我们使用realloc函数进行调整。但是内存空间的变化可能有不同的情况。

情况一,后面有足够的空间。

即这样的情形:

要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

情况二,后面没有足够的空间:

那么就在堆空间上另找一个合适大小的连续空间 来使用。这样函数返回的是一个新的内存地址。

c 复制代码
//增加一些空间
int *ptr=realloc(p, 80);
if (ptr != NULL)
{
	p = ptr;
}

在用realloc函数调整动态内存空间时,要注意不能将原来的p指针,来接收realloc(p,80)。
这是因为relloc函数不一定开辟成功新的空间而进行调整,即realloc函数的返回值可能为NULL。

那么p=NULL,本来p维护40个字节的空间。那么这样那个40个字节空间的字节就没有指针维护了。但还没有释放,可能用不到了,但可能找不到了,从而造成内存泄露

常见动态内存错误

对空指针的解引用操作

当我们用malloc函数在堆上开辟了内存空间,此时会返回一个指针,如果,我们不判断返回值的话,可能就会发生对空指针解引用的错误。

比如:

c 复制代码
void test()
{
 int *p = (int *)malloc(INT_MAX*10);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}
  • INT_MAX 是在计算机编程中表示有符号整型(signed integer)所能存储的最大值。

如上如,p是空指针。那么就发生了对空指针的解引用操作的错误。

对动态开辟空间的越界访问

这个道理和在栈上申请空间是一样的道理。

在堆上申请空间,超过范围越界访问就会报错。

c 复制代码
void test()
{
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(-1);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
}

对非动态开辟内存使用free释放

对于在栈上开辟的空间,却用free来释放...头脑不清醒可能会用free来释放吧...

使用free释放一块动态开辟内存的一部分

例如,下面的一段代码,p没有释放掉动态内存起始位置的那块空间。

这种错误就是在写代码的过程中,起始指针跑偏了,但自己可能没有意识到。

所以,一块连续的空间必须重头释放,一次性全部释放完。

对同一块动态内存多次释放

例如下面这段代码,它释放了两次,就会出现报错。

更好的习惯是,当我们释放完一段空间后,将指针p设置为空指针。

c 复制代码
void test()
{
	int *p=(int *)malloc(100);
	free(p);
	p=NULL;
	free(p);//此时就什么事就没有了
}

动态开辟内存忘记释放(内存泄漏)

c 复制代码
void test5()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}


int main()
{
	test5();
	while(1);
	return 0;
}

例如,这段代码,在主函数中,我调用test5()函数时,p指针在堆上申请了一块空间,但是函数调用完毕后,出了这个test5()函数,局部变量指针p已经销毁了。

但是,这在堆上开辟的100个空间还在占用。

并且出了这个函数,我们已经找不到这块空间的地址了,程序while(1)还在继续。

我们想用,但是不知道这块空间的起始地址,所以我们用不上。同样的,我们想要释放,我们还是释放不了。这就造成了内存泄漏

所以它只有当程序结束后,才会自动释放。

相关推荐
苏-言11 分钟前
SSM框架探秘:Spring 整合 Mybatis 框架
java·spring·mybatis
qq_4476630541 分钟前
java-----多线程
java·开发语言
a辰龙a43 分钟前
【Java报错解决】警告: 源发行版 11 需要目标发行版 11
java·开发语言
听海边涛声1 小时前
JDK长期支持版本(LTS)
java·开发语言
IpdataCloud1 小时前
Java 获取本机 IP 地址的方法
java·开发语言·tcp/ip
MyMyMing1 小时前
Java的输入和输出
java·开发语言
忆~遂愿1 小时前
3大关键点教你用Java和Spring Boot快速构建微服务架构:从零开发到高效服务注册与发现的逆袭之路
java·人工智能·spring boot·深度学习·机器学习·spring cloud·eureka
Easonmax1 小时前
【javaSE】内部类(来自类和对象的补充)
开发语言·javascript·ecmascript
云夏之末1 小时前
【Java报错已解决】java.lang.UnsatisfiedLinkError
java·开发语言
计算机-秋大田2 小时前
基于SpringBoot的假期周边游平台的设计与实现(源码+SQL脚本+LW+部署讲解等)
java·vue.js·spring boot·后端·课程设计