前言
我们已经掌握的内存开辟的方法有两种
int a = 10; //在栈空间上开辟4个字节的空间
int a[10] = {0}; //在栈空间上开辟40个字节的连续空间
这些开辟方式都有两个共同的特点:
1.空间开辟大小是固定的
2.数组在申明的时候,必须指定数组的长度,它需要的内存在编译的时候分配
我们为什么要实现动态管理内存呢,这又什么作用呢?
我们对于空间的需求不仅仅只是上面两种,有时候我们到底需要多少空间,需要运行之后才能知道,这个时候就需要动态开辟内存空间,即动态内存函数就诞生了!
一、动态内存函数有那些?
1.malloc和free
2.calloc
3.realloc
1.1 malloc和free
malloc是C语言提供的一个动态内存开辟的函数:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
1.如果开辟成功,则返回一个指向开辟好空间的指针。
2.如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
void*的返回类型,使用的时候根据情况强制类型转换
C语言还提供free函数,专门是用于做动态内存的释放和回收的,函数原型如下:
free函数是用来释放动态开辟的内存:
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。(会报错)
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
图文演示:
头文件要加上 malloc.h
代码演示:
1. int main() 2. { 3. int num = 0; 4. scanf("%d", &num); 5. //int arr[num] = { 0 }; num 在 [] 中 6. //VS 不支持这样,但是可以使用动态内存函数,实现动态数组 7. int* ptr = (int*)malloc(sizeof(int) * num); 8. if (NULL == ptr) {//进行判断是否创建成功 9. perror("malloc::ptr"); 10. } 11. else { 12. for (int i = 0; i < 10; i++) { 13. *(ptr + i) = i; 14. } 15. for (int i = 0; i < 10; i++) { 16. printf("%d ", *(ptr + i)); 17. } 18. free(ptr); //使用free函数释放动态申请的ptr 19. ptr = NULL; //将ptr free之后,置为NULL,防止野指针非法访问 20. } 21. 22. return 0; 23. }
而且malloc函数创建的空间不会进行初始化,里面存放的是随机值,如图
1.2 calloc
calloc函数也是C语言提供的,用来动态内存分配,原型如下:
calloc函数介绍:
1.函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
实操图文分析:
代码演示:
1. int main() 2. { 3. int num = 0; 4. int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函数 5. for (int i = 0; i < 10; i++) { 6. *(ptr + i) = i; 7. } 8. for (int i = 0; i < 10; i++) { 9. printf("%d ", *(ptr + i)); 10. } 11. free(ptr);//free 动态申请的ptr 12. ptr = NULL;//置为NULL,防止野指针越界访问 13. return 0; 14. 15. }
对于calloc动态申请的空间是否每一个字节都变为0呢?我们来看下图
这也是calloc和malloc函数的最大的区别,是否自动初始化,前者有,后者无
1.3 realloc
realloc也是C语言提供的动态内存申请函数,使得动态内存管理更加灵活。本质是可以对已经动态申请过的空间进行增容,是更加灵活的。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。
函数原型如下,并对两个形参ptr和size进行分析:
如上图:
1.ptr可以为NULL,相当于malloc一个新的空间,ptr是要调整的内存地址
2.size同样可以为0,则返回值取决于特定的库实现:它可能是空指针,也可能是不应取消引用的其他位置。size是调整之后的大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来的数据移动到新空间。
1.3.1 realloc调整内存空间的时候有两种情况:
第一种情况:当原有空间之后的内存空间足够的时候
第二种情况:当原有空间之后的内存空间不够时
如图所示:
因为这两种情况是随机发生的,不能控制必须使用哪一种,所以我们就要小心一个事情,不要用原来动态开辟的变量ptr来直接接收realloc,应该创建临时变量接收,先判空,之后再赋值给ptr
代码图示:
可以自行测试:
1. int main() 2. { 3. int* p = (int*)malloc(sizeof(int)*10); 4. if (p == NULL) { 5. perror("malloc::p"); 6. } 7. else { 8. printf("%p\n", p); 9. } 10. int* ptr = (int*)realloc(p, sizeof(int) * 20);//创建临时变量 11. //如果使用 int* p = (int*)realloc(p,....这样的话如果创建失败,返回NULL, 12. //这样的话p的内容就没有了,所以创建临时变量ptr,然后下面判空之后可以交换 13. if (NULL == ptr) { 14. perror("realloc::ptr"); 15. } 16. else { 17. p = ptr; 18. ptr = NULL; 19. printf("%p\n", p); 20. } 21. 22. free(p); 23. p = NULL; 24. return 0; 25. }
二、常见动态内存错误(案例分析)
2.1 对于NULL指针的解引用操作
意思就是要学会使用动态内存函数的时候吗,要进行判空,不然谁知道有没有问题NULL
1. int main() 2. { 3. int* p = (int*)malloc(sizeof(int) * 10); 4. *p = 10;//这个时候谁知道p是不是NULL,如果是NULL,那么这就是非法访问,是错误 5. free(p); 6. return 0; 7. } 8.
2.2 对动态开辟空间的越界访问
就是说,开辟多少空间就是多少空间,不能越过这个字节数的界限访问空间外的地址
1. int main() 2. { 3. int* p = (int*)malloc(sizeof(int) * 10); 4. if (NULL == p) { 5. perror("malloc::p"); 6. } 7. else { 8. for (int i = 0; i < 100; i++) { 9. *(p + i) = i + 1;//当i等于10的时候就开始越界访问 10. } 11. for (int i = 0; i < 11; i++) { 12. printf("%d ", *(p + i)); 13. } 14. free(p); 15. p = NULL; 16. } 17. return 0; 18. }
和数组一样,不要越界,不需要多想什么额外的东西
2.3 对非动态开辟内存使用free释放
free可以放置NULL进去,不会报错,但是不能放非动态开辟的内存,会报错
图示分析free函数:
代码演示:
1. int main() 2. { 3. int* p = (int*)malloc(sizeof(int) * 10); 4. int a = 10; 5. free(&a);//非动态内存开辟的,会报错 6. //free(NULL); //没有什么反应,程序正常 7. return 0; 8. } 9.
2.4 使用free释放了动态开辟内存的一部分
就是说如果动态开辟内存之后的p指针的位置发生改变的话再去释放free(p)只是释放一部分
代码演示:
1. //举例 2. int main() 3. { 4. int* p = (int*)malloc(sizeof(int) * 10); 5. p++; 6. free(p);//这个的时候p向右移动一个整型字节空间,再进行释放,那么先前那个空间就没被释放 7. return 0; 8. }
2.5 对同一块动态内存进行多次释放
多次释放会报错的
图示:
2.6 动态开辟空间忘记释放(内存泄漏)
所以我们要养成当一个动态空间不用的时候就free他,放置内存泄露
代码演示:
1. int main() 2. { 3. //test(); 4. while (1) { 5. malloc(1);//一直申请就是不释放 6. } 7. }
三、练习题
3.1 第一个
1. void GetMemory(char *p) 2. { 3. p = (char *)malloc(100); 4. } 5. void Test(void) 6. { 7. char *str = NULL; 8. GetMemory(str); 9. //改为传递地址就可以或者就是用str接收 10. //str= GetMemory(str);//实际上用临时变量接收更好 11. strcpy(str, "hello world"); 12. printf(str); 13. //用完释放 14. //free(str); 15. //str=NULL; 16. }
1.传值操作,就算p申请了空间也不会使得str发生改变,所以str依旧是NULL,不能有strcpy
2.内存泄漏, GetMemory(str);未释放p的空间
3.2 第二个
1. char *GetMemory(void) 2. { 3. //修改为: 4. //static char p[] = "hello world"; 5. char p[] = "hello world"; 6. return p; 7. } 8. void Test(void) 9. { 10. char *str = NULL; 11. str = GetMemory(); 12. printf(str); 13. }
典型的返回栈地址问题,p数组是局部变量 ,确实是返回了p的地址给str,但是GetMemory函数结束之后,数组p的空间就没有,再访问p的地址(printf(str))就会非法访问
3.3 第三个
1. void GetMemory(char **p, int num) 2. { 3. *p = (char *)malloc(num); 4. } 5. void Test(void) 6. { 7. char *str = NULL; 8. GetMemory(&str, 100); 9. strcpy(str, "hello"); 10. printf(str); 11. //修改为:free(str); 12. //str=NULL; 13. }
没有释放str动态开辟的空间,没有free(str),str=NULL
3.4 第四个
1. void Test(void) 2. { 3. char *str = (char *) malloc(100); 4. strcpy(str, "hello"); 5. free(str); 6. //修改意见: 7. //str=NULL; 8. if(str != NULL) 9. { 10. strcpy(str, "world"); 11. printf(str); 12. } 13. }
在使用str之前就释放了str申请的空间,释放之后str!=NULL,保留原来地址,str这个时候已经是野指针了(因为没有了对相应空间的访问权限),之后确实是输出了world,但是从if语句就已经错误了,置为str=NULL 就可以了
总结
本文主要是对于malloc、calloc、realloc、free函数的介绍和使用细节的说明,还有一些关于动态内存管理的函数,学会了这些,对于以后数据结构的内容会更加得心应手,所以希望大家能多多支持,接下来,下一章,我们跟大家讲解一下,文件管理的内容。学会了就可以更新通讯录啦!!!