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