15

在大多数托管语言(即具有 GC 的语言)中,超出范围的局部变量是不可访问的,并且具有更高的 GC 优先级(因此,它们将首先被释放)。

现在,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,为什么?何时/在什么情况下会释放局部变量?

4

3 回答 3

19

代码示例的实际行为由两个主要因素决定: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通常会为两个指针输出相同的指针值。

于 2012-12-15T00:57:36.067 回答
5

自动对象的生命周期在它被声明的块的末尾结束。

在对象的生命周期之外访问对象是 C 中未定义的行为。

(C99,6.2.4p2)“如果一个对象在其生命周期之外被引用,则行为是不确定的。当指针指向的对象到达其生命周期的末尾时,指针的值变得不确定。”

于 2012-12-15T00:55:28.477 回答
3

局部变量在堆栈上分配。在您考虑 GC 语言或在堆上分配的内存的意义上,它们并没有“释放”。它们只是超出了范围,对于内置类型,代码不会做任何事情——对于对象,析构函数被调用。

超出范围访问它们是未定义的行为。您很幸运,因为还没有其他代码覆盖该内存区域。

于 2012-12-15T00:57:26.343 回答