【发布时间】:2010-09-19 18:44:44
【问题描述】:
如果一个变量在函数的作用域中被声明为static,它只会被初始化一次并且在函数调用之间保持它的值。它的寿命究竟是多少?什么时候调用它的构造函数和析构函数?
void foo()
{
static string plonk = "When will I die?";
}
【问题讨论】:
如果一个变量在函数的作用域中被声明为static,它只会被初始化一次并且在函数调用之间保持它的值。它的寿命究竟是多少?什么时候调用它的构造函数和析构函数?
void foo()
{
static string plonk = "When will I die?";
}
【问题讨论】:
函数static 变量的生命周期从程序流第一次遇到声明时开始[0],并在程序终止时结束。这意味着运行时必须执行一些记录,以便只有在实际构建时才将其销毁。
另外,由于标准规定静态对象的析构函数必须按照其构造完成的相反顺序运行[1],而构造的顺序可能取决于具体的程序运行,必须考虑到构造顺序。
示例
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。
【讨论】:
[basic.start.term]
Motti 对订单的看法是正确的,但还有其他一些事情需要考虑:
编译器通常使用隐藏标志变量来指示本地静态变量是否已经初始化,并且在函数的每个条目上都会检查此标志。显然这是一个小的性能损失,但更令人担忧的是这个标志不能保证是线程安全的。
如果您有上述本地静态,并且从多个线程调用 foo,您可能会遇到竞争条件导致 plonk 被错误地初始化甚至多次初始化。此外,在这种情况下,plonk 可能会被与构造它的线程不同的线程破坏。
尽管标准说了什么,我还是会非常警惕局部静态破坏的实际顺序,因为您可能会在不知不觉中依赖静态在它被破坏后仍然有效,这真的很难追踪下。
【讨论】:
如果没有 6.7 中的标准中的实际规则,现有的解释并不完整:
所有具有静态存储持续时间或线程存储持续时间的块范围变量的零初始化在任何其他初始化发生之前执行。具有静态存储持续时间的块范围实体的持续初始化(如果适用)在首次进入其块之前执行。在允许实现在命名空间范围内静态初始化具有静态或线程存储持续时间的变量的相同条件下,允许实现对具有静态或线程存储持续时间的其他块范围变量执行早期初始化。否则,此类变量在控件第一次通过其声明时被初始化;这样的变量在其初始化完成时被认为已初始化。如果初始化通过抛出异常退出,则初始化 不完整,所以下次控制进入声明时会再次尝试。如果在初始化变量时控制同时进入声明,则并发执行将等待初始化完成。如果在初始化变量时控件以递归方式重新进入声明,则行为未定义。
【讨论】:
FWIW,Codegear C++Builder 不会按照标准按预期顺序进行破坏。
C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if
...这也是不依赖销毁命令的另一个原因!
【讨论】:
静态变量在程序执行开始后开始发挥作用,并且在程序执行结束之前一直可用。
静态变量在内存的数据段中创建。
【讨论】: