本篇重点内容;
1.为什么存在动态内存分配?
2.动态内存函数的介绍
✨malloc
✨free
✨calloc
✨realloc
3.常见的动态内存错误
4.几个经典的笔试题
5.柔性数组
我们已经掌握的内存开辟方式有:
创建一个变量或者创建一个数组
但是上面这样的开辟空间的方式有两个特点:
1.空间开辟的大小是固定的
2.数组在申明的时候,必须指明数组的长度,它所需要的内存在编译时分配
实际上,对于空间的需求,有时候我们需要的空间大小在程序运行时才能知道,那么上述数组的开辟方式就不能满足了。
这时就需要使用动态内存开辟。
malloc头文件
C语言提供了一个动态内存开辟的函数
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
(1)如果开辟成功,则返回一个指向开辟好空间的指针。
(2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。(注意开辟失败是因为malloc不会无节制的开辟下去)
对malloc的返回值进行判断:
if(p= =Null){
printf(“%s\n”,strerror(errno));
return 1;
}else{
…
}
注意:
a. strerror(errno):将错误码对应的错误信息的字符串地址返回
b. strerror 头文件
c. errno 头文件
(3)返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
(4)如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
(5)malloc的使用:
//malloc的使用
#include
#include
#include
#include
int main() {
//张三
//申请
int* p = (int*)malloc(20);
//对malloc的返回值进行判断
if (p == NULL) {
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++) {
p[i] = i + 1;//相当于*(p+i)=i+1;
}
for (i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
//释放malloc开辟的空间并置空
free(p);
p = NULL;
return 0;
}
(6)堆默认给malloc出来的空间初始化
如果不给malloc空间放值,直接打印出来空间的值是一些随机值
//堆默认给malloc出来的空间初始化
#include
#include
#include
#include
int main() {
//张三
//申请
int* p = (int*)malloc(20);
//对malloc的返回值进行判断
if (p == NULL) {
printf("%s\n", strerror(errno));
return 1;
}
//直接打印
int i = 0;
for (i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
//释放malloc开辟的空间并置空
free(p);
p = NULL;
return 0;
}
free头文件
专门是用来做动态内存的释放和回收的,函数原型如下:
free函数用来释放动态开辟的内存:
(1)要释放哪块空间就把哪块空间的起始地址传给free
(2)ptr必须指向的是动态开辟的空间,才能用free释放
(3)free空指针,则什么事情也不做
(4)free前,指针ptr本来指向一个地方,释放ptr指针之后,ptr里面还存着一个地址,如果不置空,这个地址被别有用心的人利用,ptr就是野指针了。
所以:释放指针完毕后,将指针主动置为空指针,这样就再也找不到刚刚开辟的空间了,避免出现野指针
calloc函数也用来动态内存分配。原型如下:
callloc(num个元素,每个元素的字节大小)
calloc会开辟一个像数组一样的内存块
注意点:
(1)calloc的功能是为num个大小为size字节的元素开辟一块空间,并且把空间的每个字节初始化为0
(2)calloc与malloc开辟空间的不同点是calloc会在返回地址之前把申请的空间的每个字节初始化为0
(3)calloc与malloc的对比:
a. 参数不一样
b. 都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0
如果要初始化,就使用calloc
如果不需要初始化,就使用malloc
(4)举例使用calloc:
//举例使用calloc
#include
#include
#include
#include
int main() {
int* p = (int*)calloc(10, sizeof(int));
//判断是否开辟成功
if (p == NULL) {
printf("%s\n", strerror(errno));
return 1;
}
//使用空间
int i = 0;
for (i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
return 0;
}
realloc可以对空间进行大小调整
函数原型如下:
realloc(要调整的空间的内存地址,调整后空间的新大小)
返回值为调整之后的内存的起始位置
注意点
(1)realloc调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
(2)realloc在调整内存空间时存在两种情况:
第一种情况:原有空间之后有足够大的空间,realloc后面追加的新的空间是不会赋初值的,只会把旧的数据拷贝下来,此时realloc返回的是旧地址
第二种情况:原有空间之后没有足够大的空间,扩展的方法是在堆空间上另找一个更大的连续空间来使用,将原来的数据拷贝到新的空间,释放旧的空间,此时函数返回一个新的内存地址
(3)堆中开辟的内存不一定是连续的
(4)如果realloc调整的空间要比原空间小, 多余的空间会全释放不使用,还给操作系统
(5)举例使用realloc:
//举例使用realloc:
#include
#include
#include
#include
int main() {
int* p = (int*)malloc(20);
if (p == NULL) {
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++) {
p[i] = i + 1;
}
int* ptr = (int*)realloc(p, 40);
if (ptr != NULL) {
p = ptr;
}
else {
printf("%s\n", strerror(errno));
return 1;
}
//调整空间后使用
for (i = 0; i < 10; i++) {
p[i] = i + 1;
}
for (i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
//释放malloc开辟的空间
free(p);
p = NULL;
return 0;
}
malloc/calloc动态开辟失败,没有检查返回值
举例说明:
解析:
如果动态开辟内存失败,p指针就是空指针,当i=0时,对p指针(空指针)解引用出问题,不能对空指针解引用
动态开辟申请了多少字节就访问多少字节,不要越界
举例说明:
解析:
总共开辟了20个字节的整型空间,在访问的时候访问了10个整型(40个字节)的数据,越界访问
在内存中开辟的空间如果不在堆上,不能用free释放
举例说明:
解析:
函数里面的局部变量或者数组都是在栈上开辟的空间,不能用free释放
free释放空间必须提供要释放空间的起始位置
举例说明:
解析:动态开辟的空间的起始位置的指针在使用的过程中被修改,不在再指向起始位置,使用完这块空间用free回收的时候,无法提供动态开辟内存空间的起始位置,程序出错
(1)malloc / calloc动态开辟的空间是放过一次就不能再释放了。如果在第一次释放完之后置为空指针,第二次释放相当于释放空指针没有问题
(2)与别的函数交互时,不了解函数内部,很可能在函数内部已经释放过一次,又回到主函数中释放一次导致对同一块动态内存多次释放出错;又或者在函数的内部没有释放动态开辟的空间,回到主函数中也没有释放,导致内存泄漏,程序出错
举例说明:
解析:
连续重复释放同一块动态内存出现问题
指的是动态开辟内存既不使用free释放,也不结束程序时造成的
✨重点✨
(1)malloc / calloc /realloc所申请的空间如果不想使用,需要用free释放。如果不释放,程序结束后,也会由操作系统回收。
(2)如果不使用free释放,程序也不结束,就会造成内存泄漏
意思是不用不还,程序也不结束,别人也无法利用这块空间,等于这块空间浪费掉了
(3)回收内存的两种方式:
a. free掉
b. 程序结束后,由操作系统回收
举例说明:
解析:
申请的20个字节的地址放在p指针里面,p是个局部变量,函数一旦返回,p就销毁不见了,并没有通过p把这20个字节的地址返回来。所以出了函数,来到主函数内部,主函数根本找不到这块内存空间在哪里,想释放都释放不了,造成了内存泄漏