c++ 静态初始化顺序失败适用于C吗?

2g32fytz  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(73)

我在互联网上找到的关于静态初始化顺序失败的一切都是关于C++的,但是如果我初始化一些类型的全局变量,比如

struct Foo {
    int flag;
    pthread_key_t key;
    void *ptrs[10];
};

字符串
不能初始化类型为struct Foo的变量,如static struct Foo x = { 0 };?如果我想得到正确的代码,因为SIOF?

os8fio9y

os8fio9y1#

C中初始化的问题是允许可执行代码在main函数之前运行,因此并不总是清楚这些代码将以何种顺序运行。由于静态对象的构造函数,这在C中是必要的。
另一方面,C不允许代码在函数之外运行。静态对象的初始化器必须是可以在编译时计算的常量表达式。
这意味着像static struct Foo x = { 0 };这样的初始化器在C中是完全正确的。

g6ll5ycj

g6ll5ycj2#

C没有静态初始化顺序失败。在C89中,规则是:
具有静态存储期的对象的初始化器中的所有表达式,或具有聚合或联合类型的对象的初始化器列表中的所有表达式都必须是常量表达式。
因此,标量类型的静态变量只能用单个常量表达式初始化。如果变量的类型是标量元素类型的数组,则每个初始化器都需要是常量表达式,依此类推。由于常量表达式既不会产生副作用,也不会依赖于任何其他计算产生的副作用,改变常量表达式的计算顺序不会影响结果。此外,编译器可以简单地发出已经初始化的数据(即在编译时计算这些常量表达式),因此当程序启动时,不需要进行静态初始化。
main之前可以计算的唯一非常量表达式是从C运行时调用的表达式。这就是为什么stdinstdoutstderr指向的FILE对象已经可供main的第一条语句使用,例如,标准C不允许用户注册自己的启动代码,以便在main之前运行--尽管GCC确实提供了一个名为__constructor__的扩展(大概是根据C特性命名的),如果你愿意的话,你可以用它来重新创建C中的静态初始化顺序失败。
Stroustrup在《C
的设计与进化》中写道,他的目标是让用户定义的类型在任何内置类型的地方都可用。这意味着C必须允许类类型的全局变量,这意味着它们的构造函数将在程序启动时被调用。因为早期的C没有constexpr函数,这样的构造函数调用永远不可能是常量表达式。所以,静态初始化命令的失败就诞生了。
在C标准化过程中,执行静态初始化的顺序问题是一个有争议的主题。我想大多数人都会同意,理想的情况是每个静态变量在使用之前都要初始化。不幸的是,这需要链接器技术,而当时并不存在(也许现在还没有。静态变量的初始化可以涉及函数调用,并且那些函数可以在另一TU中定义,这意味着您需要执行整个程序分析,以便成功地按依赖顺序对静态变量进行拓扑排序。可以这样设计,它仍然不能完全防止初始化顺序问题。想象一下,如果你有一些库,其中use函数的前提条件是init函数在过去的某个时候被调用过。然后,如果你有一个静态变量的初始化器调用init,另一个静态变量的初始化器调用use,那么就有一个编译器看不到的顺序依赖。
最终,我们在C
98中得到的有限的初始化顺序保证是我们在这种情况下所能得到的最好的保证。有了无限的后见之明,也许有人会抗议说,如果没有constexpr函数,标准就不完整(静态变量应该只被要求有常量初始化)。

uxh89sit

uxh89sit3#

对于具有静态(和线程)存储期限的对象,C只声明它们在main()被调用之前的某个时刻被初始化。C只允许它们被初始化为常量表达式。而在C中,对象可以有构造函数,可以被初始化为函数的结果。
如果我们在main()被调用之前看到“C运行时”(CRT)代码的“幕后”,就变量而言,它只会初始化.data.bss。从那里它就准备好了。等效的C
运行时并不是那么微不足道,因为它也会启动构造函数调用等。由于C标准和程序员都没有指定特定的顺序,CRT只会按照一些主观的出现顺序调用它们。2如果在那一点上对象之间存在初始化顺序依赖关系,那么一切很快就会崩溃。
C
还通过将 static initialization 定义为适合两个子类别的所有内容来增加额外的复杂性:constant initializationzero-initialization。然后将其他所有内容命名为 dynamic initialization(不要与动态分配混淆)。动态初始化反过来又带有出现顺序,排序等概念。

n3ipq98p

n3ipq98p4#

在如何对结构进行零初始化的上下文中,没有问题。
然而,在一般情况下,当打开.so库(也称为共享库)时,问题可能会发生在C代码中。这是因为共享库可能包含一个.init代码段,该代码段在加载库时运行。
因此,您必须想象两个共享库,它们在初始化例程中引用彼此的数据结构。
诚然,这超出了C语言的范围,但是,当处理共享库时,它与链接器的上下文中有关。

相关问题