绪论
书接上回,本章来到动态内存管理,这章的知识相较于结构体来说来简单一点,但是有许多地方需要注意不能马虎,并且该章的知识也比较重要,通过名称可以知道动态的内存管理,这样就可以对内存有一个很方便的管理方法!
所以安全带系好,发车啦(建议电脑观看)。
思维导图:
要XMind思维导图的话可以私信哈
目录
1.动态内存分配存在的意义
2.动态内存函数
2.1malloc
2.2free
2.3callloc
2.4realloc
3.动态内存常见的错误
3.1对NUL指针的解应用操作
3.2对动态内存开辟的空间越界访问
3.3对非动态开辟的内存进行free释放
3.4使用free释放动态内存开辟的一部分空间
3.5对同一块动态内存空间多次释放
3.6动态内存空间的忘记释放
4.动态内存常见问题、笔试题
5.C/C++程序在内存中的内存开辟
6.柔性数组
1.动态内存分配存在的意义
为什么要有动态内存管理?
下面通过对比来解释:
在我们一般申请内存的时候都是通过创建变量类型来申请的空间
如:int(4byte)、char(1byte)、int [ ]、结构体、联合体、枚举......
而这些内存的空间当我们创建好后他就固定死了,就比较的局限(死板)。
现在C语言提供了一种方式就是动态内存管理(函数)当我们需要时就创建一定的空间,当空间不够后又可以再次的补充其空间,当然当空间过大也可以减小空间的大小
2.动态内存函数
2.1malloc
知识点:
对于malloc函数来说他的类型是void *malloc(size_t);
用法:申请一个连续可用的size字节大小的空间,开辟成功时返回指向这块空间的指针
头文件:#include<stdlib.h>
所以一般的用法是要将返回来的void * 强转成自己所需要的指针类型
如:
int main() { //当你要开辟多个以整型大小为基础的空间时 int * ptr = (int *)malloc(sizeof(int) * 10);//当然你也可以直接在()内写成“40”byte //对于上面因为你需要开辟的是整形个大小的空间所以最后将返回的指针强转成整形指针类型 //并且ptr也要是整形指针这是你所要用的,你甚至可以把它想像成开辟了一个大小为10的整形数组 return 0; }
为什么这样写我已经写上了注释
细节:
既然是申请那就有可能会失败,所以当申请失败的时候系统会返回一个空指针(NULL)
所以在我们写完后要加上一个判断,判断其是否申请成功
if (ptr == NULL) { printf("%s\n", strerror(errno));//打印错误 //perror("ptr");//打印错误 return 0; }
不能开辟0byte的空间
判断空间是否申请成功
用free释放申请好的空间(并且将其指针置为空)
练习
用动态内存分配的空间来打印打印1~10:
分析:
因为最终传回来的是指针所以就可以通过指针的方法来进行内存的访问,并且所开辟的大小也是以整形大小一个个开辟的,所以直接+1即可
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> int main() { //当你要开辟多个以整型大小为基础的空间时 int* ptr = (int*)malloc(40); if (ptr == NULL) { perror("ptr");//打印错误 return 0; } for(int i = 0; i<10 ; i++) { *(ptr+i) = i +1; printf("%d ",*(ptr + i)); } free(ptr);//释放向内存中借的空间 ptr = NULL; return 0; }
2.2free
知识点:
函数:void free (void* ptr);
当我们向操作系统借了的空间所以用完了后我们需要将其归还(若不归还的话对这空间再你没有结束程序的过程中都处于被借的状态也就浪费了这片没有用的空间)
用法:释放所传递过来的动态内存开辟的指针所指向的空间
细节:
对于free所释放的空间,虽然将其所存的内存归还给了操作系统,但ptr所指地址并不会被其改变,所以为了防止我们后面不小心还使用ptr这个地址指向的被已经释放的内存,所以需要再将ptr置为NULL
free(ptr); ptr = NULL;
当所传递过来的是NULL时,free什么都不做。
不能释放不是动态内存开辟的空间
2.3callloc
知识点:
void* calloc (size_t num, size_t size);
该函数的意义和malloc一样,都是用来开辟空间的。
但是其参数不相同,num表示的是元素个数,而size表示的是每个元素的大小,返回值一样
所以对于之前用malloc开辟的10个整形大小的空间(40byte)就还可以写成calloc形式的:
int main() { int* p = (int*)calloc(10, sizeof(int)); if (p == NULL) { perror("p"); return 0; } //使用 free(p); p = NULL; return 0; }
细节:
同样的calloc也需要判断是否申请成功
用free释放申请好的空间(并且将其指针置为空)
通过对比上面两段代码我们可知:
malloc在申请往空间后不会对这部分空间有任何作业
calloc在申请完空间后会对这部分全部空间初始化为0
所以在我们使用时可以通过他们不同的类型进行选择使用
上面的malloc、calloc都是开辟内存的空间的,而下面的realloc就是修改开辟的内存空间
2.4realloc
知识点:
void* realloc (void* ptr, size_t size);
第一个参数是要调整的内存地址(由malloc、calloc、realloc开辟的内存块),第二个参数是所要调整的新的空间的大小(size个字节),返回情况是一样的
具体使用如下
int main() { int* p = (int*)malloc(sizeof(int) * 5); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } int* ptr = (int *)realloc(p, sizeof(int) * 10); if(ptr != NULL)//当等于空指针时就没有动作了不用管ptr { p = ptr; ptr = NULL; } for (int i = 5; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
细节:
在上述代码中我们可以发现在用realloc增加或减少动态内存的空间后 返回时 接收返回值的指针是一个新的指针(int * ptr)而不是所要改变的那片空间的地址(p),这是因为:realloc开辟空间时分着两种情况:
1.当realloc所要开辟的空间在原始空间的后面有足够的空间让其使用时那就直接在原始位置处开辟
2.当realloc所要开辟的空间在原始位置后面没有足够的空间让其使用,此时就会寻找一个放的下的空间,并且将原数据拷贝过去,并且释放掉原空间和返回新的地址
3.就是因为可能的两种情况,所以为了避免第二种释放原位置返回新地址,所以用一个新指针接收其返回的地址,如果直接写成 p = realloc(p,sizeof(int)*10)的话 假如开辟没有成功那就会返回NULL而原本还有的5个空间大小直接就没了(p = NULL)。
当第一个参数传的是NULL时其用法和malloc类型开辟申请一块新的空间:
realloc(NULL,40) == malloc(40) ;
并且返回指向这块空间的地址