自动变量的潜在问题

关于C语言自动变量的一个基本规则是:声明自动变量的函数已经返回后,不能再引用这些自动变量。

下面就此问题进行测试。
测试环境:
    编译器:GCC 4.2.4
    OS:Ubuntu 8.04 LTS

首先编写一个简单的C程序,暂且命名为test_auto_val.c。 

[cpp] view plain copy print ?
  1. #include    
  2. int*  
  3. func1 ()  
  4. {  
  5.     int *ptr;  
  6.     int val;  
  7.     val = 5;  
  8.     ptr = &val;  
  9.     return ptr;  
  10. }  
  11. int   
  12. main ()   
  13. {  
  14.     int *ptr = func1 ();  
  15.     printf ("%d/n", *ptr);  
  16.     return (0);  
  17. }  
#include int* func1 () { int *ptr; int val; val = 5; ptr = &val; return ptr; } int main () { int *ptr = func1 (); printf ("%d/n", *ptr); return (0); } 

在这个程序中,main()函数里面的ptr指针引用了已经返回的函数func1()中声明的自动变量val。
编译运行这个程序,输出结果为5,看起来似乎并没有出现问题,但是我们的确引用了已经返回的函数中声明的自动变量。

接下来,修改test_auto_val.c的内容,将main()函数修改为: 

[cpp] view plain copy print ?
  1. int   
  2. main ()   
  3. {  
  4.     int *ptr = func1 ();  
  5.     printf ("%d/n", *ptr);  
  6.     printf ("%d/n", *ptr);  
  7.     return (0);  
  8. }  
int main () { int *ptr = func1 (); printf ("%d/n", *ptr); printf ("%d/n", *ptr); return (0); } 

只是增加了一条printf语句,并且与上一行一模一样。在我的系统的本次运行中,得到的结果是:
    5
    -1078398860
问题出现了,为什么两次输出会不一样呢?

程序运行时,系统会为之分配一个栈,而程序中的每个函数在被调用时会占据这个栈的一部分,称为这个函数的栈帧,自动变量的存储空间就分配在函数的栈帧上。

现在来分析这两个程序的运行。

第一个程序运行时,系统为之分配栈空间,然后main()函数被调用,在栈空间里创建自己的栈帧。随后,在main()函数中func1()被调用,func1()也在栈空间创建自己的栈帧。func1()的栈帧在main()的栈帧之下(假设栈顶向低地址扩展),自动变量val分配在func1()的栈帧上。当func1()返回后,逻辑上认为func1()的栈帧已经不存在。但是,事实上在栈空间中,为自动变量val分配的内存单元并没有被改变。因此,当我们在main()函数中使用指针引用其值时,仍然得到了正确的结果。

那修改以后的程序为什么会得到两个不同的值呢?因为printf()函数被调用时也会创建栈帧,而这个栈帧覆盖了func1()的栈帧曾使用过的空间,破坏了自动变量val的内存单元的值。事实上,当我们执行第一条printf语句时,我们将*ptr(注意,这已经是一个int类型的值)作为参数传递给printf()函数,而在进行*ptr这个运算的时候,printf()函数尚未被调用,因此我们得到了正确的值。而执行第二条printf语句时,上一次的printf()函数的调用已经破坏了func1()的栈帧曾使用过的栈空间,因而不能再得到val的值。

下面,再次修改test_auto_val.c来验证以上分析: 

[cpp] view plain copy print ?
  1. #include    
  2. int*  
  3. func1 ()  
  4. {  
  5.     int *ptr;  
  6.     int val;  
  7.     val = 5;  
  8.     ptr = &val;  
  9.     return ptr;  
  10. }  
  11. void   
  12. func2 ()  
  13. {  
  14.     int *ptr;  
  15.     int val;  
  16.     val = 9;  
  17. }  
  18. int   
  19. main ()   
  20. {  
  21.     int *ptr = func1 ();  
  22.     printf ("%d/n", *ptr);  
  23.     func2 ();  
  24.     printf ("%d/n", *ptr);  
  25.     return (0);  
  26. }  
#include int* func1 () { int *ptr; int val; val = 5; ptr = &val; return ptr; } void func2 () { int *ptr; int val; val = 9; } int main () { int *ptr = func1 (); printf ("%d/n", *ptr); func2 (); printf ("%d/n", *ptr); return (0); } 

增加了函数func2(),并且在main()函数中执行第一条printf语句后调用func2(),然后再次输出ptr指向的内存单元的值。在func2()中定义了与func1()相同的变量,目的是以同样的结构覆盖func1()的栈帧。
运行程序,得到如下结果:
    5
    9
从运行结果可以看出func1()的栈帧确实被func2()覆盖了。

前面提到了*ptr作为参数调用printf()函数之时已经是一个int类型的值,也就是说printf()函数被调用前我们已经取出了val内存单元的值,正因为如此,printf()函数才会输出正确的结果。事实上,如果我们用字符串来做这个测试将不能在main()函数中输出字符串的值。同样,可以通过修改test_auto_val.c来验证:

[cpp] view plain copy print ?
  1. #include    
  2. char*  
  3. func1 ()  
  4. {  
  5.     char *ptr;  
  6.     char s[] = "Just for test./n";  
  7.     ptr = s;  
  8.     return ptr;  
  9. }  
  10. int   
  11. main ()   
  12. {  
  13.     char *ptr = func1 ();  
  14.     printf ("%s", ptr);  
  15.     return (0);  
  16. }  
#include char* func1 () { char *ptr; char s[] = "Just for test./n"; ptr = s; return ptr; } int main () { char *ptr = func1 (); printf ("%s", ptr); return (0); } 

运行程序,输出的很有可能是乱码,或者是其它东西,总之不是我们在func1()中定义的字符串。这是因为对字符串的访问是在printf()函数调用以后通过作为参数传入的ptr指针来实现的,而此时func1()的栈帧已经被printf()的栈帧覆盖了。

你可能感兴趣的:(liunx)