C++ 中静态 STL 容器的双重初始化图书馆

发布于 2024-10-26 10:34:35 字数 2342 浏览 5 评论 0原文

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

我有一个自定义 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");

myapp.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] 如何防止“静态初始化顺序惨败”?'

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

There are a few good questions and answers here around the "static initialization order fiasco", but I seem to have hit against yet another expression of it, specially ugly because it does not crash but looses and leaks data.

I have a custom C++ library and an application that links against it. There is an static STL container in the library that registers all instances of a class. Those instances happen to be static variables in the application.

As a result of the "fiasco" (I believe), we get the container filled with the application instances during application initialization, then the library gets to initialize and the container is reset (probably leaking memory), ending up only with the instances from the library.

This is how I reproduced it with simplified code:

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

myapp.cpp:

#include "mylib.hpp"

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

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

Compile the objects with:

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

Run 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

Run 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

Here comes the question, the static vector was re-initialized, or used before initialization? Is this an expected behavior?

If I 'ar' the library as 'mylib.a' (ar rcs mylib.a mylib.o), the problem does not happen, but probably because there is only one valid order to link to the .a and it is by having the library in the last place, as for myapp1 here.

But in our real application, a more complex one with many object files and a few static (.a) libraries sharing a few static registries, the problem is happening and the only way we managed to solve it so far is by applying '[10.15] How do I prevent the "static initialization order fiasco"?'.

(I am still researching in our somewhat complex build system to see if we are linking correctly).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

柠檬色的秋千 2024-11-02 10:34:35

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

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

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

在直接使用 registry 的地方,让它调用 GetRegistry代码>.

One way to work around initialization order problems is to move the static variables from global scope to local scope.

Instead of having a registry variable within the class, put it into a function:

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

In the places where you would have used registry directly, have it call GetRegistry.

Spring初心 2024-11-02 10:34:35

如果您给 vector 一个自定义构造函数,您将看到它确实只被调用一次,但在 myapp2 中您正在使用 registry首先未初始化,然后对其进行初始化(“删除”内部的所有内容),然后再次填充。它没有段错误只是运气:)

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

If you give vector<string> a custom constructor you will see, that it is indeed called only once, but in myapp2 you are using registry uninitialized first, then it gets initialized ("removing" everything that's inside) and then filled again. That it doesn't segfault is just luck :)

I can't tell which part of the standard says something about this behaviour, but IMHO you should /never/ let static variables depend on each other. You might use a Meyers singleton for example for registry.

十六岁半 2024-11-02 10:34:35

您正在使用两种已知的技术。

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

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

我还将“终结器”添加到“初始化器”,并替换“注册”的标识符。

祝你好运。

You are using 2 known techiques.

(1) The "module/library/namespace" as a "device" pattern

(2) Custom type registration, with a static class.

Done something similar with "Object Pascal" and "Plain C". I have several files, each file working as a module / namespace, with typedefs, classes, functions.
Additionally, each "namespace" had 2 special methods (same signature or prototype), that simulate connecting a device, and disconnecting a device. Already tryed to call those methods automatically, but executing order also went wrong.

Static, Singleton classes can become a mess. I suggest, forget using macros or preprocessor/compiler and call your initialization / finalization methods yourself.

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

Check that this code is more complex and verbose that yours,
but, in my experience, is more stable.

I also add "finalizer" to "initializer", and replace identifier for "Register".

Good Luck.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文