静态变量初始化?

我想知道为什么C,C ++和Java中的静态变量默认初始化为零? 为什么局部变量不是这样呢?

为什么静态变量是确定性初始化而局部变量不是?

了解静态变量的实现方式。 它们的内存在链接时分配,它们的初始值也在链接时提供。 没有运行时开销。

另一方面,局部变量的内存在运行时分配。 堆栈必须增长。 你不知道之前有什么。 如果需要,可以清除该内存(零),但这会产生运行时开销。 C ++哲学是“你不为你不使用的东西买单”,所以默认情况下它不会将内存归零。

好的,但为什么静态变量初始化为零,而不是其他值?

好吧,你通常想用这个变量做点什么。 但是你怎么知道它是否已被初始化? 您可以创建一个静态布尔变量。 但是它也必须可靠地初始化为某种东西(最好是假的)。 指针怎么样? 你宁愿把它初始化为NULL而不是一些随机垃圾。 结构/记录怎么样? 它里面还有一些其他的数据成员。 将所有这些值初始化为默认值是有意义的。 但为简单起见,如果使用“初始化为0”策略,则不必检查单个成员并检查其类型。 您只需将整个内存区域初始化为0即可。

这不是真正的技术要求。 如果默认值不是0,但仍然是确定性的,那么初始化的语义仍然可以被认为是合理的。 但那么,这个价值应该是什么? 你可以很容易地解释为什么使用0(尽管听起来确实听起来有点武断),但解释-1或1024似乎更难(尤其是变量可能不足以容纳该值,等等)。

并且您始终可以显式初始化变量。

并且您始终拥有C ++标准的第8.5.6段,其中说“静态存储持续时间的每个对象都应在程序启动时进行零初始化”。

有关详细信息,请参阅以下其他问题:

  • 全局内存是用C ++初始化的吗?
  • 以下短语在C ++中的含义是什么:零,默认和值初始化?

C ++标准的第8.5.6段规定:

“静态存储持续时间的每个对象都应在程序启动时进行零初始化”

(标准还说局部变量的初始化是未定义的)

至于为什么,标准没有说;)一个猜测是它的实施相当容易,没有任何额外的缺点。

说到java:

必须先初始化局部变量才能访问它,因为这是一个安全的好处。 如果确定变量,编译器会检查您。

static或类变量(使用Object类型)使用null初始化,因为编译器无法检查它们是否在编译时初始化。 如果它访问非初始化变量,它将不会让程序失败,而是使用null隐式初始化它。

具有本机类型的变量无法获取null值,因此非局部变量初始化为0false ,作为回退。 当然,这不是最佳解决方案,但我不知道更好的解决方案。 😉

所以在某种程度上,这些只是语言设计者的设计决策。 但是Java中这些决策的可能原因是:

  • 对于静态/成员变量,如果你要将它们初始化为某个东西,则零是一个方便的值,因为(a)它通常是一个合适的值,意思是“不设置为其他任何特殊的”,并且是你将拥有的值无论如何在某些情况下挑选,例如柜台; (b)在内部,零可以用于“特殊”值,特别是在对象引用的情况下表示null。
  • 对于局部变量,给它们没有默认允许强制程序员在读取变量之前设置一些值的规则,这实际上可以用于允许编译器发现某些错误。

在局部变量的情况下,也可以想到可以声明局部变量(在字节码/机器代码级别实质上意味着分配堆栈空间/移动堆栈指针)但是然后从未在特定代码路径中实际写入/读取。 因此,没有默认设置可以避免在这些情况下进行不必要的设置默认操作。

不过,我再说一遍,这些都是设计决定。 它们本质上是在JVM实现方便和程序员方便之间的权衡。

NB在C / C ++中,“静态”变量对Java中的静态变量意味着不同的东西!

我不知道java,我怀疑它与java中的静态/本地不同。

至于c和c ++,它是关于程序员关心他们的代码效果和喜欢控制。 每次程序进入范围时,初始化局部变量都意味着执行额外的代码。 对于经常被称为可能是灾难的function。

这与C / C ++中“仅为您使用的东西付费”的概念有关。

对于静态变量,可以在不生成代码的情况下进行初始化。 目标文件包含数据段中变量的初始值,当OS加载可执行文件时,它会加载并在程序开始执行之前映射此数据段。

对于局部变量,没有代码就无法初始化它们,因为它们没有初始化一次,每次进入它们的范围时都应该初始化它们; 它们也被分配在堆栈中,并且当分配发生时,一般情况下堆栈中的初始值就是之前的情况(除了那些罕见的时刻,你比以前增长了更多的堆栈)。

因此,为了隐式初始化一个局部变量,编译器需要生成代码而程序员没有明确地命令它这样做,这完全违背了“哲学”。

关于Java,据我所知,变量总是在程序进入其范围时初始化,无论它们是否是静态的。 它们之间唯一的显着区别是静态变量的范围是整个程序。 鉴于此,所有这些行为都是一致的。

这只是一个猜测,但它可能是静态的方式,因为它易于实现,并且很有用。

编译器可以将所有变量共同分配到一个连续的内存区域,然后在调用main()之前发出代码(单个memset()调用)以清除它。 在许多情况下,它还可以依赖操作系统的可执行文件格式的function,如果该格式支持“ bss部分” ,则由加载程序清除。 这可以节省可执行文件中的空间

 static unsigned char megabyte[1 << 20]; 

并且可执行文件不会增长一兆字节。

对于局部变量,这些都不适用; 它们是“即时”分配(通常在堆栈上),清除它们将浪费资源,因为它们通常很快就会分配给它们。