7

关于“静态初始化顺序惨败”,这里有一些很好的问题和答案,但我似乎遇到了它的另一种表达方式,特别丑陋,因为它不会崩溃但会丢失和泄漏数据。

我有一个自定义 C++ 库和一个链接到它的应用程序。库中有一个静态 STL 容器,用于注册类的所有实例。这些实例恰好是应用程序中的静态变量。

由于“惨败”(我相信),我们在应用程序初始化期间让容器充满了应用程序实例,然后库开始初始化并且容器被重置(可能泄漏内存),最终只有来自的实例图书馆。

这就是我用简化代码复制它的方式:

mylib.hpp:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class MyLibClass {
    static vector<string> registry;
    string myname;
  public:
    MyLibClass(string name);
};

mylib.cpp:

#include "mylib.hpp"

vector<string> MyLibClass::registry;

MyLibClass::MyLibClass(string name)
: myname(name)
{
    registry.push_back(name);
    for(unsigned i=0; i<registry.size(); i++)
        cout << " ["<< i <<"]=" << registry[i];
    cout << endl;
}

MyLibClass l1("mylib1");
MyLibClass l2("mylib2");
MyLibClass l3("mylib3");

我的应用程序.cpp:

#include "mylib.hpp"

MyLibClass a1("app1");
MyLibClass a2("app2");
MyLibClass a3("app3");

int main() {
    cout << "main():" << endl;
    MyLibClass m("main");
}

编译对象:

g++ -Wall -c myapp.cpp mylib.cpp
g++ myapp.o mylib.o -o myapp1
g++ mylib.o myapp.o -o myapp2

运行 myapp1:

$ ./myapp1
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main

运行 myapp2:

$ ./myapp2
 [0]=app1
 [0]=app1 [1]=app2
 [0]=app1 [1]=app2 [2]=app3
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main

问题来了,静态向量是重新初始化的,还是在初始化之前使用的?这是预期的行为吗?

如果我将库“ar”为“mylib.a”(ar rcs mylib.a mylib.o),则问题不会发生,但可能是因为只有一个有效的订单可以链接到 .a,而且它是通过图书馆在最后,至于 myapp1 这里。

但是在我们的实际应用程序中,一个包含许多目标文件和一些静态 (.a) 库的更复杂的应用程序共享一些静态注册表,问题正在发生,到目前为止,我们设法解决它的唯一方法是应用'[10.15 ] 如何防止“静态初始化命令惨败”?.

(我仍在研究我们有些复杂的构建系统,看看我们是否正确链接)。

4

3 回答 3

4

解决初始化顺序问题的一种方法是将静态变量从全局范围移动到局部范围。

不要registry在类中使用变量,而是将其放入函数中:

vector<string> & MyLibClass::GetRegistry()
{
    static vector<string> registry;
    return registry;
}

在你会registry直接使用的地方,让它调用GetRegistry.

于 2011-03-24T17:13:51.803 回答
3

如果您提供vector<string>自定义构造函数,您会看到它确实只调用了一次,但是在myapp2registry首先使用未初始化的情况下,它会被初始化(“删除”内部的所有内容)然后再次填充。它没有段错误只是运气:)

我不知道标准的哪一部分说明了这种行为,但恕我直言,你应该/从不/让静态变量相互依赖。例如,您可以将 Meyers 单例用于注册表。

于 2011-03-24T17:06:16.350 回答
0

您正在使用 2 种已知技术。

(1) “模块/库/命名空间”作为“设备”模式

(2) 自定义类型注册,带静态类。

用“Object Pascal”和“Plain C”做了类似的事情。我有几个文件,每个文件都作为模块/命名空间工作,带有 typedef、类、函数。此外,每个“命名空间”都有 2 个特殊方法(相同的签名或原型),模拟连接设备和断开设备。已经尝试自动调用这些方法,但执行顺序也出错了。

静态的、单例的类可能会变得一团糟。我建议,忘记使用宏或预处理器/编译器,自己调用初始化/终结方法

----
mylib.hpp
----

class MyLibClass {
  public:
    Register(string libraryName);
    UnRegister(string libraryName);
};

// don't execute the "custom type registration here"

-----
mynamespace01.cpp
-----
#include "mylib.hpp"

void mynamespace01_otherstuff() { ... }

// don't execute registration
void mynamespace01_start() { 
  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01");
}

void mynamespace01_finish()
{ 
  if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01");
}

-----
mynamespace02.cpp
-----
#include "mylib.hpp"

// check, "2" uses "1" !!!
#include "mynamespace01.hpp"

void mynamespace02_otherstuff() { ... }

// don't execute registration !!!
void mynamespace02_start() { 
  // check, "2" uses "1" !!!
  void mynamespace01_start();

  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02");

  void mynamespace02_start();  
}

void mynamespace02_finish(){ 
  void mynamespace02_finish();

  if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02");

  // check, "2" uses "1" !!!
  void mynamespace02_start();  
}

-----
myprogram.cpp
-----

#include "mynamespace01.hpp"
#include "mynamespace02.hpp"

void myprogram_otherstuff() { ... }

// don't execute registration !!!
void myprogram_start() { 
  // check, "2" uses "1" !!!
  mynamespace01_start();
  mynamespace02_start();

  if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram");
}
void myprogram_finish() {
  if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram");

  // check, "2" uses "1" !!!  
  mynamespace01_finish();
  mynamespace02_finish();  
}

void main () {
  // all registration goes here !!!:

  // "device" initializers order coded by hand:
  myprogram_start();

  // other code;

  // "device" finalizers order inverse coded by hand:  
  myprogram_finish();
}
-----

检查此代码是否比您的代码更复杂和冗长,但根据我的经验,它更稳定。

我还将“finalizer”添加到“initializer”,并替换“Register”的标识符。

祝你好运。

于 2011-03-24T17:26:32.670 回答