C语言——动态内存管理

大家好,本期和大家分享C语言动态内存管理有关知识,记得三连支持一下哦!

一、为什么要有动态内存分配

我们目前已经知道的内存开辟方式有两种,分别是:定义变量和开辟数组空间。

这两种方式都有共同的特点:

1.内存空间都是在栈空间上开辟的;

2.开辟的空间大小是固定的,数组在声明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整。

但是我们在编程时对于空间的需求往往是在程序运⾏的时候才能知道,那么开辟的空间一开始就固定的话,这对我们编程是有很多局限性的,所以,在C语言中程序员可以动态的申请空间和释放空间,这就大大的增加了程序的灵活性。

二、malloc、calloc、realloc和free

我们要想动态的开辟空间和释放空间,就必须用到以下几个函数:

malloc、calloc、realloc和free

下面我们就依次来介绍。

1.malloc

malloc是一个库函数,这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。

它有以下特点:

1.如果开辟成功,则返回⼀个指向开辟好空间的指针;
2.如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查;
3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定;
4.如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器;

5.使用要包含头文件#include<stdlib.h>.

如何使用呢?我们来看例子:

上面这段代码是以后我们经常会用到的申请空间的方式,肯定有许多小伙伴们不太理解,下面我就来讲解一下:

在使用malloc函数是我们要注意:

1.包含头文件#include<stdlib.h>;

2.强制转换指针类型;

3.判断空间是否成功开辟;

4.使用完后要手动释放并将使用的指针置空。

2.calloc

和malloc一样,calloc也是一个开辟空间的库函数,唯一不同的是,calloc在返回地址前会把开辟的空间每个字节都初始化为0。

下面我们来看个例子:

calloc的使用注意事项和malloc一样,这里不再赘述。

3.realloc

realloc也是一个开辟空间的库函数,功能和malloc相似,但是又有不同之处。有时我们在开辟空间的时候也不知道具体需要多大的的空间,开大了还不要紧,但是开小了就比较麻烦了,这个时候我们就可以使用realloc来解决这个问题。realloc 函数就可以做到对动态开辟内存⼤⼩的调整。

1.ptr是要调整的内存地址;
2.size 调整之后新⼤小;
3.返回值为调整之后的内存起始位置;
4.这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。
所以realloc在调整内存空间的是存在两种情况:

(1)原有空间之后有⾜够⼤的空间:
要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化;

(2)原有空间之后没有⾜够⼤的空间:
原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩
的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。

下面我们画图来理解一下:

在这里我们还要注意的是:情况(2)中新空间开辟之后会把原有空间中的数据拷贝到新空间中。

所以说realloc函数的功能还是很强大的。功能强大那么我们使用时需要注意的地方就更多,下面我们就来看看例子:

我们看这段代码好像没有什么问题,但它存在着一定的缺陷:

我们思考一下如果realloc扩容失败会怎么样?

上面说过realloc扩容失败会返回空指针,这就会导致前面的数据全都丢失了,这就非常得不偿失了。所以我们应该这样优化:

这样优化就能防止上面的情况出现。

当然,在通常情况下,realloc是可以当成malloc使用的

4.free

free是一个释放空间的库函数,在上面我们也使用过了,下面我们具体来介绍一下。

free函数⽤来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。

注意:malloc、calloc、realloc主动申请的空间都需要我们手动释放,如果不主动释放,除了作用域不会自动销毁就可能导致内存泄漏。

三、常⻅的动态内存的错误

下面我们来看一些动态内存常见的错误

1.对NULL指针的解引⽤操作

我们可以看到上面两段代码都警告说取消对空指针的引用,第一段代码当然运行时会崩掉,第二段代码即使运行出来了也只是没有遇到极端情况,遇到极端情况也是会崩的。

所以我们通常在使用开辟空间这些函数后要检查一下是否开辟成功,防止传回空指针导致下面的程序出错。

我们在使用指针时也要注意不要对空指针解引用。

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

对动态开辟空间的越界访问和数组越界访问相似,都是访问到不属于你的空间,这个时候往往会导致报错。

我们看到在VS上没有报错,这是因为VS环境还是比较友好的,但也报了警告,在其他环境下就不一定了。所以无论是数组还是动态开辟的空间我们都要控制好数据,防止越界访问。

3.对⾮动态开辟内存使⽤free释放

我们知道我们定义的变量系统会自己分配空间,当出了作用域时系统会自己销毁,不需要我们自己手动销毁。如果我们去干别人的事,就会出现问题。如:

4.使⽤free释放⼀块动态开辟内存的⼀部分

我们在做一件事的时候要去做完才算成功,free释放空间也是这样的。只释放一部分也会出现问题,如:

5.对同⼀块动态内存多次释放

当然一块空间释放完了,我们就不需要再去释放一遍,否则就会出现错误。

6.动态开辟内存忘记释放

这个问题我们上面也多次强调过,动态开辟的内存一定要手动释放掉,否则会导致内存泄漏。

一次我们可能察觉不到,但是在一个项目里,我们每次动态开辟的空间都不释放,到后面累计的到一定程度将会成为一个大问题。

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

四、C语言中程序内存区域划分

在上面我们提到栈、堆等等到底是什么呢?其实它们是内存中的区域划分。下面就来具体介绍一下C语言中内存区域是如何划分的。

1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等;
2. 堆区(heap):⼀般由程序员分配释放,若程序员不释放,程序结束时可能由系统回收。分配⽅式类似于链表;
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放;
4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

好了,以上就是本篇文章的全部内容了,创作不易,记得三连支持一下哦!我们下期再见!

相关推荐
陌小呆^O^8 分钟前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
I_Am_Me_23 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
重生之我是数学王子34 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
Ai 编码助手35 分钟前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php
学习前端的小z39 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹1 小时前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
zwjapple1 小时前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省1 小时前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript