• 动态内存管理


    1.为什么存在动态内存分配

    我们已经掌握的连续内存空间的开辟方式有:

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

    上述的开辟空间的方式有一个非常明显的缺陷,数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配,开辟的空间是固定的,有可能大了,有可能小了,很难满足我们的实际需求。这时候就只能试试动态内存分配了。

    2.动态内存函数的介绍

    我们知道内存可以划分位栈区,堆区,静态存储区,其中栈区主要存放局部变量和形式参数,而我们malloc,calloc,realloc动态申请的内存空间都是在堆区开辟的,同时free函数释放的内存空间也是堆区的内存空间,静态存储区主要存放静态变量和全局变量。

    下面我们就深入探讨一下用于在堆区动态申请空间的动态内存函数malloc,calloc,realloc。

    (1)malloc和free

    malloc函数是C语言提供的一个动态内存分配函数,其原型为void * malloc (size_t size);

    这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。如果开辟成功,则返回一个指向所开辟空间的指针。如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。比如:

    //使用malloc函数申请了一片内存空间,让指针ptr指向这片内存空间
    ptr = (int*)malloc(num*sizeof(int));
    
    //后面我们想使用ptr时,一定要检查它是否为NULL
    if(NULL != ptr)
    {
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    返回值的类型是 void* ,是因为malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

    如果参数 size 为0,malloc的行为是标准未定义的,这取决于编译器。

    C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型为void free (void * ptr);

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

    值得注意的是,使用free函数释放了动态申请的空间后,指针仍然保存着这片空间的地址,也就是野指针,也就是我们依然可以通过指针访问到这片已经被系统回收了的内存,但这就构成了非法访问,为了避免这种非法访问,我们释放这片内存空间后,要将指针赋值为NULL。

    free(ptr);
    ptr = NULL;
    
    • 1
    • 2

    注:
    栈区变量自动回收,无需我们手动释放,只有malloc,calloc,realloc动态申请的内存空间才需要free。

    (2)calloc

    calloc 函数也用来动态内存分配,其函数原型为void* calloc (size_t num, size_t size);

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

    在这里插入图片描述
    所以如果我们对申请的内存空间的内容要求初始化,那么可以使用calloc函数很方便地来完成任务。

    (3)realloc

    realloc函数的出现让动态内存管理更加灵活。

    有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定需要对内存的大小做灵活的调整,那 realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型为:
    void* realloc (void* ptr, size_t size);其中ptr 是要调整的内存地址,size是调整之后的新大小,返回值为调整之后的内存起始位置。

    realloc在调整内存空间时存在两种情况:

    情况1:原有空间之后有足够大的空间
    情况2:原有空间之后没有足够大的空间
    在这里插入图片描述

    当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。当是情况2 的时候,原有空间之后没有足够多的空间,扩展的方法是:在堆空间上另开辟一片合适大小的连续空间来使用,这时函数会将原来内存中的数据复制到新开辟的空间,这样函数返回的是一个新的内存地址。

    当然除了上述两种情况外,还有第三种情况,就是realloc函数开辟空间失败返回NULL,也正是因为这第三种情况,realloc函数的使用就要注意一些。 举个例子:

    #include <stdio.h>
    int main()
    {
    	int *ptr = malloc(100);
    	if(ptr != NULL)
    	{
    		//业务处理
    	}
    	else
    	{
    	  exit(EXIT_FAILURE);  
    	}
    	//扩展容量
    	//代码1
    	ptr = realloc(ptr, 1000);//这样可以吗?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    显然按照上述代码1的写法是不可取的,因为ptr本来指向一片由malloc函数开辟的空间,如果realloc函数调整空间失败,返回NULL被ptr接收了,那么ptr的值就为NULL,这就导致我不仅调整空间失败,连我原来所指向的那片空间也丢了,为了避免这种赔了夫人又折兵的情况发生,我们应该使代码更加严谨。

    //代码2
    	int*p = NULL;
    	//先让一个指针p来接收返回值
    	p = realloc(ptr, 1000);
    	if(p != NULL)
    	{
    	//确认返回值不为NULL后,我们再将其赋值给ptr
    		ptr = p;
    	}
    	//业务处理
    	free(ptr);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    需要注意的是,realloc传的是NULL,那功能就和malloc函数一样,直接开辟指定大小的空间,所以说realloc可以调整空间,也可以像malloc一样开辟空间。

  • 相关阅读:
    【数据库Redis】Redis五种基本数据结构以及三种配置方式——默认配置、运行配置、配置文件启动
    [附源码]计算机毕业设计JAVA面向服装集群企业的个性化定制服务系统
    软件盘点企业使用服装ERP的好处
    javaweb汽车租赁系统
    HTTP/1.1协议的状态码
    Java线程的并发工具类
    Matplotlib:科研绘图利器(写论文、数据可视化必备)
    高性能本地缓存Ristretto(一)——存储策略
    SR660 V2 ESXI 的安装
    解决Oracle死锁问题
  • 原文地址:https://blog.csdn.net/weixin_44049823/article/details/125509770