用代码说服同学C语言不要返回局部变量地址的辩论过程
事情缘于这一段代码
#include <stdio.h>
char * funa()
{
char arr[] = "Hello\n";
return arr; //直接返回一个数组,数组名当地址使用。
}
char * funb()
{
char arr[] = "Hello\n";
char *p = arr; //把数组的地址保存在一个指针中。
return p; //返回指针。
}
int main(void)
{
printf("%s", funb()); //输出Hello,编译器无报警
printf("%s", funa()); //输出null,编译器警告错误
return 0;
}
同学无法理解为什么同样是返回了地址,且编译器没有任何警告,一个会输出null且一个会输出正常数值。
我的理解是:
编译器能分清楚通过数组名返回的是地址,所以给出了警告。
但是通过返回指针变量的方式返回地址,编译器无法分清你返回的是一个变量还是地址,返回的地址是否安全,无法做出预测处理所以正常通过编译
并且在和同学的讨论结束后,用gdb调试了一下这段不安全的代码。发现编译器应该是出于安全考虑,对返回数组的操作都统一返回了0x0的空地址。所以如果对返回值取字符串会返回null。如果返回值当整数数组处理。要对地址做解引用。即尝试对空地址解引用。因为不是输出null而是输出段错误程序崩溃。
Breakpoint 1, main () at arr.c:17
17 printf("%s", funb());
(gdb) n
Hello
18 printf("%s", funa());
(gdb) n
20 return 0;
(gdb) p funa()
$1 = 0x0
(gdb) p funb()
$2 = 0x7fffffffe2e1 "Hello\n"
(gdb)
同学认为既然能正确返回,那就能用这种方法在函数中创建一个局部变量的数组并返回。写了一段代码重复输出yes的代码,认为数据是安全的。即使我不断的循环也没有改变数值,编译器会保护数据不被覆盖。
#include <stdio.h>
int * fun(void)
{
int arr[3] = {1, 2, 3};
int *p = arr;
return p;
}
int main(void)
{
int i;
int * d = &fun()[1];
for (i = 0; i < 100; i++)
{
if (*d == 2)
{
int j[100] = {1};
printf("YES");
}
//保存函数回返的地址。
//在循环中不断创建变量,比较和解引用证明数据是安全的。
}
return 0;
}
我认为只是没有足够的数值刚好覆盖到变量地址,并且编译器可能做了优化数组没有覆盖到数据并写了如下代码
#include <stdio.h>
int * func();
int * funca();
int main(void)
{
int * p = func();
printf("func return address:\t%p\nfunc return data:\t%d\n", p, *p);
int *pp = funca();
printf("funca return address:\t%p\nfunc return data:\t%d\n", pp, *pp);
printf("func return address:\t%p\nfunc return data:\t%d\n", p, *p);
return 0;
}
int * func()
{
int abc[2]= {1, 2};
int *p = abc;
return p;
}
int * funca()
{
int abc[2]= {2, 1};
int *p = abc;
return p;
}
程序运行结果:
func return address: 0x7ffe968f2a70
func return data: 1
funca return address: 0x7ffe968f2a70
func return data: 2
func return address: 0x7ffe968f2a70
func return data: 2
可以直观看出func和funca两个不同的函数返回同一个地址,后者会覆盖掉前者的数据,不安全的返回局部变量很容易被未知操作覆盖掉数据。