【问题标题】:Why are static variables auto-initialized to zero? [duplicate]为什么静态变量会自动初始化为零? [复制]
【发布时间】:2010-07-30 15:45:03
【问题描述】:

可能重复:
Why global and static variables are initialized to their default values?

发生这种情况的技术原因是什么?所有平台的标准都支持它吗?如果静态变量没有显式初始化,某些实现是否有可能返回未定义的变量?

【问题讨论】:

  • 投票重新打开,因为这个问题还询问是否某些编译器不会将静态变量归零。答案是肯定的!一些特定领域的编译器不符合所有标准。例如,在 TIGCC(TI-89/92/V200 计算器的 C 编译器)中,具有显式初始化的全局变量(例如 `static int high_score = 0;')将在程序运行期间保留(除非它已存档在闪存中),提供了一种简单但低俗的方式来保留程序设置。

标签: c


【解决方案1】:

标准要求的(§6.7.8/10)。

没有技术原因必须采用这种方式,但这种方式已经存在了足够长的时间,标准委员会将其作为一项要求。

忽略此要求会使在许多(大多数?)情况下使用静态变量变得更加困难。特别是,您经常需要进行一次性初始化,并且需要一个可靠的起始状态,以便您知道某个特定变量是否已经初始化。例如:

int foo() { 
    static int *ptr;

    if (NULL == ptr)
       // initialize it
}

如果ptr 在启动时可能包含任意值,您必须将其显式初始化为 NULL 才能识别您是否已完成一次性初始化。

【讨论】:

  • +1 用于标准链接。 ANSI C 委员会的最初任务是编纂现有实践,这很可能是它成为要求的原因。
  • @paxdiablo:毫无疑问——它在 K&R1(附录 A,§6.8)中明确说明:“未初始化的静态和外部变量保证以 0 开始;自动和注册变量是未初始化的保证以垃圾开始。”
  • 如果它不是零初始化的并且你需要一个可靠的默认值,你可以简单地做static int *ptr = NULL;。这有什么问题?
【解决方案2】:

是的,因为它在标准中;但实际上,这是因为它是免费的。静态变量看起来就像生成的目标代码的全局变量。它们在 .bss 中分配,并在加载时与所有常量和其他全局变量一起初始化。由于它们所在的内存部分只是直接从您的可执行文件中复制而来,因此它们被免费初始化为编译时已知的值。选择的值为 0。

【讨论】:

  • ELF 文件中的 .bss 部分长度为零。加载程序负责对块进行零初始化。如果这不是标准要求的,它可以让块未初始化。
  • 进程启动时未初始化的页面无论如何都将是全零,除非内核不遗余力地写入随机垃圾。在任何现代实现中,bss 都是对所有进程共享的“零页”的 COW(写时复制)引用。即使不是,内核仍然需要做一些事情来防止新进程看到随机物理内存的内容(可能包含私有的内部内核数据或来自其他用户的数据),因此他们不妨将其设置为清除时有用的值,例如 0...
【解决方案3】:

当然没有争论它在 C 标准中。因此,期望一个兼容的编译器会以这种方式运行。

这样做的技术原因可能源于 C 启动代码的工作方式。通常有几个内存段,链接器必须将编译器输出放入其中,包括代码(文本)段、块存储段和初始化变量段。

在运行时创建函数范围之前,非静态函数变量没有物理存储空间,因此链接器不会对它们做任何事情。

程序代码当然放在代码(或文本)段中,但用于初始化全局和静态变量的也是如此。初始化变量本身(即它们的地址)进入初始化的内存段。未初始化的全局和静态变量进入块存储 (bss) 段。

当程序在执行时加载时,一小段代码创建了 C 运行时环境。在基于 ROM 的系统中,它会将初始化变量的 从代码(文本)段复制到它们各自在 RAM 中的实际地址中。基于 RAM(即磁盘)的系统可以将初始值直接加载到最终的 RAM 地址。

CRT(C 运行时)还将包含所有没有初始化程序的全局和静态变量的 bss 清零。这可能是为了防止未初始化的数据。这是一种相对简单的块填充操作,因为所有的全局变量和静态变量都被塞进了一个地址段。

当然,浮点数和双精度数可能需要特殊处理,因为如果浮点格式不是 IEEE 754,它们的 0.0 值可能不会全为零。

请注意,由于自动变量在程序加载时不存在,它们不能由运行时启动代码初始化。

【讨论】:

  • IEEE 754 浮点标准是专门设计的,因此 +0 都是零位。您是否见过使用另一种浮点值表示的 C 实现,但事实并非如此?
  • @Jeffrey:我使用了 TI 等 DSP 特定浮点格式的编译器。但现在我不能 100% 确定他们是否也拥有该属性。
  • @Jeffrey:不,TI 格式不会将 0.0 编码为全零位。它需要 -128 的指数才能唯一标识 0.0。
【解决方案4】:

主要是因为静态变量被链接器组合在一个块中,所以在启动时将整个块的 memset() 设置为 0 真的很容易。我不相信这是 C 或 C++ 标准所要求的。

【讨论】:

  • memset 不是 0,除非在 DOS 和其他 [非] 操作系统上。在现代系统上,它是对“零页”的写时复制引用。
【解决方案5】:

关于这个here有讨论:

首先在 ISO C (ANSI C) 中,所有的静态变量和全局变量都必须在程序启动之前进行初始化。如果程序员没有明确地这样做,那么编译器必须将它们设置为零。如果编译器不这样做,则它不遵循 ISO C。但是,变量的具体初始化方式并未由标准指定。

【讨论】:

    【解决方案6】:

    看看:here 6.2.4(3) 和 6.7.8 (10)

    【讨论】:

      【解决方案7】:

      假设您正在编写一个 C 编译器。您希望某些静态变量将具有初始值,因此这些值必须出现在编译器将要创建的可执行文件中的某个位置。现在当输出程序运行时,整个可执行文件被加载到内存中。程序初始化的一部分是创建静态变量,因此所有这些初始值都必须复制到它们的最终静态变量目的地。

      还是他们?程序启动后,不再需要变量的初始值。变量本身不能位于可执行代码本身中吗?那么就不需要复制这些值了。静态变量可以存在于原始可执行文件中的块中,根本不需要为它们进行初始化。

      如果是这样,那你为什么要为未初始化的静态变量做一个特例呢?为什么不在可执行文件中放一堆零来表示未初始化的静态变量呢?这将用一些空间换取一点时间和更少的复杂性。

      我不知道是否有任何 C 编译器实际上以这种方式运行,但我怀疑以这种方式做事的选项可能推动了语言的设计。

      【讨论】:

      • 一些旧的 C 编译器可能已将初始化变量放入代码映像中并从那里访问它们。我知道 Turbo Pascal 就是这么做的。我认为没有理由将未初始化的变量放在那里。用零填充一块内存并不难。
      • @supercat - 用零填充一块内存一点也不难。但是将静态变量分为已初始化和未初始化类别并为它们分配单独的内存块(虽然也不难)是不必要的。
      • 这是节省磁盘和内存空间的“必要”。如果您称其为“不必要的”,那么您应该停止编码...
      • @R.. - 哦,是吗?也许,应该停止编码。 :-P
      • @R.. - 开个玩笑,等待执行开始分配静态内存位置究竟如何节省“内存”空间?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-01
      • 2014-03-24
      • 2013-07-21
      • 2017-12-07
      • 2014-05-17
      相关资源
      最近更新 更多