提问人:blonded04 提问时间:11/16/2023 最后编辑:Lundinblonded04 更新时间:11/17/2023 访问量:175
静态初始化顺序惨败是否适用于 C?
Is static initialization order fiasco applicable to C?
问:
我在互联网上找到的关于静态初始化顺序惨败的所有内容都是关于 C++ 的,但是如果我初始化某种类型的全局变量,这是真的吗 Foo 像
struct Foo {
int flag;
pthread_key_t key;
void *ptrs[10];
};
我无法初始化类型的变量,如?如果我想因为 SIOF 而获得正确的代码?struct Foo
static struct Foo x = { 0 };
答:
C++ 中初始化的问题在于允许可执行代码在函数之前运行,因此并不总是清楚此类代码将以何种顺序运行。这在 C++ 中是必需的,因为静态对象的构造函数。main
另一方面,C 不允许代码在函数之外运行。静态对象的初始值设定项必须是常量表达式,可以在编译时计算。
这意味着像这样的初始化器在 C 中是完全可以的。static struct Foo x = { 0 };
评论
static void* foo = &x;
x
x
x
foo
x
对于具有静态(和线程)存储持续时间的对象,C 仅声明它们在调用 main() 之前的某个时间点初始化。C 只允许它们初始化为常量表达式。而在 C++ 中,对象可以具有构造函数,并且可以初始化为函数的结果。
如果我们在调用 main() 之前运行的“C 运行时”(CRT) 代码的“底层”,就变量而言,它只会初始化和 .从那里就可以开始了。等效的 C++ 运行时并不是那么简单,因为它还会启动构造函数调用等。由于 C++ 标准和程序员都没有指定特定的顺序,因此 CRT 只会以某种主观的外观顺序调用它们。如果此时对象之间存在初始化顺序依赖关系,则一切都将很快崩溃。.data
.bss
C++ 还通过将静态初始化定义为适合两个子类别的所有内容来增加额外的复杂性:常量初始化和零初始化。然后命名其他所有动态初始化(不要与动态分配混淆)。动态初始化反过来又带有出现顺序、排序等概念。
在您希望如何零初始化结构的上下文中,没有问题。
但是,在一般情况下,打开库(又名共享库)时,C代码中可能会出现问题。这是因为共享库可能包含一个代码段,该代码段在加载库时运行。.so
.init
因此,您必须想象两个共享库,它们在初始化例程中相互引用彼此的数据结构。
诚然,这超出了 C 语言的范围。但是,在处理共享库时,它与链接器的上下文相关。
C does not have the static initialization order fiasco. In C89, the rule was:
All the expressions in an initializer for an object that has static storage duration or in an initializer list for an object that has aggregate or union type shall be constant expressions.
So a static variable with scalar type could only be initialized with a single constant expression. If the variable's type is an array with a scalar element type, then each initializer would need to be a constant expression, and so on. Since a constant expression can neither produce side effects, nor depend on side effects produced by any other evaluation, changing the evaluation order of constant expressions doesn't affect the result. Furthermore, the compiler can simply emit already-initialized data (i.e., evaluate those constant expressions at compile time), so when the program starts, there is no static initialization to be done.
The only non-constant expressions that can be evaluated before are ones that are invoked from the C runtime. That's why the objects that are pointed to by , , and are already available for use by the first statement of , for example. Standard C doesn't allow users to register their own startup code to be run before —although GCC does provide an extension called (presumably named after the C++ feature) that you can use to recreate the static initialization order fiasco in C if you so desire.main
FILE
stdin
stdout
stderr
main
main
__constructor__
Stroustrup wrote in The Design and Evolution of C++ that his aim was to make user-defined types usable wherever built-in types were. That meant that C++ had to allow global variables of class type, which means that their constructors would get called during program startup. Because early C++ didn't have functions, such constructor calls could never be constant expressions. And so, the static initialization order fiasco was born.constexpr
During the C++ standardization process, the question of the order in which to perform static initialization was a controversial topic. I think most people would agree that the ideal situation would be for every static variable to be initialized prior to its use. Unfortunately, that requires linker technology that didn't exist in those days (and probably still doesn't?). The initialization of a static variable can involve function calls, and those functions may be defined in another TU, which means you would need to perform whole-program analysis in order to successfully topologically sort the static variables in dependency order. It's worth noting that even if C++ could have been designed this way, it still wouldn't have completely prevented initialization order issues. Imagine if you had some library where a precondition of the function was that the function had been called at some point in the past. Then, if you have one static variable whose initializer calls and another whose initializer calls , then there's an order dependency that the compiler can't see.use
init
init
use
Ultimately, the limited initialization order guarantees that we got in C++98 were the best that we could get under the circumstances. With the benefit of unlimited hindsight, perhaps one could have protested that the standard wouldn't be complete without functions (and that static variables should be required to only have constant initialization).constexpr
评论
Foo
static void* foo = &x;