一.动态内存函数介绍
内存函数的头文件:
include<stdlib.h>
1.free
free函数用来释放动态开辟的内存。
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么事都不做
使用场景:
int num=10; int* p=NULL; free(p); p=NULL;
2.malloc
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
- 如果开辟成功,则返回一个指向开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查
- 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
- 如果参数size为0,malloc的行为是未定义的,取决于编译器
情景1:申请内存空间失败——返回空指针
情景2:动态内存的底层原理
- 局部变量特点,进函数时创建,出函数时销毁
- 局部变量放在栈区
- 动态内存分配放在堆区,不会自动摧毁,直到程序退出才释放。(易造成爆内存)
情景3:释放动态内存空间后还要把p置为空指针——防止野指针
释放动态内存空间后,p还会记得一个地址(野指针)
1. free(p); 2. p=NULL;
3.calloc
- 函数的功能是为num个大小为size的元素开辟一块空间,并把空间每个字节都初始化为0
- 与malloc的区别:只在于calloc会在返回地址之前把申请的空间每个字节全初始化为0
相当于calloc=malloc+memset
情景1:calloc开辟的值是0,而malloc开辟的空间没有初始化是随机值
4.realloc
- ptr是要调整的内存地址
- size是要调整后的新大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
- 解决方案:使用时也要判断,创建临时指针ptr来判断,如果成功再给p指针
情景2:realloc调整空间,分类讨论(内存空间足够与否)
- 当内存空间足够时,拓展方法:就直接原有内存之后直接追加空间,原来空间数据不发生变化,这样函数返回的是原内存地址
情景1:realloc开辟失败,与malloc一样,返回的是NULL
- 当内存空间不足时,拓展方法:在堆空间上另外找一个合适的连续空间来使用,这样函数返回的是一个新的内存地址
情景4:realloc调整空间时,申请内存的起始位置是空指针
申请内存起始位置是空指针时:等价于malloc
int*p=(int*)realloc(NULL,40);等价于malloc(40)
二.常见的动态内存错误
情景一:对空指针进行解引用
解决方法:判断指针是否为空
情景二:对动态开辟空间的越界访问
情景三:对非动态开辟的内存使用free释放 / 对同一块动态开辟的内存多次释放
解决方法:把p指针置为NULL,则无影响
情景四:使用free释放一块动态开辟内存的一部分
在释放时,p指针已经移动,指向的不再是动态内存空间的起始位置
解决方法:
- 创建一个新指针ptr拷贝起始地址,遍历/移动指针时时用新指针ptr;
- 不改变"p指向的是动态内存空间的起始地址"的写法
情景五:未对动态开辟内存使用free释放——造成内存泄漏
指针会变成野指针,造成非法访问
三.几个经典的笔试题
例题1:
例题2:
例题3:内存泄漏
例题4:非法访问(野指针)
四.C/C++程序的内存开辟
五.柔性数组
C99中,结构中的最后一个元素允许是未知大小的数组
柔性数组的两种写法:
有些编译器无法编译时,可以改成:
1.柔性数组的特点
- 结构中的柔性数组成员前面必须至少有一个其他成员
- sizeof返回这种结构大小,不包括柔性数组的内存
- 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
- 柔性数组的体现——可以更改数组的大小
2.用另一种方式实现柔性数组功能
法二:“单独malloc块空间,在此之上realloc”
两种方式的对比:
- 第一种方式好处:方便内存释放
- 第二种方式好处:有利于访问速度(连续的内存有益于提高访问速度)
- 第二种方式坏处:要进行两次释放,容易造成内存泄漏