为什么文件范围静态变量必须为零初始化?
C ++默认初始化不会将变量与自动存储器清零,为什么对静态存储变量进行特殊处理?
它是由C和C ++定义的东西必须兼容吗? 如果是这样的话,为什么C决定进行零初始化?
如果文件范围静态变量与初始化程序一起提供,它们将首先进行零初始化,然后再次进行常量/动态初始化。 这不是多余的吗? 例如,以下代码来自cppreference: http : //en.cppreference.com/w/cpp/language/zero_initialization
#include 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的操作系统上的行为已经形成了这些标准规定。 当应用程序加载时,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都具有高效的指令,可以将大量内存清零。
全局变量的零初始化是“免费的”,因为它们的存储在main()启动之前在“BSS”段中分配。 也就是说,当您访问指针p
,指针本身必须存储在某处,并且某处实际上是BSS中的特定位块。 既然必须将其初始化为某些东西,为什么不归零?
现在,为什么不自动/堆栈变量这样做? 因为这会花费时间:堆栈上的分配只不过是堆栈指针的递增(或递减,透视)。 无论什么垃圾都可以留在那里(根据C)。 由于我们无法免费获得零init,因此我们根本没有得到它(因为它是C,我们不喜欢支付我们不使用的东西)。
默认初始化std :: string或其他类类型有点复杂:C ++要求以某种方式对其进行初始化,默认构造函数当然是使用的构造函数,是的,从技术上讲,它首先是零初始化,但正如所讨论的那样,零初始化是“免费的”。 可以允许一个能够充分分析std :: string的实现,以便在构建时确定如何初始化其位,就像调用默认构造函数一样,但我不知道是否有任何实现。
C中的全局变量和静态变量在程序的生命周期内具有固定的内存地址。 这使程序启动器能够通过将适当的存储区域从文件可执行文件复制到计算机存储器来初始化它们。
因此,C可以(必须)为每个静态/全局变量提供初始值。 如果用户未提供任何值,则标准行为是使用零。 与局部变量相反,这不会增加应用程序的内存和速度(因为无论如何都必须写入一个值)。
最终,如果您有没有任何初始数据的大型数组,这种行为(将静态初始数据复制到可执行文件中)可能非常糟糕。 事实上,现代C编译器似乎能够避免这种浪费,并且将零填充大型数组,而不是在程序可存储的情况下存储零。 然而,一旦给出规则,即使用户可能不需要它们,它们也被迫填充该区域。 无论如何,这是一个非常便宜的操作,在程序启动时执行一次。