• 动态内存管理(malloc calloc realloc free)--- C语言


    写在前面

    本文介绍了C语言中四个常用的内存分配和释放函数,它们是:malloc、calloc、realloc 、free。文章中讨论了这些函数的使用方法,示例代码,常见的动态内存错误以及如何防止内存泄漏。

    1. malloc 和 free函数

    malloc和free是C语言中用于动态内存分配和释放的函数,它俩基本上都是配套使用的。malloc和free的声明在 stdlib.h 头文件中。

    1.1 malloc函数介绍

    函数原型:

    void* malloc (size_t size);
    
    • 1
    • 函数参数:size表示要申请的内存大小,单位是字节。如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
    • 函数返回值:返回一个void*类型的指针,这是因为malloc函数并不知道开辟空间的类型,所以在使用的时候由使用者自己来决定。如果开辟成功,则返回动态开辟空间的起始地址;如果开辟失败,返回空指针(NULL),因此对malloc的返回值一定要做检查

    以下代码,演示了如何使用malloc函数来动态申请内存并在其中存储整数数据:

    #include 
    #include 
    
    int main() 
    {
        int* arr;
        int size = 5;
    
        // 使用malloc分配整数数组的内存
        arr = (int*)malloc(size * sizeof(int));
    	//对malloc的返回值进行检查
        if (arr == NULL) 
        {
            perror("malloc");
            return 1;
        }
    
        // 在动态数组中存储数据
        for (int i = 0; i < size; i++) 
        {
            arr[i] = i;
        }
    
        // 使用动态数组中的数据
        for (int i = 0; i < size; i++)
         {
            printf("%d ", arr[i]);
        }
    
        // 释放动态分配的内存
        free(arr);
    	arr = NULL;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    需要注意的是
    使用malloc函数动态申请的空间在不需要使用的时候要用free函数释放,以避免内存泄漏。释放内存后,应将指针设置为NULL,以避免出现野指针的问题。

    1.2 free函数介绍

    函数原型:

    void free (void* ptr);
    
    • 1
    • 函数参数:ptr是要释放的内存空间起始地址。

    free函数主要是用来回收内存的,用于释放先前通过动态内存函数(例如malloc、calloc、realloc等)申请的空间。

    需要注意的是:

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

    以下代码,演示了如何使用free函数来释放动态申请的内存:

    #include 
    #include 
    
    int main()
    {
    	int* arr;
    	int size = 5;
    
    	// 使用malloc分配整数数组的内存
    	arr = (int*)malloc(size * sizeof(int));
    	//对malloc的返回值进行检查
    	if (arr == NULL)
    	{
    		perror("malloc");
    		return 1;
    	}
    
    	// 使用内存
    	// ....
    
    	// 释放动态分配的内存
    	free(arr);
    	arr = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2. calloc函数

    函数原型:

    void* calloc (size_t num, size_t size);
    
    • 1
    • num:表示要分配的元素的数量。
    • size:表示每个元素的大小(以字节为单位)。
    • 返回值:如果开辟成功,则返回动态开辟空间的起始地址;如果开辟失败,返回空指针(NULL),因此对calloc的返回值也要做检查。

    表面上看calloc和malloc函数的功能非常相似,但它与malloc也是有区别的。区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
    在这里插入图片描述
    以下是上面图片中的代码:

    #include 
    #include 
    
    int main()
    {
    	int* ptr;
    
    	// 使用calloc申请空间
    	ptr = (int*)calloc(5, sizeof(int));
    	//对calloc的返回值进行检查
    	if (ptr == NULL)
    	{
    		perror("malloc");
    		return 1;
    	}
    	// 使用内存
    	// ....
    	// 释放动态分配的内存
    	free(ptr);
    	ptr = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    需要注意的是:

    • 和malloc类似,使用calloc函数动态申请的空间在不需要使用的时候也需要用free函数释放,以避免内存泄漏。释放内存后,也应将指针设置为NULL,以避免出现野指针的问题。
    • 如果我们对申请的内存空间的内容要求初始化,可以使用calloc函数来完成任务。

    3. realloc函数

    函数原型:

    void* realloc (void* ptr, size_t size);
    
    • 1
    • ptr:是先前分配的内存区域的起始地址。
    • size:是重新分配后的内存块的新大小,单位是字节。
    • 返回值:如果调整成功,则返回一个调整好的空间的起始地址;如果调整失败,返回空指针(NULL),因此对realloc的返回值一定要做检查。

    realloc 函数是用来调整动态开辟的内存大小的。realloc在调整内存空间是存在两种情况:
    情况1:原有空间之后有足够大的空间
    在这里插入图片描述
    情况2:原有空间之后没有足够大的空间
    当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
    在这里插入图片描述
    以下代码,首先使用malloc申请一块初始大小的内存,然后使用realloc调整初始内存。

    #include 
    #include 
    
    int main() 
    {
        int* arr;
        size_t sz = 5;
        size_t newSz = 10;
        
        // 分配内存
        arr = (int*)malloc(sz * sizeof(int));
        
        if (arr == NULL) 
        {
            perror("malloc");
            return 1;
        }
    
        // 使用内存
    
        // 重新分配内存
        int* _arr = (int*)realloc(arr, newSz * sizeof(int));
        
        if (_arr == NULL) 
        {
            perror("malloc");
            free(arr);
            return 1;
        } 
        else
        {
            arr = _arr;
        }
    
        // 使用重新分配后的内存
    
        // 释放内存
        free(arr);
    	arr = NULL;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    4. 常见的动态内存错误

    4.1 对NULL指针的解引用操作

    对NULL指针进行解引用操作是一种非常严重的错误,因为NULL指针表示一个无效的内存地址,尝试访问它的内容将导致未定义的行为,通常会导致程序崩溃。
    如下面代码,如果malloc开辟空间失败就会返回空指针,造成对NULL指针进行解引用。

    #include 
    #include 
    
    void test()
    {
     int *p = (int *)malloc(INT_MAX/4);
     *p = 20;//如果p的值是NULL,就会有问题
     free(p);
    }
    
    
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    代码改正:
    检查指针是否为NULL:在进行指针解引用操作之前,应该先检查指针是否为NULL。

    #include 
    #include 
    
    void test()
    {
    	int *p = (int *)malloc(INT_MAX/4);
    	//解引用之间,检查指针是否为NULL
    	if (p == NULL)
    	{
     		perror("malloc");
     		return;
     	}
    	*p = 20;//如果p的值是NULL,就会有问题
    	free(p);
    	p = NULL;
    }
    
    
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    对动态开辟空间的越界访问是指在使用malloc、calloc、realloc等动态内存分配函数分配的内存空间中,访问超出申请空间的内存位置。这是一种严重的编程错误,通常会导致不可预测的行为和程序崩溃。

    如下面代码,当 i 是10的时候越界访问。

    #include 
    #include 
    
    void test()
    {
    	int i = 0;
    	int *p = (int *)malloc(10*sizeof(int));
    	if(NULL == p)
    	{
    		perror("malloc");
     		return;
    	}
    	for(i=0; i<=10; i++)
    	{
    		*(p+i) = i;//当i是10的时候越界访问
    	}
    	free(p);
    	p = NULL;
    }
    
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    因此,在使用指针进行操作时,一定要确保我们了解分配内存的大小,以避免越界访问

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

    使用 free 函数释放非动态分配的内存是一种非常严重的错误。free 函数应该仅用于释放通过 malloc、calloc、realloc 等动态内存分配函数申请的空间。如果尝试使用 free 来释放不是动态分配的内存,将导致不可预测的行为,通常会导致程序崩溃。

    如下代码,试图使用 free 释放栈上局部变量,这将导致程序崩溃。

    void test()
    {
    	int a = 10;
    	int *p = &a;
    	free(p);//ok?
    }
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    free 函数通常用于释放整个动态申请的空间,而不是它的一部分。一旦我们使用 malloc、calloc 或 realloc 分配了内存,就只能使用 free 来释放整个块。如果使用free释放一块动态开辟内存的一部分,通常会导致程序崩溃。

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

    多次释放同一块动态内存是一种非常严重的错误,通常会导致不可预测的行为和程序崩溃。这是因为一旦申请的空间被释放,它就不再属于当前的程序,再次释放它将导致内存错误。
    如何避免同一空间被重复释放?
    我们可以在每次释放后将指针设为NULL:一旦我们使用 free 函数释放了动态分配的内存,将指针设为 NULL,后续在通过该指针进行内存释放的时候就不会出问题了,这是因为当free函数的参数 是NULL指针时,函数什么事都不做。

    	int* ptr = (int*)malloc(sizeof(int));
    
    	// ...
    
    	free(ptr);
    	ptr = NULL; // 将指针设为 NULL,避免再次释放
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    内存泄漏是指在程序运行过程中动态申请的的空间没有被释放,导致程序持续占用系统内存而不释放,最终可能导致系统内存资源被耗尽。由于忘记释放不再使用的动态开辟的空间会造成内存泄漏,因此,动态开辟的空间一定要释放,并且正确释放

    有句说的好,一个人想写出来有bug的代码,是拦不住的。因此,我们在编程的时候,如果能避免这些动态内存错误,那么我们写出的代码将是高质量、稳定和可维护的代码!!!

    至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
    创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
    如果本篇博客有任何错误,请批评指教,不胜感激 !!!
    在这里插入图片描述

  • 相关阅读:
    EtherCAT转Modbus网关做为 MODBUS 从站配置案例
    【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例
    Obsidian 国内插件安装指北
    用 HTTP 提交数据,基本就这 5 种方式
    java计算机毕业设计springboot+vue服装商城-服装销售网站
    电子学:第010课——实验 8:继电振荡器
    Java练习题十四期:不要二
    【Redis】Hash 哈希内部编码方式
    非常经典的Oracle基础知识
    一键启停脚本
  • 原文地址:https://blog.csdn.net/m0_50655389/article/details/133810701