- 前言
- 1.
- 结果分析
- 改正或改进
- 2.
- 结果分析
- 改进或改正
- 3.
- 结果分析
- 4.
- 结果分析
- 改进或改正
- 结语
本节主要分析几个例题,以此来稍微加深对动态内存管理的认识。
1.
#include结果分析#include void GetMemory(char* p){ p = (char*)malloc(100); } void Test(void){ char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }
main()函数调用Test()函数,之后Test()函数调用GetMemory()函数。
字符指针str传入了函数GetMemory(),GetMemory()函数的参数也是字符指针,故字符指针str实际上是传值调用,传的是str的值。相当于在内存的栈区创建了以个字符指针p接收了字符指针str的值NULL。
在GetMemoey函数内部,指针p指向了malloc()函数开辟的100个字节的空间的起始位置。p中存放的是该内存空间的起始地址。之后GetMemory函数调用结束,并且没有返回值,指针p由于是在GetMemory()函数内部,是局部变量,所以p其所在的内存块就随着函数调用结束而销毁了。此时p所指向的空间还未被释放free(),并且由于函数GetMemory()调用已经结束了没有任何一个指针指向这块开辟的空间,属于内存泄漏的情况。
改正或改进回到Test()函数内部继续执行程序,此时由于指针p是指针str的一份临时拷贝,指针p发生什么都不会影响指针str的值,故字符指针str的值还是NULL。接着函数strcpy()执行操作:把字符串"hello world"拷贝到指针str所指向的空间。但是str的值是NULL,指针str就是一个空指针,没有明确的指向,拷贝会发生错误导致程序停止。
#include#include //返回动态开辟的空间的起始地址 char* GetMemory(){ char* p = (char*)malloc(100); return p; } void Test(void){ char* str = NULL; str = GetMemory(); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; }
运行结果:
2.
#include#include char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
运行结果:
main()函数调用Test()函数,之后Test()函数调用GetMemory()函数。
在GetMemory()函数内部,p是局部字符数组名,该数组存放了一个字符串"hello world",GetMemory()函数返回了局部字符数组名,也就是数组首元素的地址。
改进或改正在GetMemory函数结束调用之后其内部所有数据都被销毁,包括局部字符数组,其所在内存区域被操作系统回收了,Test()函数内字符指针str接受的是已经被回收内存区域的地址,故str是野指针,指向了不属于本程序的已经被回收的内存区域。此时这个内存区域存放的是什么我们并不知道,输出的内容当然也是未知的。
定义函数时可以返回局部变量的值,这个值可以被外部变量接收;不要返回局部变量的地址,这没有意义。还会导致接受这个地址的指针成为野指针。
#includeint* test() { int a = 20; return &a; } int main() { int* p = test(); //printf("%dn", 10); printf("%dn", *p); return 0; }
运行结果:
#includeint* test() { int a = 20; return &a; } int main() { int* p = test(); printf("%dn", 10); printf("%dn", *p); return 0; }
运行结果:
每一次调用函数都会开辟函数栈帧,函数调用完成返回后栈帧被操作系统回收。回收之后,开辟且已经被回收的空间里存放的值是否还是原来的值是未知的,如果又开辟了新的函数栈帧,那么会覆盖原来的已被回收的(旧的)函数栈帧。
3.
#include#include void GetMemory(char** p, int num){ *p = (char*)malloc(num); } void Test(void){ char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } int main() { Test(); return 0; }
运行结果:
main()函数调用Test()函数,之后Test()函数调用GetMemory()函数。
Test()函数中字符指针str的地址和整型100传入了GetMemory()函数。GetMemory()函数分别形参用二级指针p和整型num正确接收了。之后在GetMemory()函数内部,二级指针p解引用一次找到了Test()函数内的一级指针str,相当于一级指针str接收malloc()分配的内存空间的起始地址。之后GetMemory()函数调用结束回到Test()函数。
4.指针str中存放的是动态开辟的内存的起始地址。strcpy()函数把字符串"hello"拷贝到指针str所指向的空间。printf()函数打印指针str所指向的字符串。之后Text()函数调用结束并返回到main()。之后main()函数执行结束并返回0,程序结束。
#include结果分析#include void Test(void){ char* str = (char*)malloc(100); strcpy(str, "hello"); //free之后直接没有把指针str置为空指针,str就是野指针。 //直接使用野指针会出问题。 free(str); if (str != NULL){ strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; }
main()函数调用Text()函数。
Test()函数内部,指针str指向了malloc()分配的内存空间的起始地址。但是也会有malloc()分配内存失败的情况,此时str存放的是NULL。应该要有对str是否是NULL的检查。
strcpy()函数把字符串"hello"拷贝复制到指针str所指向的内存空间,之后free()函数释放掉指针str所指向的动态分配的内存空间。注意之后的指针str没有被置为NULL,指针str还是指向了已经被释放掉的那片内存区域的起始地址。这时指针str就是一个野指针。很危险。
改进或改正if判断str是否是空指针,我们已经直到str不是空指针,指向了已经不属于本程序的内存的地址。判断成立执行以下操作:strcpy()函数将会把字符串"world"拷贝复制到指针str所指向的空间,这个操作属于非法访问内存,程序虽然还能够把字符串输出到控制台,但这个程序也是有着问题的。
#include#include void Test(void){ char* str = (char*)malloc(100); strcpy(str, "hello"); // free(str); str = NULL; if (str != NULL){ strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; }
结语
有关动态内存管理中代码可能会出现显而易见的错误,这当然很好改正。比如对接收malloc()、calloc()、realloc()的空间的起始地址的指针进行有效性检查、释放指针所指向的动态开辟的内存空间后及时把指针置为NULL等。也有写逻辑上的漏洞并不好看出来,需要我们认真思考和寻找。
END



