Двойная инициализация статического контейнера STL в библиотеке C ++ - PullRequest
7 голосов
/ 24 марта 2011

Здесь есть несколько хороших вопросов и ответов о «фиаско статического порядка инициализации», но я, кажется, натолкнулся на еще одно его выражение, особенно уродливое, потому что оно не падает, а теряет и пропускает данные.

У меня есть пользовательская библиотека 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

Здесь возникает вопрос, статический вектор был-инициализирован или используется перед инициализацией?Является ли это ожидаемым поведением?

Если я "обозначаю" библиотеку как "mylib.a" (ar rcs mylib.a mylib.o), проблема не возникает, но, вероятно, потому, что естьтолько один действительный порядок для ссылки на .a, и это при наличии библиотеки на последнем месте, как для myapp1 здесь.

Но в нашем реальном приложении более сложный, с большим количеством объектных файлов и несколькимистатические (.a) библиотеки, совместно использующие несколько статических реестров, проблема возникает, и до сих пор нам удалось ее решить только путем применения '[10.15] Как предотвратить «фиаско статического порядка инициализации»?'.

(я все еще исследую нашу довольно сложную систему сборки, чтобы проверить, правильно ли мы связываемся).

Ответы [ 3 ]

4 голосов
/ 24 марта 2011

Один из способов обойти проблемы порядка инициализации - переместить статические переменные из глобальной области в локальную область.

Вместо использования переменной registry в классе, поместите ее в функцию:

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

В тех местах, где вы бы использовали registry напрямую, попросите позвонить GetRegistry.

3 голосов
/ 24 марта 2011

Если вы предоставите vector<string> пользовательский конструктор, вы увидите, что он действительно вызывается только один раз, но в myapp2 вы сначала используете registry неинициализированным, затем он инициализируется («удаляя» все, что внутри) и затем снова заполнен. То, что это не segfault - это просто удача:)

Я не могу сказать, какая часть стандарта что-то говорит об этом поведении, но ИМХО, вы должны / никогда / не позволять статическим переменным зависеть друг от друга. Вы можете использовать синглтон Meyers, например, для регистрации.

0 голосов
/ 24 марта 2011

Вы используете 2 известных метода.

(1) «Модуль / библиотека / пространство имен» как шаблон «устройства»

(2) Регистрация пользовательских типов со статическим классом.

Сделано что-то похожее с "Object Pascal" и "Plain C".У меня есть несколько файлов, каждый из которых работает как модуль / пространство имен, с typedefs, классами, функциями.Кроме того, каждое «пространство имен» имело 2 специальных метода (одна и та же подпись или прототип), которые имитируют подключение устройства и отключение устройства.Уже пытались вызвать эти методы автоматически, но выполнение заказа также пошло не так.

Статические классы Singleton могут стать беспорядком.Я предлагаю, забудьте об использовании макросов или препроцессора / компилятора и сами вызовите ваши методы инициализации / финализации .

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

Убедитесь, что этот код более сложный и подробный, чем ваш, но, по моемуболее стабильный.

Я также добавляю «финализатор» к «инициализатору» и заменяю идентификатор на «Регистр».

Удачи.

...