当一个变量超出作用域时会发生什么?

16
在大多数托管语言(也就是带有垃圾收集器的语言)中,超出作用域的本地变量将无法访问,并且具有更高的垃圾收集优先级(因此,它们将首先被释放)。由于C不是托管语言,那么在这里超出作用域的变量会发生什么呢?我在C语言中创建了一个小型的测试案例:
#include <stdio.h>
int main(void){
    int *ptr;

    {
        // New scope
        int tmp = 17;
        ptr = &tmp; // Just to see if the memory is cleared
    }

    //printf("tmp = %d", tmp); // Compile-time error (as expected)
    printf("ptr = %d\n", *ptr);

    return 0;
}

我正在使用GCC 4.7.3进行编译,上面的程序输出了17,这是为什么?在什么情况下会释放局部变量?


截至今日,gcc 4.7.3尚未发布。这必须是一个4.7.3预发布版。 - ouah
3个回答

20

您的代码示例的实际行为由两个主要因素决定:1)该行为在语言中是未定义的,2)优化编译器将生成的机器代码与您的C代码不会完全匹配。

例如,尽管该行为是未定义的,但GCC可以(并且将会)轻松地将您的代码优化为仅仅是

printf("ptr = %d\n", 17);
这意味着你看到的输出结果与代码中任何变量发生的情况几乎没有关系。如果您希望代码行为更好地反映物理上发生的情况,应该将指针声明为volatile。行为仍将是未定义的,但至少它将限制一些优化。现在,当局部变量超出作用域时会发生什么?物理上不会发生任何事情。典型的实现会在程序堆栈中分配足够的空间来存储当前函数中块嵌套的最深层次处的所有变量。这个空间通常在函数启动时一次性分配在堆栈中,并在函数退出时释放回去。这意味着先前由tmp占用的内存仍然保留在堆栈中,直到函数退出。这也意味着相同的堆栈空间可以(并且将)被具有大致相同“局部深度”级别的兄弟块中的不同变量重用。该空间将保存最后一个变量的值,直到某个兄弟块中声明的其他变量覆盖它。在您的例子中,没有人覆盖tmp以前占用的空间,因此您通常会在该内存中看到值17完好无损地存活下来。但是,如果您这样做
int main(void) {
  volatile int *ptr;
  volatile int *ptrd;

  { // Block
    int tmp = 17;
    ptr = &tmp; // Just to see if the memory is cleared
  }

  { // Sibling block
    int d = 5;
    ptrd = &d;
  }

  printf("ptr = %d %d\n", *ptr, *ptrd);
  printf("%p %p\n", ptr, ptrd);
}

你会发现原来被 tmp 占用的空间已经被重复利用,用于 d 并且它之前的值已被覆盖。第二个 printf 通常会输出两个指针的相同指针值。


5
自动对象的生命周期结束于声明它的块的末尾。
在C语言中,在对象生命周期之外访问该对象是未定义的行为。
引用C99标准(6.2.4p2):“如果一个对象在其生命周期之外被引用,则其行为是未定义的。当指向的对象到达生命周期结尾时,指针的值变得不确定。”

4

本地变量分配在堆栈上。它们不像GC语言或堆上分配的内存那样被“释放”。它们只是超出了作用域,对于内置类型,代码不会执行任何操作,而对于对象,则调用析构函数。

超出其作用域访问它们是未定义行为。你很幸运,因为没有其他代码覆盖那个内存区域...但这并不代表以后不会发生。


如果在C++中的对象具有一个启动线程并超出作用域的函数,会发生什么? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接