静态对象的初始化和编译 C++

10 浏览
0 Comments

静态对象的初始化和编译 C++

如果在函数的作用域中声明变量为static,它只会被初始化一次,并且在函数调用之间保留其值。它的生命周期是什么时候?它的构造函数和析构函数什么时候被调用?\n

void foo()
{
    static string plonk = "我会在什么时候死亡?";
}

0
0 Comments

C++中的静态对象初始化和编译问题

在C++中,当我们声明一个具有静态存储期的块作用域变量时,它的初始化行为可能会导致一些问题。这个问题的出现是因为C++标准中的规则。

根据C++标准6.7节的规定,所有具有静态存储期或线程存储期的块作用域变量的零初始化将在任何其他初始化之前进行。如果适用,具有静态存储期的块作用域实体的常量初始化将在首次进入其所在块之前执行。在相同条件下,实现可以在其他具有静态或线程存储期的块作用域变量的早期初始化中执行与在命名空间作用域下静态初始化变量相同的操作。否则,这样的变量将在控制流第一次经过其声明时进行初始化;这样的变量在其初始化完成后被认为已经初始化。如果初始化过程中抛出异常导致初始化未完成,那么将在下一次控制流进入该声明时再次尝试初始化。如果在变量初始化过程中并发地再次进入声明,那么并发执行将等待初始化完成。如果在变量初始化过程中通过递归方式再次进入声明,行为将是未定义的。

根据上述规则,我们可以看出静态对象的初始化可能会出现问题。如果在初始化过程中抛出异常,初始化将不会完成,下次控制流进入该声明时会再次尝试初始化。如果在初始化过程中并发地再次进入声明,将会等待初始化完成。如果通过递归方式再次进入声明,行为将是未定义的。

为了解决这个问题,我们可以采取一些措施。首先,我们可以确保在静态对象的初始化过程中不会抛出异常。这可以通过在初始化代码中使用try-catch块来捕获异常,并处理它们。另外,我们可以使用互斥锁来保证在初始化过程中并发进入声明的控制流会等待初始化完成。这可以通过在代码中使用互斥锁来实现。最后,我们应该避免在静态对象的初始化过程中递归地再次进入声明,以避免未定义的行为。

总结起来,C++中的静态对象初始化和编译问题是由于C++标准中的规则导致的。为了解决这个问题,我们可以避免在初始化过程中抛出异常,使用互斥锁来处理并发进入声明的控制流,以及避免在初始化过程中递归地再次进入声明。这些措施可以确保静态对象的正确初始化和编译。

0
0 Comments

静态对象的初始化和编译问题是由于编译器在函数的每次调用中使用一个隐藏的标志变量来指示局部静态变量是否已经被初始化,而这个标志变量在多线程环境下并不保证线程安全。如果一个函数中有局部静态变量,并且该函数被多个线程调用,可能会出现竞争条件,导致局部静态变量被错误地初始化或者被多次初始化。此外,在这种情况下,局部静态变量的析构可能由不同的线程执行。

虽然标准规定了局部静态变量的创建顺序,但是仍然需要谨慎对待局部静态变量的析构顺序,因为你可能会无意中依赖于一个已经被析构的静态对象仍然是有效的,这种错误很难追踪到。

C++0x标准要求静态对象的初始化是线程安全的,所以虽然需要谨慎处理,但情况只会变得更好。

可以通过一些策略来避免析构顺序的问题。静态/全局对象(如单例等)在其方法体中不应访问其他静态对象,而应仅在构造函数中访问并存储引用/指针以供后续在方法中使用。这并不完美,但应该能解决99%的问题,而那些未被解决的情况很明显是可疑的,应该在代码审查中发现。然而,这仍然不是一个完美的解决方案,因为这个策略无法在语言中强制执行。

我是一个新手,但为什么这个策略不能在语言中强制执行呢?

自从C++11以后,这个问题就不再存在了。Motti的回答已经进行了更新。

0
0 Comments

静态对象的初始化和编译(static object intialization and compilation)是C++中的一个问题。问题的出现是因为函数内的静态变量的生命周期在程序流程第一次遇到其声明时开始,直到程序终止时结束。这意味着运行时必须进行一些簿记工作,以便仅在实际构造了该变量时才对其进行析构。

另外,由于标准规定静态对象的析构函数必须按照它们构造完成的相反顺序运行,并且构造顺序可能取决于特定的程序运行,因此必须考虑构造顺序。

下面是一个示例代码:

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);
}

上述代码的输出结果为:

Created in foo
Destroyed in foo

Created in if
Created in foo
Destroyed in foo
Destroyed in if

Created in foo
Created in if
Destroyed in if
Destroyed in foo

上述代码中的注释[0]指出,由于C++98对多线程没有引用,因此在多线程环境中的行为是未指定的,可能会有问题。

注释[1]指出,根据C++98规范中的3.6.3.1节,静态对象的析构函数必须按照它们构造完成的相反顺序运行。

注释[2]指出,C++11中的静态对象以线程安全的方式进行初始化,这也被称为“魔术静态”。

对于没有构造函数/析构函数副作用的简单类型,将它们初始化为与全局简单类型相同的方式是一种简单的优化。这样可以避免分支、标志和析构顺序问题。这并不意味着它们的生命周期有任何不同。

如果函数可能被多个线程调用,那么是否意味着在C++98中必须通过互斥锁保护静态声明?

以上是关于静态对象初始化和编译的一些问题和解决方法的总结。需要注意的是,在C++98中,静态对象的析构顺序比纯LIFO更为复杂。

另外,需要指出的是,C++标准中没有明确允许动态库,但直到现在,我也不相信标准中有任何与其实现相冲突的具体内容。当然,严格来说,这里的语言并没有声明静态对象不能通过其他方式提前销毁,只是它们必须在从main返回或调用std::exit时销毁。这是一个微妙的界限。

最后,需要注意的是,第一个段落只适用于具有非空初始化的对象。对于其他对象(例如`static int x = 1;`),生命周期是在获取存储空间时开始的,对于静态存储期的对象来说,这是在程序启动时。

0