【问题标题】:How can I determine whether a variable has a scope and lifetime or not?如何确定变量是否具有范围和生命周期?
【发布时间】:2025-12-25 09:25:10
【问题描述】:

我了解范围和生命周期是什么以及它们有何不同:

范围:变量的可见性,即哪些代码块可以引用该变量

生命周期:变量的值将在内存中保留多长时间

我的问题是:in the picture below,我们基于什么决定变量 x 有生命周期而不是作用域,而变量 b1 有作用域但没有生命周期?

【问题讨论】:

  • 你不应该问这样一个关于 Java 和 C 的问题。用一个语言标签创建单独的问题。
  • 所有变量总是有一个生命周期和一个范围(和一个类型,...)。
  • 乔纳森怎么说。这个问题没有任何意义。你从哪里弄来的这张垃圾照片?
  • 那张图片的来源是什么?

标签: java c scope lifetime lifetime-scoping


【解决方案1】:

在 C 示例中,static 关键字将变量 x 保存在内存中以供将来的函数调用使用。但这并不意味着您可以从该函数外部访问x(就像它试图在main 中所做的那样)。所以你在内存中有一个变量(生命周期),但不能从函数外部访问它(没有范围)

在 java 示例中,声明了一个对象引用,但从未创建任何对象。所以你可以访问引用(你有范围)但内存中没有对象(没有生命周期)

【讨论】:

  • 在 Java 中,Book b1; 创建一个引用而不是 b1 类型的对象呢?而且,如果它确实创建了一个引用,为什么我们不说该引用具有生命周期,即使它还没有与它所引用的对象相关联?引用本身不是在某个时间创建的实体,它与它所引用的对象具有某些值/关联,然后被销毁吗?
  • null 的引用被推送到局部变量数组是对的。局部变量数组与方法执行具有相同的生命周期,因此空引用确实可能被认为具有“生命周期”。实际对象转而进入堆,并且它们具有可变的生命周期。所以如果你这样说,那么很难知道在哪里画线,特别是如果你认为原始类型有生命周期(因为它们也存在于局部变量数组中)
  • 但同样...null 既不是对象也不是类型。这是一个特殊的常数。所以我不会说它有正式的生命周期,因为它根本不是一个变量
  • 在这种情况下,null“引用”几乎由一个局部变量表条目组成,其中包含变量名称、索引、长度等,以及局部变量数组中的一个常量。所有这些都将被硬编码到 jvm 字节码中
  • tl;博士,我确信该图像的作者没有考虑到所有这些信息。因为你可能不应该大声笑
【解决方案2】:

[该问题同时使用 Java 和 C 进行标记。此答案针对 C。此处的信息取自 C 2011 标准的 N1570 草案。]

一个变量由一个标识符(它被知道的名字)和一个对象(在内存中保存它的值的存储器)组成。

标识符总是有一定的范围,而对象总是有一定的生命周期。 (使用malloc分配内存时,存储有生命周期,但没有标识符,因此没有名称范围。)

对于一个变量,它的标识符的作用域是由它在源代码中的声明位置决定的?

  • 如果声明在任何块之外('{' 和 '}' 内的一系列语句和声明),它具有文件范围,并且标识符从声明到末尾都是可见的翻译单元(预处理完成后的源代码)。
  • 如果声明位于块内或函数定义的参数声明内(不仅仅是声明),则它具有块范围,并且标识符从其声明到结尾都是可见的块。
  • 如果声明位于不是定义的函数声明的参数声明中,则它具有函数原型范围,并且从其声明到函数声明符的末尾都是可见的。李>

除了变量标识符之外,还有其他标识符。函数标识符规则;结构、联合和枚举的标签;和 typedef 名称与变量标识符相同。对于标签(在goto 语句中使用,写作label:),标识符具有函数作用域,并且在它出现的函数中随处可见。

有四种存储持续时间,也称为生命周期:staticthreadautomaticallocated。对象的存储时长受其标识符的链接影响,所以我们需要先讨论链接。链接是一种使不同范围内的相同标识符引用同一对象的方法。

  • 如果文件范围内的对象或函数的标识符使用static 声明,则它具有内部链接。内部链接意味着同一翻译单元中的任何其他声明都将引用同一对象或函数。

  • 如果使用extern 声明标识符,则链接取决于是否已经存在可见的先前声明:

    1. 如果没有可见的先前声明,则标识符具有外部链接。这意味着程序中的任何其他声明都将引用相同的对象或函数。

    2. 如果有之前的声明,并且指定了内部或外部链接,则当前声明的链接与之前的声明相同。

    3. 如果有先前声明但未指定任何链接,则当前声明的链接是外部的。

  • 1234563用extern 声明(因此它遵循上述关于取决于先前声明的规则)。
  • 如果在文件范围内声明的对象没有存储类说明符,则其链接是外部的。

  • 否则,标识符没有链接,因此它的每个声明都引用不同的实体。这包括任何不是对象或函数的标识符(例如结构标记或 typedef 名称)、函数参数以及在没有extern 的函数内声明的变量。

现在我们可以说明存储时长的规则了:

  • 如果一个对象用static声明而没有_Thread_local,它有静态存储持续时间,它的生命周期是程序的整个执行过程。

  • 如果对象声明时不带_Thread_local,并且具有外部或内部链接,则它具有静态存储持续时间。

  • 如果使用_Thread_local 声明对象,则它具有线程存储持续时间,其生命周期是为其创建的线程的整个执行。

  • 如果声明的对象没有static 且没有链接,则它具有自动存储期限。如果它不是可变长度数组,则它的生命周期是从执行进入它所在的块开始,直到该块的执行结束。 (请注意,调用函数会暂停块的执行,但不会结束它。)如果它是一个可变长度数组,它的生命周期是从执行到达声明到执行离开声明的范围。

    李>

malloc 系列中的例程创建的对象还有一个分配的存储持续时间,并且有一个临时生命周期适用于在表达式中创建的对象,但我省略了对这些的讨论,因为它们与命名对象的声明无关。

如您所见,规则有些复杂。但是,随着实践,您将逐渐认识到范围和生命周期。

【讨论】: