当前位置:  开发笔记 > 编程语言 > 正文

C++函数中静态变量的生命周期是多少?

如何解决《C++函数中静态变量的生命周期是多少?》经验,为你挑选了4个好方法。

如果变量声明为static在函数的作用域中,则仅初始化一次并在函数调用之间保留其值.它的生命到底是什么?它的构造函数和析构函数何时被调用?

void foo() 
{ 
    static string plonk = "When will I die?";
}

Motti.. 243

函数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
如果
在foo中创建,则创建在foo

销毁在if 中销毁

C:> sample.exe 1 2
在foo中
创建如果在foo 中被
销毁则在
Destroyed中 创建

[0]由于C++ 98 [2]没有引用多线程如何在多线程环境中表现如何是未指定的,并且可能存在问题,正如Roddy所提到的.

[1] C++ 98部分3.6.3.1 [basic.start.term]

[2]在C++ 11中,静态以线程安全方式初始化,这也称为Magic Statics.



1> Motti..:

函数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
如果
在foo中创建,则创建在foo

销毁在if 中销毁

C:> sample.exe 1 2
在foo中
创建如果在foo 中被
销毁则在
Destroyed中 创建

[0]由于C++ 98 [2]没有引用多线程如何在多线程环境中表现如何是未指定的,并且可能存在问题,正如Roddy所提到的.

[1] C++ 98部分3.6.3.1 [basic.start.term]

[2]在C++ 11中,静态以线程安全方式初始化,这也称为Magic Statics.


对于没有c'tor/d'tor副作用的简单类型,以与全局简单类型相同的方式初始化它们是一种简单的优化.这避免了分支,标志和破坏顺序问题.这并不是说他们的生命有任何不同.
短语"在程序终止时"并不严格正确.在动态加载和卸载的Windows dll中的静态如何?显然,C++标准根本不处理程序集(如果它确实很好的话),但是对于标准在这里所说的内容的澄清将是好的.如果包含短语"at program termination",那么从技术上讲,它将使C++的任何实现与动态卸载的程序集不一致.
@Motti我不相信标准不明确允许动态库,但到现在为止我还没有相信在这是在其执行的赔率标准的任何具体的.当然,严格讲这里的语言并没有说明静态对象不能被早期通过其他方式破坏,只是他们必须从主或调用的std ::退出返回时被销毁.虽然我认为这是一条非常好的路线.

2> Roddy..:

Motti对订单是正确的,但还有其他一些事情需要考虑:

编译器通常使用隐藏标志变量来指示本地静态是否已经初始化,并且在该函数的每个条目上都检查此标志.显然这是一个很小的性能损失,但更令人担忧的是这个标志不能保证是线程安全的.

如果你有一个如上所述的本地静态,并且从多个线程调用'foo',你可能会遇到竞争条件导致'plonk'被错误地初始化甚至多次.此外,在这种情况下,'plonk'可能会被与构造它的线程不同的线程破坏.

尽管标准说的是,我对局部静态破坏的实际顺序非常警惕,因为你可能会在无意中依赖静态在被破坏之后仍然有效,这很难被追踪.


C++ 0x要求静态初始化是线程安全的.所以要小心,但事情只会变得更好.
从C++ 11开始,这不再是一个问题.Motti的答案根据这个更新.

3> Ben Voigt..:

如果没有6.7中标准的实际规则,现有的解释并不完全完整:

具有静态存储持续时间或线程存储持续时间的所有块范围变量的零初始化在任何其他初始化发生之前执行.具有静态存储持续时间的块范围实体的常量初始化(如果适用)在首次输入其块之前执行.允许实现在允许实现静态初始化具有命名空间范围内的静态或线程存储持续时间的变量的相同条件下,使用静态或线程存储持续时间执行其他块范围变量的早期初始化.否则,在第一次控制通过其声明时初始化这样的变量; 这样的变量在初始化完成后被认为是初始化的.如果通过抛出异常退出初始化,则初始化未完成,因此下次控制进入声明时将再次尝试初始化.如果控制在初始化变量时同时进入声明,则并发执行应等待初始化完成.如果控件在初始化变量时以递归方式重新输入声明,则行为未定义.



4> Roddy..:

FWIW,Codegear C++ Builder不会按照标准按预期顺序进行破坏.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

......这是不依赖销毁令的另一个原因!


不是一个好的论据.我想说这不是使用这个编译器的论据.
嗯.如果您对生成真实的可移植代码感兴趣,而不仅仅是理论上的可移植代码,我认为知道该语言的哪些区域可能会导致问题是有用的.如果C++ Builder在处理这个问题时是独一无二的,我会感到惊讶.
我同意,除了我将其称为"编译器导致问题的原因,以及他们在语言的哪些方面所做的";-P
推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有