目录
⛳前言
现代计算机基本都是基于冯诺伊曼结构体系设计出来的,冯诺伊曼结构体系的核心就是“存储程序”,将程序(指令集)和数据以同等地位存储在内存中。但是我们的内存空间并不是无限大的,所以为了高效的利用好内存空间,操作系统会对这些内存空间进行相应的分区,不同区域的内存有其对应的功能和使用方式。
比如局部变量、函数形参通常是存储在栈区的,这部分内存空间的特点就是临时使用,用完即释放(当然这个都是由操作系统自动完成的,不需要程序员的干预);
再比如全局变量通常存放在静态区,此外由static修饰的局部变量也会放到静态区(所以static修饰局部变量,本质上是改变了其存储的位置,从栈区-- > 静态区),这部分内存空间就是生命周期很长,长到整个程序运行结束;
再例如我们使用的常量字符串,会被保存到常量区,这部分内存区域的特点就是类似于“常量”,不可被修改,相当于添加了一个“const”的buff。
进入正题
思维导图:
⌛一、寻根问底
什么是动态内存分配 / 管理?
由程序员根据实际编程需要向操作系统申请,在堆区上开辟的,供程序员操作使用和维护的内存空间,程序员的游乐园!通常是一些临时用到的数据或者变量,随时开辟,用完随时释放,而不必等到函数结束后由操作系统回收!
为什么需要动态内存分配?
我们已经掌握的内存开辟方式有:
但是上述的开辟空间的方式有两个特点:
-
空间开辟大小是固定的。
-
数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编 译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
怎么建立动态内存分配?
通过系统提供的4个库函数实现,malloc\calloc\realloc\free
,这四个函数后面我们会详细介绍。
⌚二、动态内存函数
注意:以下说的四个函数的头文件均为:stdlib.h
malloc
C语言提供了一个动态内存开辟的函数
:
size_t就是unsigned int(无符号整型)
这个函数的作用就是在动态存储区中分配一个长度为size个字节的连续空间
,并返回指向该空间的指针。
1)如果开辟成功,则返回一个指向开辟好空间的指针。
2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3)返回值的类型是void *
,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4)如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
示例
动态开辟的空间如何释放和回收呢?
C语言提供了一个专门完成这个功能的库函数-- - free
free
C语言提供了另外一个函数free
,专门是用来做动态内存的释放和回收
的。
函数原型:
free的作用就是释放指针变量p所指向的动态空间,使这部分空间能够重新被利用。
1)如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
2)如果参数 ptr是NULL指针,则函数什么事都不做。
看一下实际的使用:
注意: 用malloc申请的空间,里面的内容是随机值,如果不初始化的话,可能就会得到一些意想不到的值;
理解:如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
为什么要进行动态内存的释放和回收?
内存空间是有限的,如果我们每次在使用的时候只是一味的向申请空间,即使空间再大,也会被用完,而且如果使用的空间不释放会导致电脑越来越卡,程序运行越来越慢!
那释放之后为什么要手动将指针赋值为NULL(空指针)呢?
举一个生活中的例子吧,假设有一个男生跟他女朋友分手了,如果这个男生还一直保留这个女生的电话、微信,更有甚者,还有这个女生家里面的钥匙。如果你是这个女生的话,你希望他仍然保留这些信息和物品吗?你肯定是不想对吧,指不定哪一天他不高兴或者其它原因就来骚扰你。(所以才会有一句话叫做情侣分手千万不要藕断丝连,当然如果你是这个男生的话,你可能还想着以后和好如初,念念不忘,必有回响~haha)。
言归正传,编程中如果指针指向的空间已经被释放了,如果不将其置为NULL,那么其仍然保留这个地方的地址,之后仍然有可能访问到这片空间,这个生活就是非法访问了!
那有没有动态分配函数在申请空间的同时就进行初始化呢 ?
答案当然是有,接下来要结束的calloc就是这样的一个函数
calloc
calloc函数也用来动态内存分配
,函数原型:
1)函数的功能是为
num
个大小为size
的元素开辟一块空间,并且把空间的每个字节初始化为02)与函数
ma1loc
的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
比如刚刚的上面的代码,如果我们将malloc换成calloc,不进行手动初始化:
在内存储存如下:
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
光从上面这三个函数的介绍,我们可能并没有深刻体会到**“动态内存分配”的动态体现**在哪,接下来要介绍的函数才是动态内存分配的“灵魂”-- - realloc
realloc
realloc函数
的出现让动态内存管理更加灵活
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整
。那rea1lloc函数就可以做到对动态开辟内存大小的调整
1)ptr是要调整的内存地址.size是调整之后新大小
2)返回值为调整之后的内存起始位置
3)这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。(这种移动的方式实际上就是复制拷贝,会将原内容复制拷贝到新内存中)
4)realloc在调整内存空间的是存在两种情况︰
情况1∶原有空间之后有足够大的空间
情况2︰原有空间之后没有足够大的空间
当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是∶在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些。