本章重点
为什么存在动态内存分配
动态内存函数介绍
malloc
free
calloc
realloc
常见的动态内存错误
几个经典的笔试题
柔性数组
正文开始
1.为什么存在动态内存分配
我们已掌握的内存开辟方式有:
动态内存开辟图解:
2.动态内存函数的介绍
2.1malloc和free
malloc函数:头文件<stdlib.h>
向栈区申请一块空间,如果开辟成功返回指向此空间的指针(空指针),如果开辟失败返回NULL指针 。malloc不知道自己申请的空间的具体类型,需要我们自己强制类型转换为自己要用的类行。
参数:开辟的字节
eg:
malloc向栈区申请40个字节的空间(10个整形),返回值为指向这个空间的指针。在接收此指针的时候因为为空指针(无法解引用以及加减)需要强制类型转化成所需要的指针类型。
拓展:perror函数(打印错误信息)
参数为要检查的函数,如果函数内有错误则会打印出函数名:错误。
eg:
free函数:头文件<stdlib.h>
释放申请的空间。
free只能释放动态开辟的空间 。free释放完后指针依然保存地址但空间已经不存才,为了防止出现指针非法访问需要将指针重新定义为空指针。
(malloc与free必须成对出现)
eg:
malloc申请了一块空间用指针p存放,使用完后再用free将空间释放,释放后再将p指针定义为NULL(空指针)防止指针越界访问。
calloc函数:头文件<stdlib.h>
参数:1.元素个数(开辟元素个数)
2. 每个元素(字节长度)
calloc与malloc区别:calloc会对存储的内存进行初始化。
eg:
1.malloc
2.calloc
realloc函数:头文件<stdlib.h>
在已开辟的空间基础上再申请更多的空间以达到空间充足。
参数:1.调整的空间的起始地址
2.调整后的大小(num=已开辟的加上再次申请的空间大小(字节))
返回值类型:
在原开辟的空间后(空间充足)直接申请、
原开辟空间不够单独自己开辟出调整后的空间(原空间+申请空间),再把原空间元素复制过去。
3.没有合适空间后返回NULL。
接收增容后的空间:
拿临时指针接受,判断后再复制给原指针
eg:
1.先申请空间后,返回的指针赋值给临时指针prt
2.判断ptr是否为NULL
3.若不为NULL,再把ptr赋值给p
(这里应该进行强制类型转换):int*p=(int *)realloc()
返回类型图解:
realloc也当作malloc单独使用
把要调整的空间参数定义为NULL,则可当作malloc一样单独申请一块空间(在栈区)
动态内存常见错误:
1.对NULL指针的解引用操作
若p为NULL,下面直接使用p则会出现指针越界访问。所以要对p进行判断。
2.对动态开辟的空间的越界访问
3.对非动态开辟内存使用free释放
arr在栈区开辟的空间非动态开辟空间,运行会报错
4.使用free释放一块动态开辟内存的一部分
如图此时p指向5,free(p)也会释放p以后的空间,前面的没有释放会出现内存泄漏。
5.对同一块动态开辟的空间,多次释放。
多次释放也会是运行崩溃,如果第一次释放完 p=NULL,则没有有影响。
6.动态开辟的空间忘记释放
会出现内存泄露,若次函数一直循环则会一直内存泄漏慢慢累加则内存会被消耗完。
动态开辟空间两种回收方式:
经典笔试题:
1.
运行:无结果
p在getmemory函数中开辟了100个空间,但返回后str依然位NULL所以strcpy失败,且开辟的的空间没有被释放出现了内存泄漏。
修改:
把p强制类型转换为char*,再将上面函数返回值定义为 char*,再把p返回去
(因为p指向的空间为堆区开辟的空间,所以不会像局部变量一样被销毁)
printf(str);写法正确printf("")是打印“”内的字符串,且str正好为字符串数组可以直接打印。
2.
结果为无结果。
解释:p为局部变量,且为数组储存在栈区,栈区的局部变量一出函数就被销毁所以当str访问的时候p已经不存在。
3.
错误:ptr没有初始化里面放的随之所以为野指针。
4.
错误:没有free
malloc开辟的100字节空间没有被free,所以会出现内存泄漏。
5.
错误:str被提前free,访问的时候已经没有空间,非法访问。(只不过str只是记得空间位置,但空间早已被释放了)
总结:free完空间后一定要把指定初始化为NULL。
空间分配:
定义的变量存放在栈区,动态内存都存放在堆区
总结:
柔性数组\变长数组:(位于结构体中)
eg:定义一个柔性数组
定义的时候不定义数组的大小如 arr[]\arr[0],切为结构体的最后一个元素。
两种定义情况:
1.柔性数组特点:
1.前面至少有一个成员
2.sizeof计算结构体大小的时候不包括柔性数组的大小
eg:
3.包含柔性数组的结构体应用malloc开辟空间,且开辟的空间要比sizeof计算的结构体的大小,以用来分配给柔性数组。接收的时候也要强制类型转换为struct_*(结构体指针类型)
eg:
开辟后多余的空间分配给柔性数组,且柔性数组的空间大小还可以用realloc来改变(空间是可变的柔软的)所以称为柔性数组。
realloc增容:
最后释放的时候还是直接释放ps指针,因为ptr指针赋值给了ps
2.不写柔性数组,改成指针来代替:
1.创建:
绿色空间为第一次malloc开辟的空间,但没有arr*指向的空间
所以黄色空间为第二次malloc开辟的空间,为arr*指向目标所开辟的空间以达到柔性数组的格式。
2.增容与释放:
增加的时候直接增加arr*所指向区域开辟空间,所以为realloc(ps->arr,num)
释放的时候要先释放最近一次malloc开辟的空间,不然释放第一个malloc开辟的空间等释放第二个malloc开辟的空间是找不到指向空间的指针了,就会内存泄漏。
如此程序:
如果先释放ps,int n与*arr一同释放掉了等释放ptr的时候因为arr已经被释放掉了所以找不到指向空间的指针了所以无法被释放形成了内存泄漏。
3.柔性数组与替代版“柔性数组”的差别:
1.替代版需要开辟两次空间,用到了两次malloc,释放的时候需要两次free且有顺序性。
2.替代版空间一直频繁的开辟容易形成内存碎片
3.替代版影响效率问题
局部性原理:
空间局部性:当我们使用一块空间的时候再次开辟会使用此空间附件的空间
柔性数组优势:
只需要一次释放,且防止了内存碎片提高了访问速度。