是否保证初始化静态对象
Is static object guaranteed to be initialized
我正在尝试了解静态对象的初始化。假设您理解常量表达式和constexpr
,那么静态初始化似乎非常直接。动态初始化似乎有点棘手。
[basic.start.init]/4
是否在主语句的第一个语句之前完成具有静态存储持续时间的非局部变量的动态初始化,这是一个实现定义。如果初始化推迟到主语句的第一次语句之后的某个时间点,则应在第一次使用(3.2)与要初始化的变量在同一翻译单元中定义的任何函数或变量之前进行。
脚注34
具有静态存储持续时间的非局部变量在初始化时具有副作用,即使未使用,也必须进行初始化(3.2,3.7.1)
[basic.start.init]/5
它的实现定义了是否在线程的初始函数的第一个语句之前完成具有静态或线程存储持续时间的非局部变量的动态初始化。如果初始化被推迟到线程初始函数的第一个语句之后的某个时间点,则应在第一次使用(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
必须在某个时刻初始化。由于在转换单元中没有其他具有动态初始化的变量,因此没有其他变量可用于odr强制初始化tu1中的变量。s
在什么时候保证已经初始化?
main是否保证返回1?此外,是否有办法更改此程序,使其不再保证返回1?或者,如果不能保证,有没有办法改变这个程序,让它变得有保障?
我分解了代码,使s
的定义与main
的翻译单元不同。这避免了main
是否被odr使用的问题。假设s
是翻译单元中唯一的对象,是否保证main
会返回1?
我认为所有这些措辞都是为了描述动态加载库中会发生什么,但没有明确命名它们。
总结一下我是如何解释的:一个具有静态存储持续时间和动态初始化的非本地变量将:
- 在第一次使用其翻译单元中的任何内容之前进行初始化
- 可能,在启动
main
之前,但可能在它之后
我将脚注34解释为(但请记住,脚注不是规范性的):
当使用TU中的任何内容时,必须初始化每个具有静态存储持续时间的非局部变量,即使是未使用的变量。
因此,如果有一个TU没有使用任何单词,那么它的动态初始化可能不会发生。
示例
h1.h
extern int count;
struct S
{
S();
};
h1.cpp
#include "h1.h"
int count;
S::S()
{
++count;
}
h2.cpp
#include "h1.h"
S s;
main.cpp
#include "h1.h"
#include <stdio.h>
int main()
{
printf("%dn", count);
}
这可能会打印0或1:由于TU h2中的任何内容都从未使用过odr,因此未指定何时完成s
的代码初始化(如果有的话)。
自然,sane编译器会在main之前初始化s
,所以它肯定会打印1
:
$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1
现在,假设h2.cpp
在一个共享库中:
$ g++ -shared -fPIC h2.cpp -o h2.so
现在的主文件是:
main2.cpp
#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>
int main()
{
printf("%dn", count);
dlopen("./h2.so", RTLD_NOW);
printf("%dn", 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文件),则不会定义结果。
"首次使用与待初始化变量定义在同一翻译单元中的任何函数或变量"包括待初始化变量。
- CPU 瓶颈;处理具有许多非静态对象的 3D 场景渲染的简单方法
- 我可以读取静态对象中的文件.txt吗?C++
- 通过 Gazebo 世界插件将静态对象附加到机器人链接
- 将对象创建为全局/静态对象会崩溃,而本地对象不会崩溃
- 内联函数的函数本地静态对象是否在共享对象文件之间共享?
- 将 C# 对象(包含静态对象成员)作为参数传递给 C++/CLI 程序
- 完全释放静态对象内存
- 静态对象指针
- C++文件中.cpp静态对象声明
- C++类中定义静态对象
- 在初始化类的静态对象之前,是否保证初始化该类的静态成员?
- 静态对象如何调用私有构造函数
- cpp 静态对象实例化
- 等效于 Java 静态对象类C++
- 我怎么知道C++编译器是否制作线程安全的静态对象代码
- 在dlclose()之前破坏的静态对象
- 有没有办法为静态对象成员定义一个符合开关标准的常量?
- c++ 防止类共享静态对象
- 什么时候构造函数为静态对象
- 为什么本地静态对象的初始化使用隐藏的防护标志?