当变量超出范围时会发生什么?

Luk*_*uth 14 c memory-management

在大多数托管语言(即具有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;
}
Run Code Online (Sandbox Code Playgroud)

我正在使用GCC 4.7.3编译并打印上面的程序17,为什么?何时/在什么情况下会释放局部变量?

AnT*_*AnT 16

代码示例的实际行为由两个主要因素决定:1)语言未定义行为,2)优化编译器将生成与您的C代码物理匹配的机器代码.

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

printf("ptr = %d\n", 17);
Run Code Online (Sandbox Code Playgroud)

这意味着您看到的输出与代码中的任何变量发生的情况几乎没有关系.

如果您希望代码的行为更好地反映物理上发生的事情,那么您应该声明您的指针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);
}
Run Code Online (Sandbox Code Playgroud)

你会看到以前被占用的空间tmp已被重复使用,d并且它的前值已被覆盖.第二个printf通常会为两个指针输出相同的指针值.


oua*_*uah 5

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

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

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