「C语言进阶1」动态内存分配

目录

一、动态内存分配是什么?

二、为什么需要动态内存分配?

三、怎么进行动态内存分配?

1. malloc

2. calloc

3. realloc

a. realloc功能解析

b. 内存泄漏和内存块被截断问题

c. 总结

4. free 

四、使用动态内存分配常见的问题

【面试题】


一、动态内存分配是什么?

        动态内存分配是指在程序执行的过程中,系统根据程序的需要动态地分配或回收存储空间的分配内存的方法。

        这种分配方式不像数组等静态内存分配方法那样,需要预先指定要分配的内存大小,且一经分配后大小无法修改,而是由系统按程序的需要灵活地分配和回收内存空间。


二、为什么需要动态内存分配

        在C99引入变长数组前,定义数组时大小不能是变量,这就导致了开辟数组时必须指定数组的大小,且一经开辟,数组的大小再也无法改变。但实际情况下,如实现一个通讯录时,我们往往不知道需要多大的数组,且经常会遇到数组容量不够的情况,这就是静态内存分配的弊端。

        早期的解决方案就是使用动态内存分配,也就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行更灵活的分配和回收。

使用动态内存分配的优势:

  1. 可以灵活分配、回收内存:
            与大小固定的数组相比,动态内存分配可以使用malloc、calloc申请分配内存,使用relloc对申请好的内存空间大小进行调整,使用free回收内存。
  2. 可以随时回收,多次利用这部分空间:
            静态内存分配的空间,直到整个程序结束才会由系统自动释放。但是动态内存分配的空间需要用户手动释放,所以我们可以在使用完后立马通过free函数空间,使得这块空间能在一个程序中被反复使用,这样也能在一定程度上节省内存空间。
  3. 使用内存中堆区的空间
            动态内存分配是在堆上分配空间的,静态内存分配是在栈上分配空间的,所以了解动态内存分配后就能使用堆上的空间了。

三、怎么进行动态内存分配?

        C语言中动态内存管理方式是通过四个库函数实现的:malloc、calloc、 realloc、free。这四个库函数都在头文件<stdlib.h>

1. malloc

函数声明    |        void*  malloc (size_t  size);
功能      |        在堆上申请一个大小为size字节的空间。(申请分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
#include<stdio.h>
#include<stdlib.h>int main()
{// 开辟一个动态内存变量//malloc函数的返回值类型为void*所以要强制类型转换int* a = (int*)malloc(1 * sizeof(int));// 检查动态内存是否开辟失败,开辟失败返回NULLif (NULL == a){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存*a = 1;printf("%d\n", *a);// 释放动态内存free(a);a = NULL; //free后指针仍指向原来的空间,所以要置为NULL
}
	// 开辟一个动态内存数组int size = 10; // 动态内存数组大小int* arr = (int*)malloc(size * sizeof(int));// 检查动态内存是否开辟失败if (NULL == arr){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存数组(和静态内存数组的使用没有差别)for (int i = 0; i < size; ++i){arr[i] = i;printf("%d ", arr[i]);}// 释放动态内存数组free(arr);arr = NULL;

2. calloc

函数声明    |        void* calloc (size_t  num, size_t  size);
功能      |        在堆上申请大小为num个size字节的空间,并初始化空间中的内容为0。(分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
	// 使用calloc函数开辟一个动态内存变量/数组int size = 10; // 动态内存数组大小,size = 1即为变量int* arrc = (int*)calloc(size , sizeof(int));// calloc函数的返回值类型为void*if (NULL == arrc)// 检查{printf("动态内存开辟失败。\n");return 0;}for (int i = 0; i < size; ++i)// 使用{printf("%d ", arrc[i]); //calloc默认初始化内容全为0}free(arrc);// 释放arrc = NULL;

3. realloc

函数声明    |        void* realloc (void*  mem_address, unsigned int  newsize);
功能      |        见下方解析。(对申请好的动态内存空间大小进行调整,可以扩展或缩小动态内存。
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。

a. realloc功能解析

        realloc函数用于对申请好的动态内存空间大小进行调整。它接受两个参数:一个是已分配的动态内存的指针,另一个是要重新分配的大小(单位字节)。

        realloc函数首先检查传入的指针是否为空指针。如果是空指针,则等同于调用malloc函数,直接分配指定大小的动态内存,并返回指向该动态内存的指针。
        如果传入的指针不是空指针,则realloc函数会尝试重新分配内存块的大小。它会根据传入的新内存块大小newsize和原有内存块的大小oldsize来判断如何重新分配内存:

  • newsize = 0,释放原有的内存块,并返回空指针。
  • newsize > oldsize,则realloc函数会尝试在指针参数指向原有内存块的上进行扩容:
    如果原有内存块有足够的连续空间,直接扩容,并且将传入的指针返回;
    如果没有足够的连续空间,先按照指定的大小分配一个新的内存块,然后将原有数据拷贝到新内存块中,而后由系统自动释放原来的内存块,并返回新内存块起始位置的指针;
    如果扩容失败,则返回空指针,并且原有内存块的内容保持不变。
  • newsize < oldsize,则realloc函数会尝试缩小内存块的大小:
    它可能会直接使用缩小后的原内存块;
    也可能将原内存块中的数据复制到新内存块中,并返回指向新内存块的指针。内容不变,但是多余的数据可能会被截断。

b. 内存泄漏和内存块被截断问题

使用realloc函数时需要小心内存泄漏和内存块被截断的问题:

  • 如果realloc函数返回空指针,则说明内存分配失败,原内存块没有被释放,这可能会导致内存泄漏;(realloc失败不会释放原空间)
  • realloc函数可能会因为原来位置的内存大小不够,将原有内存块移到新的位置。但是,指向原有内存块的指针仍然指向原内存块,这可能会导致内存泄漏。所以realloc后要将原指针置为NULL。
  • 如果新的内存块大小小于原有内存块的大小,则可能会导致多余的数据被截断。

c. 总结

        realloc函数用于重新分配内存块的大小,可以扩展或缩小内存块。但是使用时需要注意内存泄漏和数据截断的问题。

	// 使用realloc函数调整动态内存的大小// 1.如果传入的是空指针,则相当于使用malloc开辟新空间:int* p1 = (int*)realloc(NULL, 1 * sizeof(int));if (NULL == p1){printf("动态内存开辟失败。\n");return 0;}*p1 = 1;printf("%d\n", *p1);free(p1);printf("p1所指向的空间:%d\n", sizeof p1);// 2.如果新空间需要的大小newsize = 0//则释放原有的内存块,并返回空指针。int* p2 = (int*)malloc(1 * sizeof(int));int* p3 = (int*)realloc(p2, 0);if (NULL == p3)printf("p2, p3所指向的空间将会由系统在合适的时间释放。\n");// 3.指针指向的旧空间大小oldsize < 新空间需要的大小newsize// 可能在原空间上扩容,也可能找一个新空间扩容int* p4 = (int*)calloc(10, sizeof(int));int* p5 = (int*)realloc(p4, 12 * sizeof(int));int* p6 = (int*)realloc(p5, 1000 * sizeof(int));if (p4 == p5)printf("在原空间上扩容。\n");if (p5 != p6)printf("找一个新空间扩容。\n");// 4.oldsize > newsize// 可能使用原空间,也可能找一个新空间int* p7 = (int*)calloc(10, sizeof(int));int* p8 = (int*)realloc(p7, 9 * sizeof(int));int* p9 = (int*)realloc(p8, 2 * sizeof(int));if (p7 == p8)printf("使用原空间。\n");if (p8 != p9)printf("找一个新空间。\n");

4. free 

函数声明    |        void free (void*  ptr);
        |        释放ptr指向的空间,如果ptr是空指针则不做处理;如果ptr指向的空间不是动态内存或是
功能      |        已经被释放的动态内存,则free函数的结果是未知的——可能导致程序崩溃,也可能导致
        |        内存泄漏。(回收动态内存)
返回值     |        无。

        释放动态内存,并不意味着执行到free函数时立马让该空间的内存全变为0,也不会让指针指向NULL,这只是告诉操作系统可以重新使用该内存块,具体的释放时间由操作系统决定。所以free后要手动将指针置为NULL。这也意味着无法通过指针是否指向NULL来判断空间是否已经被释放。

        注意,只有动态申请的堆区内存需要我们主动释放,其它区都由系统管理,强行free可能导致程序崩溃:


四、使用动态内存分配常见的问题

3.1 不检查动态内存是否开辟成功

如果开辟失败,返回值为NULL,不检查就可能会对NULL进行解引用操作。int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);

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

和静态数组一样,因忘记数组大小,导致越界访问。int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (int i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);

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

强行free非动态内存可能导致程序崩溃int t = 1;int* tp = &t;free(tp);return 0;

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

指向动态内存的指针不再指向起始位置。int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置

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

一样可能导致程序崩溃。int* p = (int*)calloc(10, sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}free(p);free(p);return 0;

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

        最常见的问题:忘记使用free函数释放动态内存空间,如果是一个长期运行的程序,这部分内存空间会因未释放而一直被占用,这就导致程序消耗的内存越来越多。切记: 动态开辟的空间一定要释放,并且正确释放 。

3.7 使用realloc和free后,未将原指针置为NULL

        realloc函数可能原位置空间不足,而使用新内存块移到新的位置。但是不会让指向原有内存块的指针不会指向NULL。free函数也不会让指针指向NULL。这可能会导致内存泄漏。所以realloc和free后要将原指针置为NULL。


【面试题】

题目1

void Test()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof(int));
    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    // 2.这里需要free(p2)吗?
    free(p3);
}


1. malloc/calloc/realloc的区别?

        从参数和函数功能方面进行回答。


2.这里需要free(p2)吗?

        答:不需要,使用realloc扩容时,自动释放了p2之前的空间。

题目2

void GetMemory(char* p)
{
    p = (char*)malloc(100);
}
void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}


1. 请问运行Test 函数会有什么样的结果?

        答:运行Test函数会导致程序崩溃,因为形参只是实参的拷贝,形参的改变并不影响实参,所以str仍指向NULL,这会导致空指针异常。

题目3 

 char* GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char* str = NULL;
    str = GetMemory();
    printf(str);
}


1. 请问运行Test 函数会有什么样的结果?

        答:不确定,因为在函数GetMemory执行完后,所处空间被系统销毁,p指向的空间也被销毁了,此时str因函数返回值指向了p指向的这片被消毁的空间。所以运行Test函数会产生不确定的结果。

题目4:

void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}


1. 请问运行Test 函数会有什么样的结果?

        答: 可能会导致程序崩溃,也可能会在显示屏上打印world。因为不是执行free函数就立即销毁指针指向的空间,指针也不会被free函数被置为NULL,所以指针仍指向原空间,如果系统还没销毁该空间,那么会在显示屏上打印world,否则会导致程序崩溃。


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/698621.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何将新标注的三元组数据转换成unicoqe可以处理的格式

目录 问题描述&#xff1a; 问题解决&#xff1a; 问题描述&#xff1a; 原始的标注的三元组格式如下&#xff1a; 需要转换的格式如下&#xff1a; tips:有一个小的难点&#xff1a; 1. 针对多三元组的情况&#xff0c;需要额外考虑 2. 最后一个样本&#xff0c;也记得需要…

QEMU之CPU虚拟化

概述 KVM是由以色列初创公司Qumranet在CPU推出硬件虚拟化之后开发的一个基于内核的虚拟机监控器。 KVM是一个虚拟化的统称方案&#xff0c;除了x86外&#xff0c;ARM等其他架构也有自己的方案&#xff0c;所以KVM的主体代码位于内核树virt/kvm目录下面&#xff0c;表示所有CP…

第九节HarmonyOS 常用基础组件25-QRCode

1、描述 用于显示单个二维码的组件。 2、接口 QRCode(value:string) 3、参数 参数名 参数类型 必填 描述 value string 是 二维码内容字符串。 4、属性 名称 参数类型 描述 color ResourceColor 设置二维码颜色。默认值&#xff1a;Color.Black backgroundCo…

阿里云优惠:优惠活动整理、云服务器价格、域名、数据库和优惠券领取

2024年阿里云优惠活动大全&#xff0c;包括阿里云服务器优惠活动清单、配置价格表、域名优惠活动、阿里云建站活动、阿里云优惠代金券免费领取、对象存储OSS活动、企业邮箱优惠、无影云电脑优惠、CDN特惠等等&#xff0c;阿里云百科aliyunbaike.com分享2024阿里云优惠活动大全_…

每日五道java面试题之spring篇(四)

目录&#xff1a; 第一题 Spring框架的设计目标&#xff0c;设计理念&#xff0c;和核心是什么&#xff1f;第二题. Spring由哪些模块组成&#xff1f;第三题. 详细讲解一下核心容器&#xff08;spring context应用上下文) 模块第四题.Spring框架中有哪些不同类型的事件第五题.…

网络中的进程监控

每个企业都有一些流程和程序来实现他们的业务目标&#xff0c;这同样适用于网络&#xff0c;网络中的进程监控是分析、处理和管理网络内发生的各种活动以提高网络性能和能力的做法。 网络中需要监控的基本进程 监视系统资源&#xff08;CPU 利用率、内存利用率、CPU 温度等&a…

【vue】如何打开别人编译后的vue项目

文件结构如下&#xff0c;编译后的文件放在dist中。 dist的文件结构大约如下&#xff0c;文件名称随项目 1.新建app.js文件 const express require(express);const app express();const port 8080;app.use(express.static(dist));app.listen(port, () > console.log); …

实用区块链应用:去中心化投票系统的部署与实施

一、需求分析背景 随着技术的发展&#xff0c;传统的投票系统面临着越来越多的挑战&#xff0c;如中心化控制、透明度不足和易受攻击等问题。为了解决这些问题&#xff0c;我们可以利用区块链技术去中心化、透明性和安全性来构建一个去中心化投票系统。这样的系统能够确保投票过…

六、回归与聚类算法 - 岭回归

目录 1、带有L2正则化的线性回归 - 岭回归 1.1 API 2、正则化程度的变化对结果的影响 3、波士顿房价预测 线性回归欠拟合与过拟合线性回归的改进 - 岭回归分类算法&#xff1a;逻辑回归模型保存与加载无监督学习&#xff1a;K-means算法 1、带有L2正则化的线性回归 - 岭回…

蓝桥杯C++竞赛常用库函数介绍

文章目录 前言一、二分查找1. 二分查找的前提2.binary_search函数3.lower_bound函数和upper_bound函数4.蓝桥杯例题 二、最值查找1. min和max函数2.min_element和max_element函数3.nth_element函数4.蓝桥杯例题 三、排序1.sort函数2.sort自定义比较函数,或lambda表达式(匿名函数…

流量分析——陇剑杯 2021【签到、jwt】

目录 签到1、攻击者正在进行的可能是什么协议的网络攻击 jwt1、该网站使用了______认证方式。前置知识&#xff1a;解&#xff1a; 2、黑客绕过验证使用的jwt中&#xff0c;id和username是3、黑客获取webshell之后&#xff0c;权限是什么4、黑客上传的恶意文件文件名是5、黑客在…

Sora没用上!国产AI创作恐怖电影:《生化危机:重生》下

Sora没用上&#xff01;国产AI创作恐怖电影&#xff1a;《生化危机&#xff1a;重生》下 丧尸围城&#xff0c;世界沦陷&#xff0c;爱丽丝是拯救这个世界的最后一剂解药&#xff0c;然而。。。 《生化危机&#xff1a;重生》&#xff08;下&#xff09;&#xff1a;在战斗的最…

物麒平台自定义事件代码修改流程

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送蓝牙音频&#xff0c;DSP音频项目核心开发资料, 1 配置工具对应关系 2 事件处理 3 事件定义 4

微服务远程调用Feign

目录 RPC概述 什么是Feign&#xff1f; Ribbon&Feign对比 Feign的设计架构 Spring Cloud Alibaba快速整合Feign Spring Cloud Feign扩展 日志配置 契约配置 通过拦截器实现参数传递 自定义拦截器实现认证逻辑 超时时间配置 RPC概述 微服务之间如何方便优雅的实…

来分析两道小题

一、源码 二、分析 首先它会接两个参数一个是id一个是ps&#xff0c;传递的话会包含一个flag.php&#xff0c;然后数据库连接&#xff0c;之后传递过滤&#xff0c;然后查询&#xff0c;如果查到了就会取id&#xff0c;取出来看是不是跟adog一样&#xff0c;如果是它告诉你账号…

算法------(12)Trie树(字典树)

例题&#xff1a;&#xff08;1&#xff09;Acwing 835. Trie字符串统计 Trie树是一个可以高效存储查询字符串的数据结构。将一个字符串的每一个字符作为一个根节点&#xff0c;从字符串头到字符串尾连接起来。因此我们可以把每一个字符串存储为一个节点&#xff0c;记录其子节…

C 嵌入式系统设计模式 09:硬件适配器模式

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之二&…

性能测试的几个指标范围(CPU,内存,IO,网络)

性能测试中&#xff0c;对服务端的指标监控也是很重要的一个环节。通过对各项服务器性能指标的监控分析&#xff0c;可以定位到性能瓶颈。 后端性能指标有 CPU&#xff0c;内存&#xff0c;网络&#xff0c;jvm&#xff0c;I/O 等等 分析思路 整体系统 CPU 利用率 内存利用…

【论文精读】Diffusion Transformer(DiT)

摘要 以往的Diffusion模型主要以卷积UNet作为主干网络&#xff0c;本文发现U-Net的归纳偏差对Diffusion模型的性能并不是至关重要的&#xff0c;可以用Transformer等取代。通过利用其他视觉方向成熟的Transformer方案和训练方法&#xff0c;Diffusion模型可以从这些架构中获益&…

一篇文章搞懂CDN加速原理

目录 一、什么是CDN CDN对网络的优化作用主要体现在以下几个方面&#xff1a; 二、CDN工作原理 CDN网络的组成元素&#xff1a; 三、名词解释 3.1 CNAME记录&#xff08;CNAME record&#xff09; 3.2 CNAME域名 3.3 DNS 3.4 回源host 3.5 协议回源 一、什么是CDN CD…