Нахождение C ++ статических проблем порядка инициализации - PullRequest
55 голосов
/ 02 декабря 2008

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

Edit: я получаю несколько хороших ответов о том, как решить проблему статического порядка инициализации, но это не совсем мой вопрос. Я хотел бы знать, как найти объекты, которые подвержены этой проблеме. Ответ Эвана кажется лучшим в этом отношении; Я не думаю, что мы можем использовать valgrind, но у нас могут быть инструменты анализа памяти, которые могут выполнять аналогичную функцию. Это может вызвать проблемы только в том случае, если порядок инициализации является неправильным для данной сборки, и порядок может меняться с каждой сборкой. Возможно, есть инструмент статического анализа, который бы это уловил. Наша платформа - это компилятор IBM XLC / C ++, работающий в AIX.

Ответы [ 11 ]

64 голосов
/ 03 декабря 2008

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

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

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.
        }
};
30 голосов
/ 04 августа 2010

Я просто написал немного кода, чтобы отследить эту проблему. У нас есть база кода хорошего размера (более 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" как можно ближе к верхней части вашего файла. Если вы поместите его под каким-то другим #include, вы рискуете его аварийно завершить, прежде чем определить файл, в котором вы находитесь.

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

14 голосов
/ 03 декабря 2008

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

Затем войдите в каждую функцию, чтобы получить имя файла и функции (при условии, что вы скомпилировали с отладочной информацией). Получив имена, выйдите из функции (вернитесь к _initterm) и продолжайте, пока не выйдет _initterm.

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

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

5 голосов
/ 11 января 2011

Существует код, который по существу "инициализирует" C ++, который генерируется компилятором. Простой способ найти этот код / ​​стек вызовов в то время - создать статический объект с чем-то, что разыменовывает NULL в конструкторе - сломать отладчик и немного изучить. Компилятор MSVC устанавливает таблицу указателей на функции, которая повторяется для статической инициализации. Вы должны иметь доступ к этой таблице и определять все статические инициализации, происходящие в вашей программе.

5 голосов
/ 02 декабря 2008

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

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

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

Если вам нужно беспокоиться о порядке деинициализации, верните новый объект вместо статически размещенного объекта.

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

4 голосов
/ 06 мая 2009

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

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

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

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

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

4) Повторяйте шаги 2 и 3, пока не исчерпаете список, сгенерированный на шаге 1.

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

2 голосов
/ 02 декабря 2008

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

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

1 голос
/ 06 мая 2009

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

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

1 голос
/ 02 декабря 2008

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

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

Шаблон синглтона подвергается большой критике, но ленивая конструкция «по требованию» - довольно простой способ решения большинства проблем сейчас и в будущем.

старый ...

MyObject myObject

новый ...

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

Конечно, если ваше приложение многопоточное, это может вызвать у вас больше проблем, чем было в первую очередь ...

0 голосов
/ 28 июня 2011

Другие ответы верны, я просто хотел добавить, что метод получения объекта должен быть реализован в файле .cpp, и он не должен быть статическим. Если вы реализуете его в заголовочном файле, объект будет создаваться в каждой библиотеке / фреймворке, из которого вы вызываете его ....

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...