【C语言】动态内存深入了解(一口气刨根问底学完系列,全乎,建议三连点赞收藏)

目录

1.动态内存分配的原因 

2.动态内存函数的介绍

2.1malloc和free函数

2.2calloc函数

2.3realloc函数 

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

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

4.动态内存经典笔试题

4.1题目1

4.2 题目2:

4.3 题目3:

5. C/C++程序的内存开辟

6.结语


 

1.动态内存分配的原因 

我们目前知道的申请内存的两种方法:

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

但是,上述申请内存的办法,一旦开辟完成,内存空间的大小就固定了,就像我们的数组超出内存范围就会产生越界的问题,没有办法改变内存的大小,非常死板,固定化,不够灵活。我们想要更灵活的使用内存空间,当我们觉得少了就补充空间。

总的来说:

上述的开辟空间的方式有两个特点:

        1. 空间开辟大小是固定的。

        2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道(比如主页的通讯录项目,只能存储1000个人信息,当我们的联系人信息到101个就无法存储了), 那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。

注意:

固定的内存开辟如我们熟知的两种方式虽然内存使用不自由但是内存管理更为简便。

自由的动态内存分配可能会导致更为复杂的内存管理问题。

2.动态内存函数的介绍

2.1malloc和free函数

C语言提供了动态内存开辟,也就是可以申请内存的函数:

函数头文件:<stdlib.h>

函数功能

     ①   这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针(空间的起始地址)。

②如果开辟成功,则返回一个指向开辟好空间的指针。

 比如:我们以前用数组申请40个字节的空间我们会这样来做:

int arr[10];

现在使用malloc可以这样来用:

malloc(40)

那么现在我们对这个malloc申请的空间进行访问,最好的方式是不是可以当做一个整型来访问,那我们将函数的返回值放在一个整型指针中,访问一次跳过4个字节,但是malloc的返回值是void*需要强制类型转换:

int *p = malloc(40);

③如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

	int* p = (int*)malloc(40);//开辟成功返回指针,自己要知道申请的这段空间用于做什么,才好确定类型//可能开辟失败,做一下判断if (p == NULL){peror("malloc");//打印错误原因return 1;}//开辟成功,就可以利用p来访问这块空间int i = 0;for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}

看一下打印结果:

这里补充图解:

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

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

malloc(0)

建议不这么写。

⑤malloc只负责申请或者开辟空间,当空间用完(不是吧空间用尽的意思,是申请内存来做事这个动作结束)不会主动还给操作系统,除非程序结束。当程序不退出,动态申请的内存不会主动释放。所以当我们用完申请的空间后,就要使用free空间来释放我们申请的空间将他还给操作系统,不然就会出现很大的内存问题。

举例来说:

我们看这段代码:

while(1)
{
int* p = malloc(40);
}

这就是一个病毒程序,只要我们一启动,程序就一直向我们的内存申请空间原先申请的又不释放,最终会导致内存卡死,除非关机,,如果写入开机程序,就会循环往复。所以申请空间后需要释放我们介绍free内存释放函数。

C语言提供了free函数,用于做动态内存的释放和回收:

函数头文件:<stdlib.h>

函数功能:

①如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

就是说我们的free只能释放我们的动态申请的内存空间,如果:

int  a = 0;
int * ptr = &a;
free(ptr);//错误写法

这种用法就是C语言标准未定义的是错误的。

②如果参数 ptr 是NULL指针,则函数什么事都不做。

③当空间被释放后,原先指向这片空间的指针需要我们手动将其置空,变为空指针,因为原先的空间被释放了,但是指针里面还保存着我们原先的地址,当不慎使用这个指针的时候,这个时候我们的指针就会变成野指针,会造成程序出现问题所以需要手动置空。

int main()
{//代码1int num = 0;scanf("%d", &num);int arr[num] = {0};//代码2int* ptr = NULL;ptr = (int*)malloc(num*sizeof(int));if(NULL != ptr)//判断ptr指针是否为空{int i = 0;for(i=0; i<num; i++){*(ptr+i) = 0;}}free(ptr);//释放ptr所指向的动态内存ptr = NULL;//手动置空return 0;
}

补充:free函数可能运作的机制:

当我们的malloc去开辟空间的时候,比如我们需要开辟40个字节的空间,但是当我们的函数在执行的时候可能会稍微开辟大一点的空间:

 

2.2calloc函数

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

函数头文件:stdlib.h

函数功能:

①函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

②与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

③也有可能开辟失败,所以需要和malloc一样做判断

例子使用:

int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}//开辟成功打印int i = 0;for (i = 0; i < 10; i++){printf("%d", p[i]);}//释放free(p);p = NULL;return 0;
}

将每个字节初始化为0.

我们看一下开辟失败的情况:

int main()
{int* p = (int*)calloc(INT_MAX*2, sizeof(int));if (p == NULL){perror("calloc");return 1;}//开辟成功打印int i = 0;for (i = 0; i < 10; i++){printf("%d", p[i]);}//释放free(p);p = NULL;return 0;
}

2.3realloc函数 

realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。

  •  函数头文件:stdlib.h
  • ptr 是要调整的内存地址(被调整空间的起始地址,如果这里传入一个空指针和malloc函数功能一样)
  • size 调整之后新大小,这里不是在后面补充,是包括原先已经申请好的大小,比如原先申请了10个空间,不够,不能说size传10变成20个空间,而是传20,变成一个20大小的空间。
  • 返回值为调整之后的内存起始位置。(所以当我们用一个指针来接收返回值的时候,这个指针可能指向原先malloc开辟的空间的起始位置也有可能指向一块新空间,这和realloc函数功能相关,下面两种情况:)
  • 增容失败会返回空指针。

补充:

int * p = (int*)malloc(40);
if(p ==NULL)
{
perror("malloc");
return 1;
}
//增加空间
p  = realloc(p,80);

注意:这种写法不严谨,因为我们的realloc函数有可能增容失败,比如后续实在没有空间了,就会增容失败,失败返回空指针赋值给P,就导致P原先维护的40个字节的空间也找不到了,内存空间既用不上也找不到,就会造成内存泄漏。正确的做法应该是重新使用一个指针来接收。

  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。 realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间:直接在后面开辟空间后返回原始空间的地址。

情况2:当申请空间的后续空间的内存被别的占用无法扩展,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

四步:
1.首先会开辟新的空间

2.会将旧空间中的数据看拷贝到新空间

3.释放旧的空间(realloc函数会做)

4.返回新的空间的起始地址

我们来看一下这个函数的正确使用:

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//开辟成功打印int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}//增加一些空间int * ptr = (int*)realloc(p, 80);//判断一下if (ptr != NULL){p = ptr;ptr = NULL;}else{perror("realoc");return 1;}for (i = 0; i < 20; i++){printf("%d\n", p[i]);}//释放free(p);p = NULL;return 0;
}

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

如果malloc开辟空间失败,返回空指针就会出问题,应该是开辟伴随着判断。

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

我们申请的空间也是有大小的,不可以申请40字节空间,做需要80空间的事。

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

我们的free只能释放我们的动态申请的内存空间

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

注意对比:

p[i] = i+1;

p[i] = *(p+i)//p指向的位置没变

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

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

void test()
{int *p = (int *)malloc(100);if(NULL != p){*p = 20;}
}
int main()
{test();while(1);
}

这是一段极端代码,我们的程序运行起来,,由于p是局部变量,出了函数就销毁了,那开辟的空间虽然在,我却没有地址找到了,想释放都释放不了用不上也找不到就内存泄漏了。

每天跑一跑,内存少一少,内存耗干重启又开始耗,这就是一个服务器BUG代码。

最好的理解例子:

梁朝伟的无间道:

当卧底的一直和唯一的警察单线联系,当这个警察死掉了,就没人知道你的真实身份了。

4.动态内存经典笔试题

4.1题目1

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

问运行会有什么样的后果

分析:

代码问题:

①对空指针进行解引用,导致程序崩溃

②动态申请的空间没有释放,且无法释放导致内存泄漏。

4.2 题目2:

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

问,运行以上代码会有什么样的结果:

分析:

结果是非法访问

这是典型的返回栈空间地址的问题

当我们使用static修饰,延长这个空间的生命周期就可以了:

4.3 题目3:

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

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

问题在于:最后申请弟弟空间没有释放
 

free(str);
str= NULL;

5. C/C++程序的内存开辟

C/C++程序内存分配的几个区域:

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

     有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了。 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序 结束才销毁 所以生命周期变长。

6.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,感谢大家的关注与喜欢。

 

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

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

相关文章

Swift 初见

Swift 初见 学习swift的记录 控制台输出 print("hello world")可以无分号&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 变量声明 let 常量声明 如果在常量声明后再次修改他会报错 var 变量声明 一个常量的值&#xf…

linux应用 进程间通信之共享内存(POSIX)

1、前言 1.1 定义 POSIX共享内存是一种在UNIX和类UNIX系统上可用的进程间通信机制。它允许多个进程共享同一块内存区域&#xff0c;从而可以在这块共享内存上进行读写操作。 1.2 应用场景 POSIX共享内存适用于需要高效地进行大量数据交换的场景&#xff0c;比如多个进程需要…

upload-labs文件上传漏洞靶场

第一关 <?php eval ($_POST[123]);?>发现他这个是通过客户端前端写了一个限制 我们禁用srcipt即可 蚁剑成功打开 第二关 我们上传文件2.php它提示我们文件类型不正确 我们可以联想到做了后缀检测 我们通过burp抓包修改后缀 第三关 我们上传一个.php文件不可上…

Peter算法小课堂—区间模型

Peter Pan来啦…… 最大不重叠区间数 二话不说&#xff0c;先来一道题 大家想想怎么贪心&#xff1f;我们可以将每一个美食摊位抽象成一个区间&#xff0c;区间左端点为开始排队时间&#xff0c;右端点为结束排队时间。其中&#xff0c;时间信息可以用数轴表示。 额……我们…

【分布式技术专题】「Zookeeper中间件」Paxos协议的原理和实际运行中的应用流程分析

Paxo算法介绍 Paxos算法是莱斯利兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。 Paxos产生背景 Paxos算法是基于消息传递且具有高度容错特性的一致性算法&#xff0c;是目前公认的解决分布式一致性问题最有效的算法之一&#xff0c;其解决的问题就是在分…

轮播图 HarmonyOS 鸿蒙 ArkTS ArkUI

第一步&#xff1a;新建图片数组 State swiperimgs:Array<Object>[$r(app.media.a), //本地图片或者网络图片$r(app.media.b),$r(app.media.c),$r(app.media.d)] 第二步&#xff1a;写入轮播图代码 Column(){Swiper(){ForEach(this.swiperimgs, (item) > {Image(item…

【c语言】字符串常见函数 上

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

【JVM篇】怎么解决内存泄漏问题

文章目录 &#x1f50e;什么是内存泄漏&#x1f6f8;解决内存泄漏⭐发现问题⭐诊断原因⭐修复问题 &#x1f50e;什么是内存泄漏 在Java中如果不再使用一个对象&#xff0c;但是这个对象仍然在GC Root的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情…

openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O

文章目录 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O218.1 查看I/O状况218.2 性能参数分析 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O 获取openGauss节点的CPU、内存、I/O和网络资源使用情况&#xf…

[C++]17:二叉树进阶

二叉树进阶 一.二叉搜索树&#xff1a;1.二叉搜索树的概念&#xff1a;2.二叉搜索树的实现---循环版本&#xff1a;1.二叉搜索树的基本结构&#xff1a;2.查找&#xff1a;3.插入&#xff1a;4.中序遍历&#xff1a;5.删除&#xff1a; 3.二叉搜索树的实现---递归版本&#xff…

随机过程及应用学习笔记(二)随机过程的基本概念

随机过程论就是研究随时间变化的动态系统中随机现象的统计规律的一门数学学科。 目录 前言 一、随机过程的定义及分类 1、定义 2、分类 二、随机过程的分布及其数字特征 1、分布函数 2、数字特征 均值函数和方差函数 协方差函数和相关函数 3、互协方差函数与互相关函…

Java String源码剖析+面试题整理

由于字符串操作是计算机程序中最常见的操作之一&#xff0c;在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质&#xff0c;并结合面试题来帮助理解。 String基本用法 在Java中String的创建可以直接像基本类型一样定义&#xff0c;也可以new一个 Str…

[ai笔记5] 个人AI资讯助手实战

欢迎来到文思源想的ai空间&#xff0c;这是技术老兵重学ai以及成长思考的第5篇分享&#xff0c;也是把ai场景化应用的第一篇实操内容&#xff01; 既然要充分学习和了解ai&#xff0c;自然少不了要时常看看ai相关资讯&#xff0c;所以今天特地用字节的“扣子”做了一个ai的资讯…

Java中抽象类和接口的区别

抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题)。 核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写而重写抽象方法), 而接口中不能包含普通方法&#xff08;接口…

宿舍|学生宿舍管理小程序|基于微信小程序的学生宿舍管理系统设计与实现(源码+数据库+文档)

学生宿舍管理小程序目录 目录 基于微信小程序的学生宿舍管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 &#xff08;1&#xff09;学生信息管理 &#xff08;2&#xff09;公告信息管理 &#xff08;3&#xff09;宿舍信息管理 &am…

CentOS7集群安装JDK1.8

准备工作 1、提前安装三台虚拟机&#xff0c;可以参考:https://mp.csdn.net/mp_blog/creation/editor/136010108 2、三台虚拟机分别配置免密登录&#xff0c;参考&#xff1a;https://blog.csdn.net/LSW_JAVADP/article/details/121757927 安装 JDK 一、官网下载对应JDK 自…

mlxtend,一个非常好用的 Python 库!

前言 Python 的 MLxtend&#xff08;Machine Learning Extensions&#xff09;库是一个强大的工具&#xff0c;为机器学习实验提供了一系列功能强大的扩展和工具。本文将深入探讨 MLxtend 库的核心功能、用法以及如何在机器学习项目中充分发挥其优势。 目录 前言 什么是 MLx…

华为问界M9:全方位自动驾驶技术解决方案

华为问界M9的自动驾驶技术采用了多种方法来提高驾驶的便利性和安全性。以下是一些关键技术&#xff1a; 智能感知系统&#xff1a;问界M9配备了先进的传感器&#xff0c;包括高清摄像头、毫米波雷达、超声波雷达等&#xff0c;这些传感器可以实时监测车辆周围的环境&#xff0…

【在Linux世界中追寻伟大的One Piece】Linux是从哪里来的?又是怎么发展的?基本指令你知道哪些?

目录 1 -> Linux背景 1.1 -> Linux发展史 1.1.1 -> UNIX发展历史 1.1.2 -> Linux发展历史 1.2 -> 开源 1.3 -> 官网 1.4 -> 企业应用现状 1.5 -> 发行版本 1.6 -> OS概念&#xff0c;定位 2 -> Linux下基本指令 2.1 -> Is指令 2…

高效的工作学习方法

1.康奈尔笔记法 在这里插入图片描述 2. 5W2H法 3. 鱼骨图分析法 4.麦肯锡7步分析法 5.使用TODOLIST 6.使用计划模板&#xff08;年月周&#xff09; 7. 高效的学习方法 成年人的学习特点&#xff1a; 快速了解一个领域方法 沉浸式学习方法&#xff1a; 沉浸学习的判据&am…