[TOC]
直入主题,动态内存管理!🕵️♂️
1.为什么会有动态内存分配?
我们一般使用以下两种方式开辟内存
1 | int a = 20;//在栈空间上开辟四个字节 |
但是上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有些时候,我们并不能提前知道需要的空间大小,而部分编译器并不支持变长数组。这时候以数组的方式开辟连续空间的方法就有点不适用了。
其次,全局变量/局部变量是存放在栈区里面的。如果存放的变量太多,就会出现栈溢出的错误
这时候就轮到动态内存上场了!
2.动态内存函数
2.1 malloc
1 |
|
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
1 |
|
2.2 free
上述代码里面出现了另外一个重要的函数,free
free函数用来释放动态开辟的内存,头文件是<stdlib.h>
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么事都不做。
free函数不能用来释放栈区里面的空间,栈区的空间由编译器进行自动创建和自动释放
重点!
被free之后的指针p指向的空间已经不属于我们的应用程序了。最好在free之后立马把指针置为NULL避免访问野指针。
1 | free(p); |
2.3 calloc
1 | void* calloc (size_t num, size_t size); |
calloc函数的功能和malloc基本一致,但是有一点不同:
- calloc函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0
1 |
|
添加断点并调试,可以看到calloc函数把这一块内存都初始化为0了
如果这块空间需要初始化,使用calloc函数比malloc更好
2.4 realloc
- 该函数用于更改已经创建好的动态内存空间(可改大可改小)
1 | void* realloc (void* ptr, size_t size); |
- ptr 是要调整的内存地址
- size 调整之后新大小
- 返回值为调整之后的内存起始地址
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
- 如果开辟失败,返回空指针NULL
在进行扩容操作的时候,会出现两种情况:
- 原动态内存空间之后有足够空间进行扩容
- 原动态内存空间之后无足够空间
如果是第一种情况,realloc函数会在这之后增添空间,分配给ptr指针。原来空间的数据不发生变化。
如果是第二种情况,realloc会找一块新的空间,开辟好后返回给ptr指针,并把原空间里的数据移动到新空间的对应位置。
情况2的时候,开辟完新空间之后,会把原来的空间给free掉(只有开辟成功才会释放原来的空间)
1 |
|
几点注意:
realloc(p, 80)
80指的是开辟之后新的空间大小为80,而不是增加80的空间- realloc函数可能开辟失败,这时候如果将开辟失败的返回值NULL赋值给了原有指针p,就很危险。
- 采用中间指针变量ptr,先判断realloc函数是否开辟成功,若成功,则赋值给p指针。
- 赋值给p之后ptr指针就没用了,置为空指针。
最后我们不需要对ptr进行free,因为ptr的指向和p是一样的
free(p)的时候,ptr所指向的空间也被free掉了
- realloc缩小空间的时候,会把原来空间后面的内容都剔除
3.常见错误
3.1对NULL指针的解引用
1 | void test() |
INT_MAX
是int类型的最大值,可以在头文件limits.h
里面查询并使用
3.2对动态内存空间的越界访问
和数组一样,动态内存空间也是不能越界访问的!
1 | void test() |
3.3对非动态内存空间进行free
1 | void test() |
3.4使用free释放动态内存空间的一部分
1 | void test() |
需要用一个指针来记住起始地址,用另外一个指针来进行赋值等操作
3.5对一个空间进行重复释放
1 | void test() |
在free之后立马把p置为空指针,就能避免这个问题
- free函数接收空指针,不做任何操作
3.6内存泄漏
如果忘记释放动态内存开辟的空间,就会导致内存泄漏
1 | void test() |
一般情况下,谁使用就谁释放。函数里使用就在函数里释放,除非需要传回主函数进行操作。
如果没有传回主函数,也没在函数里进行释放,该指针变量已经被销毁了,无法进行释放操作!
如果主函数里程序没有结束,就造成了内存浪费
4.C/C++程序中内存区域划分
栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
代码段:存放函数体(类成员函数和全局函数)的二进制代码。
有了这幅图,我们就可以更好的理解在《初识C语言》中讲的static关键字修饰局部变量的例子了。实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长
结语
动态内存其实就是数组的高级形式,它能让我们更方便的管理开辟的连续的空间。
你学废了吗?🤤
- 本文标题:【C语言】动态内存管理(详解malloc/calloc/realloc)
- 创建时间:2022-01-29 18:35:49
- 本文链接:posts/1456051952/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!