在编写 c/c++ 程序的时候需要随时提防内存泄漏的问题。而且有时候内存泄漏了却不知道是哪里的问题。这些多是对内存管理不熟悉导致的。

直接开炮,从四道面试题开始

在解答之前先补了一下内存分配的知识。

一个程序通常分为三个区域存储数据。

  • 静态存储区,存储全局变量和static变量,程序退出自动释放内存。
  • 栈存储区,存储临时变量,函数结束自动释放内存。
  • 堆存储区,向系统调用 malloc 和 new 将在这里划分存储空间,需要手动释放。

第一题

:::c
void GetMemory(char *p) 
{ 
    p = (char *)malloc(100); 
} 
void Test(void)   
{ 
    char *str = NULL; 
    GetMemory(str);  
    strcpy(str, "hello world"); 
    printf(str); 
} 

这题的意图是想在子函数里面分配堆空间。但是程序运行却奔溃了。根据预期效果,即使没有写 free 程序也不会奔溃的吧。除非 strcpy 的时候 str 还是NULL。

调试验证了 str 还是NULL 的问题。问题出在调用函数的值传递问题。

在 c/c++ 中 值传递的意思是分配一个栈空间来存储传入子函数的值,函数结束的时候释放其空间。

知道了值传递后我们分析一下程序

  • 首先 *str 指向 NULL
  • 然后执行GetMemory(str),系统就开辟了一个栈 *p 来存储这个NULL(值传递)
  • 然后 p 分配到了一段堆内存
  • 退出子函数, 释放存储 p 地址的栈(内存泄漏,因为指向p的堆内存不可能释放了)。这个时候 str 还是 NULL ,因为 p 和 str 是两个地址完全不同的指针,p 分配的内存跟 str 无关。
  • 运行strcpy 直接奔溃了。
:::c
char *GetMemory(void) 
{  
    char p[] = "hello world"; 
    return p; 
} 
void Test(void) 
{ 
    char *str = NULL; 
    str = GetMemory();   
    printf(str); 
} 
  • p 可视为指向栈内存 hello world 的指针。
  • 返回 p 但是其栈内存已被回收,会输出不确定的内容。
:::c
Void GetMemory2(char **p, int num) 
{ 
    *p = (char *)malloc(num); 
}  
void Test(void) 
{ 
    char *str = NULL; 
    GetMemory(&str, 100); 
    strcpy(str, "hello");   
    printf(str);  
}  

比起第一题好像是改善了,能打印 hello 出来,但是最后忘记 free 了。

:::c
void Test(void) 
{ 
    char *str = (char *) malloc(100);
    strcpy(str, “ hello ” ); 
    free(str);      
    if(str != NULL) 
    { 
        strcpy(str, " world " );  
        printf(str); 
    } 
}

free(str) 后没有运行 str = NULL ,所以会执行 strcpy(str, "world") ,结果是奔溃了。

最后来个开源项目的例子。

:::c
char *get_prefsdir(void)
{
        static char prefs_path [PATH_MAX];
        static int prefs_path_init = 0;
        char *homedir;

        if (prefs_path_init) {
                return prefs_path;
        }
        homedir = get_homedir();
        snprintf(prefs_path, sizeof (prefs_path), "%s/.alsaplayer", homedir);
        prefs_path_init = 1;
        return prefs_path;
}
  • 程序在静态存储区开辟了大小为 PATH_MAX 的存储空间
  • 经过一系列操作后把该静态存储区的地址回传。由于字符串存储在静态存储区,除非程序退出,内存不会被回收。
  • 还有就是静态存储区默认清零,数组不需要清零的步骤。不需要担心释放的问题。