[英]Are static class members guaranteed to be initialized before `main` is called?
[英]Is static object guaranteed to be initialized
我试图了解静态对象的初始化。 假设你理解常量表达式和constexpr
,静态初始化似乎很简单。 动态初始化似乎有点棘手。
[basic.start.init / 4
实现定义是否在第一个main语句之前完成具有静态存储持续时间的非局部变量的动态初始化。 如果初始化延迟到第一个main语句之后的某个时间点,则应该在与要初始化的变量相同的转换单元中定义的任何函数或变量的第一个odr-use(3.2)之前发生。
脚注34
具有静态存储持续时间的非局部变量必须初始化,即使它没有使用odr-used(3.2,3.7.1)。
[basic.start.init] / 5
实现 - 定义是否在线程的初始函数的第一个语句之前完成具有静态或线程存储持续时间的非局部变量的动态初始化。 如果初始化被推迟到线程初始函数的第一个语句之后的某个时间点,它应该在任何变量的第一个odr-use(3.2)之前发生,其中线程存储持续时间与变量在同一个转换单元中定义要初始化。
我假设“线程的初始函数”是指main,而不仅仅是以std :: thread开头的线程。
h1.h
#ifndef H1_H_
#define H1_H_
extern int count;
#endif
tu1.cpp
#include "h1.h"
struct S
{
S()
{
++count;
}
};
S s;
tu2.cpp
#include "h1.h"
int main(int argc, char *argv[])
{
return count;
}
tu3.cpp
#include "h1.h"
int count;
因此,如果编译器推迟动态初始化,脚注34似乎表明必须在某个时刻初始化s
。 由于在转换单元中没有其他具有动态初始化的变量,因此没有其他变量可用于强制初始化tu1中的变量。 在什么时候被s
保证已初始化?
主要保证返回1吗? 还有,有什么方法可以改变这个程序,使其不再保证返回1? 或者,如果不能保证,有没有办法改变这个程序,以保证它?
我打破了代码,以便s
的定义与main
翻译单元不同。 这避免了main
是否使用odr的问题。 鉴于s
是翻译单元中唯一的对象,是否保证main
将返回1?
我认为所有这些措辞都是为了描述动态加载库中会发生什么,但没有明确命名它们。
总结我如何解释它:具有静态存储持续时间和动态初始化的非局部变量将:
main
之前,但可能在之后。 我将脚注34解释为(但请记住,脚注不是规范性的):
当TU中的任何东西被ord-used使用时,每个具有静态存储持续时间的非局部变量都必须初始化,即使是没有使用过的变量也是如此。
因此,如果存在未使用任何东西的TU,则其动态初始化可能不会发生。
extern int count;
struct S
{
S();
};
#include "h1.h"
int count;
S::S()
{
++count;
}
#include "h1.h"
S s;
#include "h1.h"
#include <stdio.h>
int main()
{
printf("%d\n", count);
}
这可以打印0或1:因为TU h2中的任何内容都不会被使用,所以当s
的代码初始化完成时(如果有的话),它是未指定的。
当然,理智的编译器会在main之前初始化s
,所以它肯定会打印1
:
$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1
现在,假设h2.cpp
位于共享库中:
$ g++ -shared -fPIC h2.cpp -o h2.so
主文件现在是:
#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>
int main()
{
printf("%d\n", count);
dlopen("./h2.so", RTLD_NOW);
printf("%d\n", count);
return 0;
}
编译并运行:
$ g++ -shared -fPIC h2.cpp -o h2.so
$ g++ -rdynamic main.cpp h1.cpp -ldl -o test2
$ ./test2
0
1
看到? s
的初始化已被延迟! 好的部分是,如果没有先加载它就不可能在动态加载库中引用任何内容,加载它将触发动态初始化。 一切都很好。
如果您认为使用dlopen
是作弊,请记住有些编译器支持延迟加载共享库(例如VC ++),其中加载库的系统调用将在第一次需要时由编译器自动生成。
如果不在定义中搜索正确的页面,我可以说保证程序返回1.每次静态或全局初始化都在main中的第一个命令之前完成。 全局变量首先初始化然后执行全局对象的构造函数。 函数/方法范围内的静态在首次使用之前初始化。 但是有一个陷阱:
int count;
struct A
{
A()
{
count=5;
}
};
struct B
{
B()
{
count=count*2;
}
};
A a;
B b;
void main(void)
{
return count;
}
如Ben Voigt的评论中所述,如果两个实例都在同一翻译单元中创建,则定义结果。 所以在我的示例中,结果是10.如果实例是在不同的文件中创建的(并且分别编译为不同的.obj文件),则不会定义结果。
“在与要初始化的变量相同的翻译单元中定义的任何函数或变量的第一次使用”包括要初始化的变量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.