首先看一段有问题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>

char * string_copy(char * source_str){
char buffer[120];
strncpy(buffer, source_str, 119);
return buffer;
}

int main(void){
char *destination_str;
char source_str[] = "hello world";
destination_str = string_copy(source_str);
printf("%s\n",destination_str);
return 0;
}

在编译时就给出了警告:

1
2
3
In function 'string_copy':
warning: function returns address of local variable [-Wreturn-local-addr]
return buffer;

buffer是一个自动分配内存的数组,是该函数的局部变量。当控制流离开声明局部变量的范围时,自动变量便自动失效。这就意味着即使返回一个指向局部变量的指针,当函数结束时,由于该变量已被销毁,谁也不知道这个指针所指向的地址的内容是什么。

在C语言中,自动变量在堆栈中分配内存。当包含自动变量的函数或代码块退出时,它们所占用的内存便被回收,它们的内容肯定会被下一个所调用的函数覆盖。这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什么变量,写入了什么内容等。原先自动变量地址的内容可能被立即覆盖,也可能稍后才被覆盖,这就是问题难以被发现的原因。

然而,无论是编译器还是lint程序都无法检测到局部数组返回的所有情况(它有可能通过某一层间接形式存在躲过检查)。

解决这个问题有几种方案:

1.函数可以返回一个常量,或指向常量的指针。
例如:

1
2
3
int func(){
return 0;
}

这是最简单的解决方案,但是如果是其他需要返回变化的内容时,这就无能为力了。

2.使用全局声明的变量。
例如:

1
2
3
4
5
6
7
char my_global_array[120];
char *fun(){
...
my_global_array[i] = ...;
...
return my_global_array;
}

这适用于自己创建字符串的情况,也很简单易用。它的缺点在于任何人都有可能在任何时候修改这个全局数组,而且该函数的下一次调用也会覆盖该数组的内容。

3.使用静态数组。
例如:

1
2
3
4
5
char * func(){
static char buffer[120];
...
return buffer;
}

这就可以防止任何人修改这个数组。只有拥有指向该数组的指针函数(通过参数传递给它)才能修改这个静态数组。但是,该函数的下一次调用将覆盖这个数组的内容,所以调用者必须在此之前使用或备份数组的内容。和全局数组一样,大型缓冲区如果闲置不用是非常浪费内存空间的。

4.显式分配一些内存,保存返回值。
例如:

1
2
3
4
5
char * func(){
char *s = (char *)malloc(120 * sizeof(char));
...
return s;
}

这个方法具有静态数组的优点,而且在每次调用时都创建一个新的缓冲区,所以该函数以后的调用不会覆盖以前的返回值。它适用于多线程的代码(在某一时刻具有超过一个的活动线程的程序)。它的缺点在于程序员必须承担内存管理的责任。根据程序的复杂程度,这项任务可能很容易,也可能很复杂.如果内存仍在使用就释放或者出现“内存泄漏”(不再使用的内存未回收),就会产生bug。人们非常容易忘记释放已分配的内存。

5.最好的解决方案就是要求调用者分配内存来保存函数的返回值。为了提高安全性,调用者应该同时指定缓冲区的大小。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
#include <malloc.h>

void string_copy(char * destination_str,char * source_str,int size){
strncpy(destination_str, source_str, size);
}

int main(void){
char source_str[] = "hello world";
int str_size = 120;
char *destination_str = (char *)malloc(str_size * sizeof(char));
string_copy(destination_str,source_str,str_size);
printf("%s\n",destination_str);
free(destination_str);
return 0;
}

如果可以在同一代码块中同时进行”malloc”和”free”操作,内存管理是最为轻松的。