为什么文件作用域静态变量必须初始化为零

Why file scope static variables have to be zero-initialized?

本文关键字:初始化 变量 文件 作用域 静态 为什么      更新时间:2023-10-16

C++默认初始化不使用自动存储将变量清零,为什么要对静态存储变量进行特殊处理?

它是由C和C++定义的东西吗?如果是这种情况,为什么C决定进行零初始化?

如果为文件范围静态变量提供初始值设定项,则它们将首先进行零初始化,然后再次进行常量/动态初始化。这不是多余的吗?例如,以下代码来自cppreference:http://en.cppreference.com/w/cpp/language/zero_initialization

#include <string>
double f[3]; // zero-initialized to three 0.0's
int* p;   // zero-initialized to null pointer value
std::string s; // zero-initialized to indeterminate value
               // then default-initialized to ""
int main(int argc, char* argv[])
{
    static int n = argc; // zero-initialized to 0
                         // then copy-initialized to argc
    delete p; // safe to delete a null pointer
}

在这种情况下,为什么n不能直接初始化为argc?

编辑:这里的问题已经回答了这个问题的一部分:静态变量初始化?但我不认为这是重复的,因为另一个问题中的答案没有回答我的第二个问题,即为什么2阶段初始化。此外,另一篇文章的标题并没有真正说明问题所在。

开发C的操作系统上的行为塑造了这些标准规定。当应用程序加载时,操作系统加载程序为BSS提供一些内存。最好将其清除为零,因为如果其他进程早些时候使用过该内存,则您启动的程序可能会窥探前一进程的内存内容,从而可能看到密码、对话或其他数据。并不是每个早期或简单的操作系统都关心这一点,但大多数操作系统都这样做,所以在大多数情况下,初始化实际上是"免费的",因为这是操作系统无论如何都会做的任务。

默认值为0使实现可以很容易地看到动态初始化期间设置的引用标志,因为不会有未初始化的内存读取和由此产生的未定义行为。例如,给定。。。

void f() { static int n = g(); }

编译器/实现也可以隐式地添加类似static bool __f_statics_initialised变量的东西——幸运的是,由于归零行为,该变量默认为0/false——以及类似于(可能是的线程安全版本)的初始化代码。。。

if (!__f_statics_initialised)
{
    n = g();
    __f_statics_initialised = true;
}

对于上面的场景,初始化是在第一次调用时完成的,但对于全局变量,它是在调用main()之前的某个时间按未指定的每个对象排序完成的。在这种情况下,有一些特定于对象的初始化代码和动态初始化能够区分未初始化状态下的静态和他们知道需要设置为非零值的静态,这使得编写健壮的启动代码变得更容易。例如,函数可以检查非本地静态指针是否仍然为0,如果是,则new是它的对象

同样值得注意的是,许多CPU都有高效的指令来清空大量内存。

全局的零初始化是"免费的",因为它们的存储在main()启动之前分配在"BSS"段中。也就是说,当您访问指针p时,指针本身必须存储在某个地方,而这个地方实际上是BSS中的一个特定的比特块。既然它必须初始化为某个值,为什么不为零呢?

现在,为什么自动/堆栈变量不这样做呢?因为这会花费时间:堆栈上的分配只不过是增加(或减少,从长远来看)堆栈指针。无论那里有什么垃圾,都可以留在那里(根据C的说法)。由于我们不能免费获得zero-init,所以我们根本得不到它(因为它是C,我们不喜欢为不使用的东西付费)。

默认初始化std::string或其他类类型有点复杂:C++要求以某种方式初始化它,默认构造函数当然是被使用的构造函数,是的,从技术上讲,它首先是零初始化的,但正如所讨论的,零初始化是"免费的"。"对于一个能够充分分析std::string以在构建时确定如何像调用默认构造函数一样初始化其位的实现来说,这可能是允许的,但我不知道是否有任何实现能做到这一点

在C中,全局变量和静态变量在程序的生存期内具有固定的内存地址。这使得程序启动器能够通过将适当的内存区域从可执行文件复制到计算机内存来初始化它们。

因此,C可以(必须)为每个静态/全局变量提供一个初始值。如果用户没有提供任何值,则标准行为是使用零。和局部变量相反,这既不会增加内存,也不会提高应用程序的速度(因为无论如何都必须写入值)。

最终,如果您有没有任何初始数据的大型数组,这种行为(将静态初始数据复制到可执行文件中)可能会非常糟糕。事实上,现代C编译器似乎能够避免这种浪费,并将零填充大型数组,而不是将零存储在可执行程序中。然而,一旦给出了规则,即使用户可能不需要,他们也会被迫填充该区域。无论如何,这是一个非常便宜的操作,只在程序启动时执行一次。