C语言【动态内存】

1.为什么要有动态内存

我们现在掌握的内存开辟方法有:

int val = 20;//在栈空间开辟4个字节
char str[10]={0};//在栈空间开辟10个字节的连续的空间

但是上述的方式有两个点要注意:

1.空间开辟的大小是固定的
2.数组在申明的时候,一定要指定数组的长度(就算是变长数组也是先给变量赋值,再根据变量的大小,来开辟空间),数组空间一旦确定了大小是不能调整的

但是我们在实际上对于内存的需求方式,绝不仅仅是上述的情况。有时候我们需要的空间大小是在程序运行的时候才能知道,数组是在编译时开辟空间的,这是就不能满足需求了。
这时C语言就引入了动态内存开辟,让程序员可以自己根据需求来申请和释放空间这样就比较灵活。

2.动态内存函数

要想学会动态开辟内存就必须掌握这四个函数malloc free calloc realloc

它们的头文件都为<stdlib.h>
他们开辟空间的大小单位都为字节。

2.1 malloc

函数原型:void* malloc(size_t size);
该函数向内存申请一块连续何用的空间,并返回指向这块空间的指针。

  1. 如果开辟成功,返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则会返回一个NULL指针,因此malloc的返回值一定要做检查。

因为返回类型是void*,所以malloc函数并不知道开辟空间时是什么类型,在实际使用时类型是有使用者决定的。

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

代码如下:

#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}

在这里插入图片描述

注意:malloc开辟的空间并未进行初始化,里面是随机值

#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}
#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//未进行初始化//打印printf("%d ", *parr + i);}return 0;
}

结果如下:
在这里插入图片描述

这些动态开辟的空间是存放在哪里呢?

如图:
在这里插入图片描述

2.2 free

申请了空间在使用完的时候肯定是要还回去的嘛,那怎么还呢?

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

  • 如果参数ptr指向的空间不是动态内存的时候,free函数的行为是未被定义的
  • 如果参数ptrNULL指针,那么free函数什么都不会做

注意:传递给free函数的参数是要被释放空间的起始地址
代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}//只用完动态申请的空间要进行释放free(parr);parr = NULL;return 0;
}

​free仅仅是将空间的使用权限还给了操作系统;
​但parr还指向原来的地址,这就成为野指针;
为了避免成为野指针,及时将parr置为NULL。

如果一直不用函数free来释放申请的空间,这样很可能会造成内存泄漏!!!

2.3 calloc

C语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下:
void * calloc(size_t num, size_t size);

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

由于函数malloc比函数calloc少一步(将内存初始化),所以malloccalloc更快,如果更追求效率的话可以使用malloc,如果不想自己初始化的话可以使用calloc

代码如下:

int main()
{int* pca = (int*)calloc(10,sizeof(int));if (pca == NULL)//判断是否开辟失败{perror("calloc");exit(1);//直接退出程序}printf("calloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pca + i);}printf("\n\n");int* pma = (int*)malloc(10 * sizeof(int));if (pma == NULL){perror("malloc");exit(1);}printf("malloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pma + i);}return 0;
}

结果如图:
在这里插入图片描述

2.4 realloc

realloc函数能让动态内存管理更加灵活。

有些时候我们会发现我们申请的空间太小了,有时候又感觉申请的空间太大了,那么为了合理的使用空间,我们就会对申请的空间大小进行灵活的调整,那么realloc函数就能做到对动态空间大小的调整。

函数原型如下:
void* realloc(void* ptr, size_t size)

  • ptr是要被调整的内存地址
  • size是调整之后该内存的大小
  • 返回值为调整之后的内存空间的起始位置

2.4.1 realloc在调整内存空间有三种情况

  1. 原空间后面有足够的空间用来调整。
  2. 原空间后面没有足够空间用来调整,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
  3. 空间调整失败,返回NULL(这是最坏的情况)
    如图所示
    在这里插入图片描述
    代码如下:
#include<stdio.h>
#include<stdlib.h>int main()
{//开辟int* parr = (int*)malloc(5 * sizeof(int));if (parr == NULL){perror("malloc");}//使用for (int i = 0; i < 5; i++){*(parr + i) = i + 1;}//假设要多插入5个int类型的数据//这时空间就不够了,需要调整int* parr = (int*)realloc(parr, 10 * sizeof(int));//上述代码合适吗//不合适,因为万一调整失败,会返回NULL//这时我原来开辟的空间就找不到了//所以正确的方法应该如下//先申请调整空间int* ptr = (int*)realloc(parr, 10 * sizeof(int));//调整成功if (ptr != NULL){//将申请的空间地址赋给parr(如果是情况二,parr的原空间已经被释放了)parr = ptr;}else{perror("realloc");}//处理要求……free(parr);return 0;
}

上述代码int* parr = (int*)realloc(parr, 10 * sizeof(int));代码合适吗?
完全不合适!!! 因为万一调整失败,会返回NULL;
这时我原来开辟的空间就找不到了。
所以正确的realloc使用方式是先创建一个指针变量来接收开辟的空间,如果判断ptr是否开辟成功,如果开辟成功。将ptr赋给parr

如果开辟失败parr指向的原空间也能继续使用。

补充:realloc也可以完成malloc的功能;
如果传的是NULL指针,realloc就会开辟一块新空间。

3.常见的动态内存的错误

3.1对NULL指针的解引用

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr = (int*)malloc(INT_MAX);*ptr = 20;//如果ptr是NULL,就会有问题return 0;
}

在这里插入图片描述
解决方法:开辟空间后,要第一时间判断是否空

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

具体如下:

#include<stdio.h>
#include<stdlib.h>void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}int main()
{test();return 0;
}

解决方法:控制好访问的范围

3.3对非动态开辟空间使用free释放

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int a = 0;int* p = &a;//处理……free(p);//这样行吗,肯定不行!!!//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)return 0;
}
//这样行吗,肯定不行!!!
//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的
//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)

在这里插入图片描述
编译都无法编译过去

解决方法:不要对非动态开辟空间进行释放!!!

3.4使用free释放的并不是动态开辟空间的起始位置

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置return 0;
}

在这里插入图片描述
同样也无法编译过去
解决方法:传给free的时候确保是空间的起始地址

3.5对同一块内存进行对此释放

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);free(p);return 0;
}

在这里插入图片描述
同样还是无法编译过去

解决方法:在第一次释放后及时的给p赋NULL

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);//解决方法p = NULL;free(p);return 0;
}

前面也说过了,如果传给free函数的是NULLfree不会进行如何的操作。

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

具体如下:

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();//出来后test就被销毁了,就无法找到p所指向的空间了while (1);
}

忘记释放不再使用的空间会造成内存泄漏
释放动态内存有两个方法:

  1. 在不用的时候使用free函数进行释放

  2. 如果一直没有被free释放,当程序运行结束后,会由操作系统来回收
    解决方法:
    1.谁申请的空间谁释放,如果在函数里,那么在出函数前记得释放malloc/calloc/realloc要和free成对出现。

    2.如果不能释放(后续会用到),要告诉下一个使用者,记得释放动态开辟空间。

切记:动态开辟的空间⼀定要释放,并且正确释放。

结语

最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!

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

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

相关文章

数据驱动,敏捷前行|MongoDB线下技术沙龙-杭州站活动

扫描海报中二维码或点击阅读原文&#xff0c;报名参加阿里云MongoDB在5月11日杭州举办的【数据驱动&#xff0c;敏捷前行——MongoDB企业开发加速器】线下沙龙活动&#xff0c;与MongoDB专家以及其他游戏行业同行一起探讨轻松获得游戏数据库高可用性和弹性的方法&#xff01; 在…

安卓获取SHA

1&#xff1a;安卓通过签名key获取SHA 方式有两种&#xff0c; 1、电脑上来存在eclipse的用户或正在使用此开发工具的用户就简单了&#xff0c;直接利用eclipse 走打包流程&#xff0c;再打包的时候选择相应的签名&#xff0c;那么在当前面板的下面便会出现签名的相关信息。 2、…

23 重构:烟囱式、平台化、中台化的架构

上一讲里&#xff0c;我们介绍了两大类型的系统升级重构方案&#xff0c;还介绍了如何进行重构版本的上线&#xff0c;以及如何平滑地完成新老版本切换的方案。在本讲里&#xff0c;将会具体介绍如何判断系统发展到什么阶段需要重构&#xff0c;以及如何实施重构。 系统稳定性…

AutoBackgroundBackButton 在ScrollView上方自动根据返回键按钮下方内容动态改变颜色。自动变色返回键

在日常有时候有一些为了优化体验的需求。AutoBackgroundBackButton 一个可以根据按钮下方背景颜色动态的改版返回键自定义ImageView。这里只展示了黑白切换方式&#xff0c;你如果还有其他需求可以参考颜色校验来自己实现切换对应颜色按钮。【例如白色背景展示黑色样式&#xf…

Python urllib 爬虫入门(1)

本文主要为Python urllib类库函数和属性介绍及一些简单示例。 目录 urllib爬取网页 简单示例 写入文件 其他读取方法 readline函数 readlines函数 response属性 当前环境信息 返回状态码 返回url地址 对url进行编码与解码 写入文件 总结 urllib爬取网页 通过pyth…

PotatoPie 4.0 实验教程(35) —— FPGA实现摄像头图像二值化膨胀效果

手机扫码 链接直达 https://item.taobao.com/item.htm?ftt&id776516984361 什么是图像二值化膨胀&#xff0c;有什么作用&#xff1f; 图像二值化膨胀是图像处理中的一种基本操作&#xff0c;它用于扩展和增强二值图像中的白色区域。具体而言&#xff0c;二值化膨胀操作…

【论文笔记】Training language models to follow instructions with human feedback A部分

Training language models to follow instructions with human feedback A 部分 回顾一下第一代 GPT-1 &#xff1a; 设计思路是 “海量无标记文本进行无监督预训练少量有标签文本有监督微调” 范式&#xff1b;模型架构是基于 Transformer 的叠加解码器&#xff08;掩码自注意…

LeetCode55:跳跃游戏

题目描述 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 解题思想 每次…

update_min_vruntime()流程图

linux kernel scheduler cfs的update_min_vruntime() 看起来还挺绕的。含义其实也简单&#xff0c;总一句话&#xff0c;将 cfs_rq->min_vruntime 设置为&#xff1a; max( cfs_rq->vruntime, min(leftmost_se->vruntime, cfs_rq->curr->vruntime) )。 画个流…

滑动窗口详解

目录 一、滑动窗口的特定步骤&#xff1a; 二、题目解析 1、⻓度最⼩的⼦数组---点击跳转题目 3、最⼤连续 1 的个数 III----点击跳转题目 4、将 x 减到 0 的最⼩操作数----点击跳转题目 5、⽔果成篮----点击跳转题目 滑动窗口是双指针算法中细分的一种&#xff0c;它由暴…

PDF高效编辑器,支持修改PDF文档并转换格式从PDF文件转换成图片文件,轻松管理你的文档世界!

PDF文件已成为我们工作、学习和生活中不可或缺的一部分。然而&#xff0c;传统的PDF阅读器往往只能满足简单的查看需求&#xff0c;对于需要频繁编辑、修改或转换格式的用户来说&#xff0c;就显得力不从心。现在&#xff0c;我们为您带来一款全新的PDF高效编辑器&#xff0c;让…

挑战一周完成Vue3项目Day3: 品牌管理+平台属性管理+SPU管理+SKU管理

一、真实接口替换mock接口 &#xff08;1&#xff09;替换各个环境下的服务器地址&#xff08; .env.development、.env.production、.env.test &#xff09; VITE_SERVE"http://sph-api.atguigu.cn" &#xff08;2&#xff09; 配饰代理跨域&#xff1a;vite.con…

根据标签最大层面ROI提取原始图像区域

今天要实现的任务是提取肿瘤的感兴趣区域。 有两个文件&#xff0c;一个是nii的原始图像文件&#xff0c;一个是nii的标签文件。 我们要实现的是&#xff1a;在标签文件上选出最大层面&#xff0c;然后把最大层面的ROI映射到原始图像区域&#xff0c;在原始图像上提裁剪出ROI…

PLC通过Modbus转Profinet网关连接变频器与电机通讯

Modbus转Profinet网关&#xff08;XD-MDPN100&#xff09;是一种能够实现Modbus协议和Profinet协议之间转换的设备。Modbus转Profinet网关可提供单个或多个RS485接口&#xff0c;PLC作为控制中枢&#xff0c;变频器作为控制电机转速&#xff0c;通过Modbus转Profinet网关&#…

瑞米派实时系统与EtherCAT移植-米尔Remi Pi

1.概述 Remi Pi采用瑞萨RZ/G2L作为核心处理器&#xff0c;该处理器搭载双核Cortex-A551.2GHzCortex-M33200MHz处理器&#xff0c;其内部集成高性能3D加速引擎Mail-G31 GPU(500MHz)和视频处理单元&#xff08;支持H.264硬件编解码&#xff09;,16位的DDR4-1600 / DDR3L-1333内存…

Webshell绕过技巧分析之-base64编码和压缩编码

在网络安全运营&#xff0c;护网HVV&#xff0c;重保等活动的过程中&#xff0c;webshell是一个无法绕过的话题。通常出现的webshell都不是以明文的形式出现&#xff0c;而是针对webshell关键的内容进行&#xff0c;混淆&#xff0c;编码来绕过网络安全产品&#xff0c;例如IDS…

计算机提示msvcp110.dll是什么意思?msvcp110.dll丢失恢复办法

在Windows操作系统中&#xff0c;动态链接库&#xff08;DLL&#xff09;扮演着至关重要的角色&#xff0c;它们是实现程序间代码共享和模块化设计的关键组件。msvcp110.dll&#xff0c;作为Microsoft Visual C 2012运行时库的一个组成部分&#xff0c;是理解现代软件开发和维护…

【酱浦菌-爬虫技术细节】解决学术堂爬虫翻页(下一页)问题

首先我们通过css选择器获取页码信息&#xff0c;这里的css选择器&#xff0c;选择的是含有a标签的所有li标签&#xff0c;代码如下&#xff1a; li html_web.css(div.pd_c_xslb_left_fenye ul li>a) for li in li:li_url li.css(a::attr(href)).get()li_num li.css(a::t…

STM32入门_江协科技_3~4_OB记录的自学笔记_软件安装新建工程

3. 软件安装 3.1. 安装Keil5 MDK 作者的资料下载的连接如下&#xff1a;https://jiangxiekeji.com/download.html#32 3.2. 安装器件支持包 因为新的芯片层出不穷&#xff0c;所以需要安装Keil5提供的器件升级版对软件进行升级&#xff0c;从而支持新的芯片&#xff1b;如果不…

unity-C#调用百度千帆AppBuilder的OpenApi

目录 功能描述准备工作百度智能云账号创建应用编辑应用创建Api秘钥Api调用流程unity代码Unitywebrequest非流式流式注意事项 Restsharp 功能描述 使用百度千帆AppBuilder平台,通过api调用的方式实现AI大模型对话功能(文字) 准备工作 百度智能云账号 请自行在百度智能云进行…