注意
malloc、calloc、realloc申请内存空间时前面都要加强转
常见的动态内存开辟错误
(1)对NULL指针进行解引用
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. int main() 5. { 6. int* p = malloc(1000000000000000000000); 7. *p = 26; 8. printf("%d", *p);//error 对指向动态开辟内存的指针解引用时没有判空,造成直接对NULL指针解引用 9. free(NULL); 10. return 0; 11. }
报错:
(2)对动态开辟空间的越界访问
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. int main() 5. { 6. int* p = (int*)malloc(10 * sizeof(int)); 7. 8. if (p == NULL) 9. { 10. return 1; 11. } 12. 13. int i = 0; 14. 15. //越界访问,会导致程序异常 16. for (i = 0; i < 40; i++) 17. { 18. *(p + i) = i; 19. } 20. 21. free(p); 22. p = NULL; 23. 24. return 0; 25. }
异常:
(3)对非动态开辟内存使用free释放
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. int main() 5. { 6. int arr[10] = { 0 };//栈区 7. int* p = arr; 8. 9. //使用 10. 11. free(p);//使用free释放非动态开辟的空间 12. p = NULL; 13. 14. return 0; 15. }
报错:堆崩溃
(4)用free释放动态开辟内存的一部分
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. int main() 4. { 5. int* p = malloc(10 * sizeof(int)); 6. 7. if (p == NULL) 8. { 9. return 1; 10. } 11. int i = 0; 12. 13. for (i = 0; i < 5; i++)//指针走到申请的内存空间的中途停下了 14. { 15. *p++ = i; 16. } 17. 18. free(p);//释放时,系统不知道p指向的起始位置 19. p = NULL; 20. 21. return 0; 22. }
报错:堆崩溃
(5)对同一块动态内存多次释放
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. 5. int main() 6. { 7. int* p = (int*)malloc(100); 8. //使用 9. //释放 10. free(p); 11. 12. //释放 13. free(p);//重复释放 14. 15. return 0; 16. }
代码未编译之前会提示重复释放:使用未初始化的内存"p"
报错:堆崩溃
(6)动态开辟内存忘记释放(会造成内存泄漏)
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. void test() 4. { 5. int* p = (int*)malloc(100); 6. if (p != NULL) 7. { 8. *p = 20; 9. } 10. 11. //忘记释放 12. } 13. 14. int main() 15. { 16. test(); 17. return 0; 18. }
代码运行没有问题,但是,尽管动态开辟的空间有两种回收方式:主动free和程序结束自动释放,但是如果忘记主动free可能会造成内存泄漏,带来严重后果。
动态内存开辟经典笔试题
(1)返回空指针+未主动free
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. #include<string.h> 5. 6. void* GetMemory(char* p) 7. { 8. p = (char*)malloc(100); 9. } 10. 11. void test() 12. { 13. char* str = NULL; 14. GetMemory(str); 15. strcpy(str, "hello world"); 16. printf(str); 17. } 18. int main() 19. { 20. test(); 21. return 0; 22. }
GetMemory传递的是str,str是变量名,是值传递,值传递实参不可改变,由于并没有传递&str,形参p是str的一份临时拷贝,p存放申请的100个字节空间的起始地址,等函数调用完毕后,p被销毁,地址没有传给str,str还是空指针,导致strcpy拷贝失败,GetMemory调用完毕后,p消失了,malloc申请的100个字节地地址就再也找不到了,会造成100个字节的内存泄漏,无法释放。并且也无法打印,因为str是空指针。
有两种修改方式:
第一种:GetMemory函数返回p,即p动态申请的内存空间的起始地址;主动free释放申请的空间
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. #include<string.h> 5. 6. char* GetMemory(char* p) //返回类型改为char* 7. { 8. p = (char*)malloc(100); 9. return p;//返回申请的内存空间的起始地址 10. } 11. 12. void test() 13. { 14. char* str = NULL; 15. str = GetMemory(str);//str指针接收申请的内存空间的起始地址 16. strcpy(str, "hello world"); 17. printf(str); 18. free(str);//主动free 19. str = NULL;//free后将指针置NULL 20. } 21. int main() 22. { 23. test(); 24. return 0; 25. }
运行结果
第二种:将malloc开辟的空间的起始地址存放到*p也就是str中,由于返回的空间是堆上的空间,GetMemory函数调用结束后,虽然栈的临时空间被销毁了,但是堆不受影响,因此不需要返回值。不过这时需要用传址的方式传参(&str),相应地,GetMemory的形参也应为二级指针char **p。另外还需要主动free。
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. #include<string.h> 5. 6. void GetMemory(char** p) //相应地形参类型改为char** 7. { 8. *p = (char*)malloc(100); 9. //由于申请的空间在堆上,所以传址的方式在GetMemory函数调用完毕后无需返回值,因此函数返回类型为void 10. } 11. 12. void test() 13. { 14. char* str = NULL; 15. GetMemory(&str);//传址的方式传参 16. strcpy(str, "hello world"); 17. printf(str); 18. free(str);//主动free 19. str = NULL;//free后将指针置NULL 20. } 21. int main() 22. { 23. test(); 24. return 0; 25. }
(2)返回栈上的地址
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. char* GetMemory() 4. { 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. } 14. int main() 15. { 16. Test(); 17. return 0; 18. }
打印结果为
这是由于 GetMemory函数中创建的数组p是在栈上创建的,GetMemory调用结束后,数组p的空间就换给了操作系统,因此返回的地址是没有实际意义的,如果通过返回的地址,去访问内存,就是非法访问内存,所以打印结果是乱码。
修改:在堆上使用动态内存开辟空间,GetMemory需要传参,是因为要free动态内存开辟的空间,这里如果将GetMemory设计成无参数函数,那么就无法free动态内存开辟的空间了(不能在GetMemory函数结尾free,如果free,那么动态申请的内存就会被释放,后面访问str指向的内存空间就是非法访问)
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. char* GetMemory(char* p) 5. { 6. p = (char*)malloc(100);//在堆上使用动态内存开辟空间 7. p = "hello world"; 8. return p; 9. } 10. void Test(void) 11. { 12. char* str = NULL; 13. str = GetMemory(str);//GetMemory需要传参 14. printf(str); 15. free(str);//释放动态内存开辟的空间 16. str = NULL; 17. } 18. int main() 19. { 20. Test(); 21. return 0; 22. } 23.
运行结果如下:
(3)未主动free
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. void GetMemory(char** p, int num) 5. { 6. *p = (char*)malloc(num);//在堆上创建数组p 7. } 8. void Test(void) 9. { 10. char* str = NULL; 11. GetMemory(&str, 100); 12. strcpy(str, "hello"); 13. printf(str); 14. } 15. int main() 16. { 17. Test(); 18. return 0; 19. }
虽然程序正常运行不会报错,但是没有主动free,会造成内存泄漏。
修改:需要free。
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<stdio.h> 3. #include<stdlib.h> 4. void GetMemory(char** p, int num) 5. { 6. *p = (char*)malloc(num); 7. } 8. void Test(void) 9. { 10. char* str = NULL; 11. GetMemory(&str, 100); 12. strcpy(str, "hello"); 13. printf(str); 14. free(str);//释放动态内存分配申请的空间 15. str = NULL; 16. } 17. int main() 18. { 19. Test(); 20. return 0; 21. }