Нахождение C++ статические проблемы порядка инициализации

59
задан Fred Larson 8 January 2013 в 17:49
поделиться

10 ответов

Решение порядка инициализации:

Прежде всего, это - просто временное обходное решение, потому что у Вас есть глобальные переменные, что Вы пытаетесь избавиться от, но просто еще не имели времени (Вы собираетесь избавиться от них, в конечном счете не Вы?:-)

class A
{
    public:
        // Get the global instance abc
        static A& getInstance_abc()  // return a reference
        {
            static A instance_abc;
            return instance_abc;
        }
};

Это гарантирует, что он инициализируется на первом использовании и уничтожается, когда приложение завершается.

Многопоточная проблема:

C++ 11 делает гарантия, что это ориентировано на многопотоковое исполнение:

В§6.7 [stmt.dcl] p4
, Если управление вводит объявление одновременно, в то время как переменная инициализируется, параллельное выполнение должно ожидать завершения инициализации.

Однако C++ 03 делает не официально гарантия, что конструкция статических функциональных объектов ориентирована на многопотоковое исполнение. Так технически getInstance_XXX() метод нужно охранять с критическим разделом. С другой стороны gcc имеет явный патч как часть компилятора, который гарантирует, что каждый статический функциональный объект будет только инициализирован однажды даже в присутствии потоков.

отметьте: не Делают , используют проверенный дважды шаблон блокировки , чтобы стараться избегать стоимости блокировки. Это не будет работать в C++ 03.

проблемы Создания:

На создании, нет никаких проблем, потому что мы гарантируем, что оно создается, прежде чем оно сможет использоваться.

проблемы Разрушения:

существует потенциальная проблема доступа к объекту после того, как это было уничтожено. Это только происходит при доступе к объекту от деструктора другой глобальной переменной (глобальным, я обращаюсь к любой нелокальной статической переменной).

решение состоит в том, чтобы удостовериться, что Вы вызываете порядок разрушения.
Помнят, что порядок разрушения является точной инверсией порядка конструкции. Таким образом, при доступе к объекту в деструкторе необходимо гарантировать, что объект не был уничтожен. Чтобы сделать это, необходимо просто гарантировать, что объект полностью создается, прежде чем вызывающий объект создается.

class B
{
    public:
        static B& getInstance_Bglob;
        {
            static B instance_Bglob;
            return instance_Bglob;;
        }

        ~B()
        {
             A::getInstance_abc().doSomthing();
             // The object abc is accessed from the destructor.
             // Potential problem.
             // You must guarantee that abc is destroyed after this object.
             // To guarantee this you must make sure it is constructed first.
             // To do this just access the object from the constructor.
        }

        B()
        {
            A::getInstance_abc();
            // abc is now fully constructed.
            // This means it was constructed before this object.
            // This means it will be destroyed after this object.
            // This means it is safe to use from the destructor.
        }
};
66
ответ дан Cody Gray 24 November 2019 в 18:15
поделиться

В зависимости от Вашего компилятора можно поместить точку останова в коде инициализации конструктора. В Visual C++ это эти _initterm функция, которой дают запуск и указатель конца списка функций для вызова.

Тогда шаг в каждую функцию для получения имени файла и имени функции (принимающий Вы скомпилировали с информацией об отладке на). Как только Вы имеете имена, шаг из функции (назад [до 111]) и продолжаете до _initterm выходы.

, Который дает Вам весь статические инициализаторы, не только те в Вашем коде - это - самый легкий способ получить исчерпывающий список. Можно отфильтровать тех, Вы не имеете никакого контроля над (такого как те в сторонних библиотеках).

теория содержит для других компиляторов, но название функции и возможность отладчика могут измениться.

14
ответ дан paxdiablo 24 November 2019 в 18:15
поделиться

возможно, используйте valgrind для нахождения использования неинициализированной памяти. Самое хорошее решение "статического фиаско порядка инициализации" состоит в том, чтобы использовать статическую функцию, которая возвращает экземпляр объекта как это:

class A {
public:
    static X &getStatic() { static X my_static; return my_static; }
};

Этот способ, которым Вы получаете доступ к своему статическому объекту, путем вызова getStatic, это гарантирует, что инициализируется на первом использовании.

, Если необходимо волноваться о порядке de-инициализации, возвратите объект new'd вместо статически выделенного объекта.

РЕДАКТИРОВАНИЕ: удаленный избыточный статический объект, я не знаю, почему, но я смешал и соответствовал двум методам наличия помех вместе в моем исходном примере.

5
ответ дан Evan Teran 24 November 2019 в 18:15
поделиться

Замените все глобальные объекты глобальными функциями, которые возвращают ссылку на объект, объявленный статичным в функции. Это не ориентировано на многопотоковое исполнение, поэтому если Ваше приложение является многопоточным, Вам, возможно, понадобились бы некоторые приемы как pthread_once или глобальная блокировка. Это гарантирует, что все инициализируется, прежде чем это будет использоваться.

Теперь, любая Ваша программа работы (ура!) или иначе это находится в бесконечном цикле, потому что у Вас есть круговая зависимость (необходимая модернизация), или иначе Вы идете дальше к следующей ошибке.

2
ответ дан Steve Jessop 24 November 2019 в 18:15
поделиться

Первая вещь, которую необходимо сделать, входят в список всех статических объектов, которые имеют нетривиальных конструкторов.

, Учитывая, что, или необходимо включиться через них по одному, или просто заменить их всех объектами шаблона "одиночка".

шаблон "одиночка" наталкивается на большую критику, но ленивым "как требуется" конструкция является довольно простой способ решить большинство проблем теперь и в будущем.

старый...

MyObject myObject

новый...

MyObject &myObject()
{
  static MyObject myActualObject;
  return myActualObject;
}

, Конечно, если Ваше приложение является многопоточным, это может вызвать Вас больше проблем, чем Вы имели во-первых...

1
ответ дан Roddy 24 November 2019 в 18:15
поделиться

Некоторые из этих ответов теперь устарели. Ради людей, происходящих из поисковых систем, как я:

На Linux и в другом месте, нахождение экземпляров этой проблемы возможно через AddressSanitizer Google .

AddressSanitizer является частью LLVM, запускающегося с версии 3.1 и части GCC, запускающегося с версии 4.8

, Вы тогда сделали бы что-то как следующее:

$ g++ -fsanitize=address -g staticA.C staticB.C staticC.C -o static 
$ ASAN_OPTIONS=check_initialization_order=true:strict_init_order=true ./static 
=================================================================
==32208==ERROR: AddressSanitizer: initialization-order-fiasco on address ... at ...
    #0 0x400f96 in firstClass::getValue() staticC.C:13
    #1 0x400de1 in secondClass::secondClass() staticB.C:7
    ...

Посмотрите здесь для получения дополнительной информации: https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco

1
ответ дан 24 November 2019 в 18:15
поделиться

Мы столкнулись с некоторыми проблемами с фиаско статического порядка инициализации , , и я ищу способы прочесать через весь код, чтобы найти возможные вхождения. Любые предложения о том, как сделать это эффективно?

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

1) Найдите все глобалы, которые имеют нетривиальные конструкторы, и поместите их в список.

2) Для каждого из этих нетривиально построенных объектов сгенерируйте все дерево потенциальных функций, вызываемое их конструкторами.

] 3) Пройдитесь по дереву функций нетривиально-конструктора и, если код ссылается на любые другие нетривиально сконструированные глобалы (которые довольно удобны в списке, который вы сгенерировали на первом шаге),

4
ответ дан 24 November 2019 в 18:15
поделиться

Gimpel Software (www.gimpel.com) утверждает, что их инструменты статического анализа PC-Lint / FlexeLint обнаружат такие проблемы.

I Я имел хороший опыт работы со своими инструментами, но не с этой конкретной проблемой, поэтому я не могу поручиться за то, насколько они могут помочь.

1
ответ дан 24 November 2019 в 18:15
поделиться

Если ваш проект находится в Visual Studio (я пробовал это с VC ++ Express 2005 и с Visual Studio 2008 Pro):

  1. Открыть представление класса (Главное меню-> Вид-> Представление классов)
  2. Разверните каждый проект в своем решении и нажмите «Глобальные функции и переменные»

. Это должно дать вам достойный список всех глобальных объектов, которые потерпели фиаско .

В конце концов, лучший подход - попытаться удалить эти объекты из вашего проекта (иногда проще сказать, чем сделать).

0
ответ дан 24 November 2019 в 18:15
поделиться

Я просто написал немного кода, чтобы отследить эту проблему. У нас есть кодовая база хорошего размера (более 1000 файлов), которая отлично работала в Windows / VC ++ 2005, но вылетала при запуске в Solaris / gcc. Я написал следующий файл .h:

#ifndef FIASCO_H
#define FIASCO_H

/////////////////////////////////////////////////////////////////////////////////////////////////////
// [WS 2010-07-30] Detect the infamous "Static initialization order fiasco"
// email warrenstevens --> [initials]@[firstnamelastname].com 
// read --> http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 if you haven't suffered
// To enable this feature --> define E-N-A-B-L-E-_-F-I-A-S-C-O-_-F-I-N-D-E-R, rebuild, and run
#define ENABLE_FIASCO_FINDER
/////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef ENABLE_FIASCO_FINDER

#include <iostream>
#include <fstream>

inline bool WriteFiasco(const std::string& fileName)
{
    static int counter = 0;
    ++counter;

    std::ofstream file;
    file.open("FiascoFinder.txt", std::ios::out | std::ios::app);
    file << "Starting to initialize file - number: [" << counter << "] filename: [" << fileName.c_str() << "]" << std::endl;
    file.flush();
    file.close();
    return true;
}

// [WS 2010-07-30] If you get a name collision on the following line, your usage is likely incorrect
#define FIASCO_FINDER static const bool g_psuedoUniqueName = WriteFiasco(__FILE__);

#else // ENABLE_FIASCO_FINDER
// do nothing
#define FIASCO_FINDER

#endif // ENABLE_FIASCO_FINDER

#endif //FIASCO_H

и в каждый .cpp файл в решении, я добавил это:

#include "PreCompiledHeader.h" // (which #include's the above file)
FIASCO_FINDER
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"

Когда вы запустите свое приложение, вы получите выходной файл, подобный этому:

Starting to initialize file - number: [1] filename: [p:\\OneFile.cpp]
Starting to initialize file - number: [2] filename: [p:\\SecondFile.cpp]
Starting to initialize file - number: [3] filename: [p:\\ThirdFile.cpp]

В случае сбоя виновник должен быть в последнем указанном файле .cpp. И, по крайней мере, это даст вам хорошее место для установки точек останова, так как этот код должен быть абсолютным первым вашего кода для выполнения (после чего вы можете пройти через свой код и увидеть все глобальные объекты, которые инициализируются).

Примечания:

  • Важно, чтобы вы поместили макрос «FIASCO_FINDER» как можно ближе к началу файла.Если вы поместите его под другим #includes, вы рискуете его сбой до идентификации файла, в котором вы находитесь.

  • Если вы используете Visual Studio и предварительно скомпилированные заголовки, добавьте эту дополнительную строку макроса в все ваши файлы .cpp можно быстро выполнить с помощью диалогового окна «Найти и заменить», чтобы заменить существующий #include «precompiledheader.h» тем же текстом плюс строку FIASCO_FINDER (если вы отметите «регулярные выражения» , вы можете использовать "\ n" для вставки многострочного замещающего текста)

31
ответ дан 24 November 2019 в 18:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: