Воспроизведение статического порядка инициализации фиаско в C ++ - PullRequest
1 голос
/ 04 декабря 2009

Я читал о фиаско статического порядка инициализации в C ++, касающегося сбоя приложения. Я думаю, что понял это, но все еще есть несколько вопросов:
1) Если я хочу воспроизвести эту проблему, как я могу это сделать (чтобы моя программа аварийно завершилась)? Я хотел бы написать тестовую программу для воспроизведения аварии. Можете ли вы предоставить исходный код, если это возможно?
2) Я прочитал эту C ++ FAQ Lite статью, в которой говорится, что у него два статических объекта, x и y, в двух разных файлах, а y вызывает метод x. Как это возможно, поскольку глобальные статические члены имеют область действия уровня файла?
3) Эта проблема очень опасна, есть ли попытки исправить ее на уровне компилятора?
4) Сколько раз вы, специалисты по C ++, сталкивались с этой проблемой в реальной жизни?

Ответы [ 3 ]

3 голосов
/ 04 декабря 2009

РЕДАКТИРОВАТЬ: скорректированы, чтобы сделать его более точным в свете комментариев.

Хороший пример будет выглядеть так:

// A.cpp
#include "A.h"
std::map<int, int> my_map;

// A.h
#include <map>
extern std::map<int, int> my_map;

// B.cpp
#include "A.h"
class T {
public:
    T() { my_map.insert(std::make_pair(0, 0)); }
};

T t;

int main() {
}

Проблема в том, что экземпляр t может быть создан до объекта my_map. Таким образом, вставка может происходить на еще не построенном объекте. Причинение аварии.

Простое решение состоит в том, чтобы сделать что-то вроде этого:

// A.h
#include <map>
std::map<int, int> &my_map()

// A.cpp
#include "A.h"
std::map<int, int> &my_map() {
    // initialized on first use
    static std::map<int, int> x;
    return x;
}

// B.cpp
#include "A.h"

class T {
public:
    T() { my_map().insert(std::make_pair(0, 0)); }
};

T t;

int main() {
}

Получая доступ к статическому объекту через функцию, мы можем гарантировать порядок инициализации, поскольку статика области действия функции инициализируется при первом использовании. Таким образом, сначала создается объект t, он вызывает my_map(), который создает статический объект карты при первом запуске, а затем возвращает ссылку на него.

2 голосов
/ 04 декабря 2009

1) Вы должны либо изучить код запуска времени выполнения, чтобы увидеть, как он выбирает порядок инициализации, либо немного поэкспериментировать. Вы можете улучшить вероятность появления ошибки, создав зависимость инициализации между более чем двумя объектами, возможно, 3 или 4.

2) Просто создайте экземпляр объекта на уровне файла:

OBJECT_TYPE x;

3) Ни один компилятор не обращается к этому, о котором я знаю. Это потребовало бы обнаружения в или после связывания.

4) На практике этого легко избежать: сделайте все инициализации самодостаточными.

1 голос
/ 04 декабря 2009

"1) Если я хочу воспроизвести эту проблему, как я могу это сделать (чтобы моя программа вылетала)? Я хотел бы написать тестовую программу для воспроизведения сбоя. Можете ли вы предоставить исходный код, если это возможно ? "

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

Возможно, вы могли бы предположить, что компоновщик инициализирует все глобальные переменные из одной единицы перевода перед всеми глобальными переменными из другого. Поэтому настройте два исходных файла A и B, с глобальными переменными A1 и A2 в A и B1 и B2 в B. Затем используйте B1 (под этим я подразумеваю «сделать что-то, что не получится, если B1 не был инициализирован») в конструкторе A1, и используйте A2 в конструкторе B2. Также используйте A1 в конструкторе A2 (и объявите их в том порядке в A). Тогда единственный порядок, который не потерпит неудачу, это B1, A1, A2, B2, который вы можете себе представить - это довольно маловероятный выбор для реализации. В конкретной реализации, если это как-то удастся, переключите вещи так, чтобы A2 использовал B2 вместо B2, используя A2, и просто надеемся, что это не изменит порядок инициализации.

Конечно, вы также можете использовать B2 в конструкторе B1 (и объявить их в этом порядке в B), чтобы гарантировать сбой независимо от порядка инициализации. Но тогда это не будет фиаско статического порядка инициализации, это будет просто принципиально нарушенная циклическая зависимость.

"2) Я прочитал эту статью C ++ FAQ Lite, в которой говорится, что у него два статических объекта, x и y, в двух разных файлах, а y вызывает метод x. Как это возможно, поскольку глобальные статические члены имеют область действия на уровне файлов?"

Например, объявите их extern в обеих единицах перевода (возможно, используя общий заголовок). Объем, связь и продолжительность хранения - разные вещи.

"3) Эта проблема очень опасна, есть ли попытки исправить ее на уровне компилятора?"

Не то, что я знаю. Я почти уверен, что проблема заключается в том, чтобы определить, использует ли объект X (в том смысле, который я определил выше) объект Y в своем конструкторе, поэтому построение графа зависимостей во время ссылки и t-сортировка будут Лучшая частичная мера.

"4) Сколько раз вы, специалисты по C ++, сталкивались с этой проблемой в реальной жизни?"

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

Это становится трудным, только если вы реализуете API, который определяет глобальные переменные, такие как std::out. Есть трюк, который вы можете использовать, когда вы определяете фиктивную переменную области файла в том же заголовке, который объявляет глобальный. Хотя я не могу вспомнить имя.

...