前言:
在实际项目中,经常会遇到一些需要多次重复进行某种动态内存管理的情形。
例如,为了尽可能地解耦、尽可能少地使用全局变量、或者尽可能高效地维护某一类相似的内存管理操作代码,可能有需要通过函数调用的方式分配内存,并将分配的内存返回给发起调用的函数的情形。
如果要在函数调用中进行模块内存的释放,可以使用如下方式:
#include <string.h> unsigned char my_free(int* addr);//method to free a block of memory int main(void *pData) { int *addr = (int*)malloc(10); //.... some process if(my_free(addr)) { printf("Error occured"); } } unsigned char my_free(int* addr) { if(!addr) { free(addr); return 1; } return 0; }
但如果认为可以类似地,采用类比上面(即如下)这种方式进行分配,则大错特错了!
unsigned char my_alloc(int* addr, int size) { int *tmp_addr = (void*)0; tmp_addr = (int*)malloc(size); if(!tmp_addr) { addr = (VOID*)0; return 0; } addr = tmp_addr; return 0; } int main(void* pData) { int* addr = (void*)0; if(my_alloc(addr, 10)) { printf("my_alloc() method ok"); } return 0; }
分析:
1. 实际上在上述代码中,方法调用 my_alloc(addr, 10) 中,addr 传入的是一个在调用外申明的地址,该地址指向/存放的地址为空(即(void*(0));
2. 在调用该函数 my_alloc(addr, 10) 时,函数在开辟该函数运行的栈空间 stack_my_alloc_ 时,就会将 addr 的值也即其地址(而非其所存储的值/所指向的地址)进行复制,接着在 进行 addr = tmp_addr 的赋值时,将addr的副本变量的存放地址从原来的 &addr 直接修改为了tmp_addr所指向的地址,而非将 addr 的值修改为tmp_addr所指地址;
3. 上述行为也导致了这块已经开辟好的内存空间的指针,被赋予一个该子函数栈空间中的一个变量(存在无法释放的可能);
4. 在退出该函数时,原来addr 存放的地址仍然是 &addr, 其所指向地址仍然是 (void*)0, 而那个副本变量在退出调用时已经不复存在,因此这块被申请的内存永远得不到释放(部分做了深度优化的编译器可以检查并在退出时进行free的情况除外) 。
结论:
这种错误的写法,有两个后果:一,达不到想要的malloc目的; 二,导致内存耗尽(被吞噬、泄露),造成不断死机。
正确姿势:
从上述分析可以看出,显然在函数退出调用时,应将 tmp_addr 所指向的内存地址 存入 addr 中,而不是将 &addr 的副本变量(仅存活于该调用栈空间中)由原来在栈空间中的地址(绝对不等于&addr)重新指向tmp_addr。
因此,在不利用全局变量的情况下,必须使用指向指针的指针来进行调用,这个动态分配内存的子函数就应该写成:
unsigned char my_alloc(int** addr, int size) { int *tmp_addr = (void*)0; tmp_addr = (int*)malloc(size); if(!tmp_addr) { *addr = (VOID*)0; return 0; } *addr = tmp_addr; return 0; }
调用方式为:
int main(void* pData) { int* addr = (void*)0; if(my_alloc(&addr, 10)) { printf("my_alloc() method ok"); } //do something with addr return 0; }