如果在函数的作用域中声明一个变量为static
,它只会被初始化一次,并保留其值直到函数调用。那么它的生命周期是什么时候?它的构造函数和析构函数何时被调用?
void foo()
{
static string plonk = "When will I die?";
}
static
变量的生命周期始于程序流第一次遇到它的声明,结束于程序终止。这意味着运行时必须进行一些书记记录,以便仅在实际构造时才进行析构。
此外,由于标准规定静态对象的析构函数必须按照其构造完成的相反顺序运行,并且构造顺序可能取决于特定的程序运行,因此必须考虑构造顺序。
示例
struct emitter {
string str;
emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
~emitter() { cout << "Destroyed " << str << endl; }
};
void foo(bool skip_first)
{
if (!skip_first)
static emitter a("in if");
static emitter b("in foo");
}
int main(int argc, char*[])
{
foo(argc != 2);
if (argc == 3)
foo(false);
}
输出:
C:>sample.exe
在foo中创建
在foo中销毁C:>sample.exe 1
在if中创建
在foo中创建
在foo中销毁
在if中销毁C:>sample.exe 1 2
在foo中创建
在if中创建
在if中销毁
在foo中销毁
[0]
由于C++98[2]没有关于多线程的参考,因此这在多线程环境下的行为是未指定的,可能会出现问题,正如Roddy所提到的。
[1]
C++98第3.6.3.1
节[basic.start.term]
[2]
在C++11中,静态变量以线程安全的方式初始化,这也被称为Magic Statics。
Motti的关于顺序的说法是正确的,但还有其他一些需要考虑的问题:
编译器通常使用隐藏的标志变量来指示本地静态变量是否已经初始化,而这个标志在每次进入函数时被检查。显然,这会带来一些性能损失,但更令人担忧的是,这个标志不能保证线程安全。
如果你有一个像上面那样的本地静态变量,并且foo
从多个线程中调用,你可能会遇到竞争条件,导致plonk
被错误地初始化或甚至多次初始化。此外,在这种情况下,plonk
可能会被一个不同的线程析构,而非构造它的线程。
尽管标准规定了顺序,但我仍然非常谨慎地对待本地静态销毁的实际顺序,因为你可能无意中依赖于静态对象在被销毁后仍然有效,这真的很难追踪。
如果没有标准中实际规则的真实规定,那么现有的解释并不完整,该规定在 6.7 中找到:
所有具有静态存储期或线程存储期的块作用域变量的零初始化是在任何其他初始化之前执行的。如适用,则在首次进入其块之前执行带有静态存储期的块作用域实体的常量初始化。 在相同条件下,实现允许对具有静态或线程存储期的其他块作用域变量进行早期初始化,就像在名称空间范围内静态初始化具有静态或线程存储期的变量一样。否则,这种变量将在控制第一次通过其声明时进行初始化; 这种变量被认为在其初始化完成时已经初始化。如果初始化通过抛出异常退出,则初始化尚未完成,因此下次控制进入声明时会再次尝试。 如果在初始化变量时同时并发地进入声明,则并发执行必须等待初始化完成。 如果在初始化变量时递归地重新进入声明,则行为是未定义的。
就我所知,Codegear C++Builder不按照标准预期的顺序进行析构。
C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if
这也是不依赖销毁顺序的另一个原因!
一旦程序开始执行,静态变量就会发挥作用,并且在程序执行结束之前一直可用。
静态变量是在内存的数据段中创建的。
[basic.start.term]
**。 - Ben Voigtmain
返回和调用std::exit
的结果中被调用。您能否指出标准中明确允许动态库的部分? - Motti