关于C语言自动变量的一个基本规则是:声明自动变量的函数已经返回后,不能再引用这些自动变量。
下面就此问题进行测试。
测试环境:
编译器:GCC 4.2.4
OS:Ubuntu 8.04 LTS
首先编写一个简单的C程序,暂且命名为test_auto_val.c。
- #include <stdio.h>
- 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()函数修改为:
- 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来验证以上分析:
- #include <stdio.h>
- 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来验证:
- #include <stdio.h>
- 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()的栈帧覆盖了。