【问题标题】:Do automatic variables have lifetime equal to that of a static variable (within the same block)?自动变量的生命周期是否等于静态变量的生命周期(在同一块内)?
【发布时间】:2019-12-23 05:51:42
【问题描述】:

我知道问这个问题很愚蠢,但请稍等一下。

我知道什么?

  • 变量x 的范围基本上是定义x 的块。如果x 属于 那么自动存储类,只能在声明它的块内使用(正好是x的范围)。

  • 变量x 的生命周期基本上是允许x 存在于内存中的时间段。如果x 是一个自动变量,那么默认情况下,它会在其作用域执行后立即被杀死。

那么,我为什么要问这个问题? 是因为我不小心想到了下面这段代码。

#include<stdio.h>
void scratch(void);
int main(void)
{
        for(int i = 0; i < 5; i++)
                scratch();
        return 0;
}
void scratch(void)
{
        static int static_var = 0;
        int auto_var1, auto_var2 = 0;
        printf("[info] 'static' variable = %d | 'auto' variable 1 = %d | 'auto' variable 2 = %d\n", static_var++, auto_var1++, auto_var2);
}

输出:

[info] 'static' variable = 0 | 'auto' variable 1 = 0 | 'auto' variable 2 = 0
[info] 'static' variable = 1 | 'auto' variable 1 = 1 | 'auto' variable 2 = 0
[info] 'static' variable = 2 | 'auto' variable 1 = 2 | 'auto' variable 2 = 0
[info] 'static' variable = 3 | 'auto' variable 1 = 3 | 'auto' variable 2 = 0
[info] 'static' variable = 4 | 'auto' variable 1 = 4 | 'auto' variable 2 = 0

静态变量static_var 做得很好。但是,请注意两个自动变量的行为:auto_var1auto_var2。根据我对自动变量的理解,auto_var1auto_var2 在每次迭代中都应该打印0,因为它们的生命周期应该在scratch() 返回main() 的那一刻结束。但看起来这些家伙的寿命很长。所以我的问题是为什么?这是否意味着自动变量的生命周期也等于同一块中静态变量的生命周期?还是基于编译器的问题?

注意:以上代码是在 Ubuntu 17 上通过gcc version 7.2.0 编译的。另外,请尝试回答此问题的每个人:请坚持C 回答。

【问题讨论】:

  • 未定义的行为。
  • 如果静态变量在退出函数后被销毁,那么下次调用函数时如何保留其值?
  • @phuclv,对于静态变量,我相信,只要程序存在,它们就会存在。但是,它们只能在其范围内使用。而且因为他们的寿命很长,所以他们能够在他们的范围之外保持他们的价值观。
  • 未初始化变量auto_var1的值是不确定的,使用不确定的值会导致C中的未定义行为。

标签: c


【解决方案1】:

自动变量的生命周期是否与静态变量的生命周期相同 变量(在同一块内)?

简短回答 - 不,具有自动存储持续时间的对象仅限于声明它的块。具有静态存储持续时间的对象在程序的生命周期内保持有效。见C11 Standard - 6.2.4 Storage durations of objects

现在在同一个块中,从任一对象的角度来看,两者在块的生命周期内都保持有效。具有自动存储时长的将在离开块后停止访问,而具有静态存储时长的则可以在其他地方访问。

【讨论】:

  • 考虑到自动变量的范围也是它的生命周期这一事实,如何描述“auto_var1”在其生命周期之外保留其值的能力?还是应该像@jxh 提到的那样简单地将其视为未定义的行为?
  • auto_var1 永远不能在其生命周期之外保留价值以合法的方式访问它。内存被释放以供重用。它是否已经被覆盖是无关紧要的——因为这个原因,你不能在它的生命周期之外访问一个变量——不能保证——调用Undefined Behavior"If an object is referred to outside of its lifetime, the behavior is undefined." (6.2.4(p2))
  • 好的。现在我懂了。之所以auto_var1 能够在它的生命周期之后保留它的价值,只是因为它的内存位置没有进行覆盖。所以,这给我带来了一个问题,每次调用scratch() 时,auto_var1 不应该被重新分配新的内存位置吗?为什么要使用相同的内存位置重新分配它?
  • 是的!宾果游戏...价值仍然存在——但你不能合法地访问它——不能保证它会在那里。因此,我们将超出范围的每个变量都视为不可触及...
  • @Argon - 您缺少的最后一块拼图是每次调用 scratch() 时,都会创建一个单独的 Function Stack,并声明所有局部变量在那块内存中。当函数返回时,函数堆栈被释放以供重用。所以对scratch() 的每次调用都是一个新的、独立的函数堆栈设置(现在这是否意味着编译器不能足够聪明地以某种方式优化它以最小化系统资源——当然可以——但是编译器如何以及是否优化一些调用的各个方面不是标准允许您依赖的。)
【解决方案2】:

第一个变量没有初始化,你在那里打印垃圾。

int auto_var1, auto_var2 = 0;

更改此项以初始化两者,然后它们都将打印 0。

int auto_var1 = 0, auto_var2 = 0;

【讨论】:

  • 同意。但是,您能说一下他们的一生吗?
【解决方案3】:

其他答案告诉你根据 C 标准你应该做什么,我会解释为什么它碰巧可以这样工作。

为了跟踪函数应该返回的位置以及它们的自动变量的值,大多数平台都定义了一个名为the stack 的数据结构,它可以在一端增大或缩小。当一个函数f 调用另一个函数g 时,首先将返回地址压入堆栈,然后在堆栈上创建g 的自动变量。当g返回时,它会释放其自动变量,并从堆栈中弹出返回地址并跳转到它,将堆栈返回到函数调用之前的状态。

在您的scratch 函数中,auto_var1 未初始化,因此在创建变量时,不会向其中写入任何内容;它包含内存中那个地方发生的任何事情。第一次调用scratch,恰好包含零,所以递增为1,然后变量被销毁。然而,在随后的调用中,堆栈布局与第一次调用 scratch 时完全相同,因此 auto_var1 被分配在与之前相同的位置。由于该变量仍未初始化,因此该值再次递增,并且该变量看起来就像是静态的一样。

现在,这种方式只能靠运气:这是因为每次调用中的堆栈布局完全相同。例如,如果您有一个电话 mainscratch 然后 mainfunc → scratch,情况将不再如此。甚至上面的解释还是有些简化的;例如,编译器可能选择将变量保留在CPU register 中而不是堆栈中,并在函数调用之间重用寄存器,因此即使保持调用图相同也不能保证它以这种方式工作。

除此之外,根据 C 标准,代码仍然表现出未定义的行为。编译器不能保证在不同情况下保持这种行为,并且不能移植到不同的架构;在某些(例如 Itanium)上,代码可能会彻底崩溃。未初始化的内存甚至不能保证是“稳定的”:多次读取同一个地址可能每次都会产生不同的值。

故事的寓意:在阅读变量之前对其进行初始化。

【讨论】:

  • 感谢您将此视角添加到图片中。帮助我塑造了我的整个思维过程。
【解决方案4】:

auto_var1 的值显示了未定义的行为,变量似乎在函数调用之间保持其值并递增,就像静态变量一样。

如果你在 Intel 上运行,那么这可以解释如下:

for 循环不会更改堆栈,并且每次调用函数scratch() 时都会以相同的方式通过推送返回地址、保存寄存器然后递增堆栈指针来修改堆栈以创建局部变量 int auto_var1auto_var2 的空间。第二个初始化为零,第一个未初始化,因此该内存位置(变量)中的值与上一个函数调用中的值相同。内存位置递增,函数返回。下一次迭代一次又一次地递增它。

唯一需要注意的一点是,在第一次迭代中,内存位置以某种方式为零。这是巧合。

【讨论】:

    猜你喜欢
    • 2011-09-12
    • 1970-01-01
    • 1970-01-01
    • 2013-07-27
    • 2011-04-18
    • 2016-07-20
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多