为什么文件范围静态变量必须为零初始化?

时间:2014-09-09 10:56:53

标签: c++ c initialization

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?

编辑: 这个问题的部分问题已在此处回答:Static variable initialization? 但我不认为它是重复的,因为另一个问题的答案并没有回答我的第二个问题,即。为什么2阶段初始化。 此外,另一篇文章的标题并没有真正说出究竟是什么问题。

3 个答案:

答案 0 :(得分:13)

开发C的操作系统上的行为已经形成了这些标准规定。当应用程序加载时,OS加载程序为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都有高效的指令来清除大量内存。

答案 1 :(得分:7)

全局变量的零初始化是“免费的”,因为它们的存储在main()启动之前在“BSS”段中分配。也就是说,当您访问指针p时,指针本身必须存储在某处,并且某处实际上是BSS中的特定位块。既然必须将其初始化为某种东西,为什么不归零?

现在,为什么不自动/堆栈变量这样做?因为这会花费时间:在堆栈上的分配只不过是递增(或递减,透视)堆栈指针。无论什么垃圾都可以留在那里(根据C)。由于我们不能免费获得零init,我们根本不会得到它(因为它是C,我们不喜欢支付我们不使用的东西)。

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

答案 2 :(得分:3)

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

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

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