Порядок инициализации статических переменных - PullRequest
57 голосов
/ 17 октября 2008

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

Но порядок инициализации переменных не определен в разных единицах компиляции.

Где я могу увидеть некоторые пояснения об этом заказе для gcc и MSVC (я знаю, что полагаться на это очень плохая идея - просто понять проблемы, которые могут возникнуть у нас с унаследованным кодом при переходе на новый GCC разные ОС)?

Ответы [ 6 ]

69 голосов
/ 17 октября 2008

Как вы говорите, порядок в разных единицах компиляции не определен.

Внутри той же единицы компиляции порядок четко определен: тот же порядок, что и у определения.

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

Для gcc: Проверьте ld

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

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

Существуют техники, позволяющие обойти проблему.

  • Ленивая инициализация.
  • Счетчик Шварца
  • Поместите все сложные глобальные переменные в одну единицу компиляции.

  • Примечание 1: глобальные значения:
    Свободно используется для ссылки на статические переменные продолжительности хранения, которые потенциально инициализируются до main().
  • Примечание 2: Потенциально
    В общем случае мы ожидаем, что статические переменные продолжительности хранения будут инициализированы до main, но в некоторых ситуациях компилятору разрешается откладывать инициализацию (правила сложные, подробности см. В стандарте).
17 голосов
/ 17 октября 2008

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

Однако GCC позволяет использовать init_priority для явного указания порядка для глобальных ctors:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

выводит 'ABC', как и следовало ожидать, но

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

выводит 'BAC'.

13 голосов
/ 12 сентября 2016

Поскольку вы уже знаете, что вам не следует полагаться на эту информацию, если она не является абсолютно необходимой, вот она. Мое общее наблюдение по различным цепочкам инструментов (MSVC, gcc / ld, clang / llvm и т. Д.) Заключается в том, что порядок, в котором ваши объектные файлы передаются компоновщику, является порядком, в котором они будут инициализированы.

Есть исключения из этого, и я не претендую на них всех, но вот те, с которыми я столкнулся:

1) Версии GCC до 4.7 фактически инициализируются в обратном порядке линии связи. Этот билет в GCC - это когда произошло изменение, и оно сломало множество программ, которые зависели от порядка инициализации (включая мою!).

2) В GCC и Clang использование приоритета функции конструктора может изменить порядок инициализации. Обратите внимание, что это применимо только к функциям, которые объявлены как «конструкторы» (то есть они должны запускаться так же, как и конструктор глобального объекта). Я попытался использовать такие приоритеты и обнаружил, что даже с наивысшим приоритетом для функции конструктора все конструкторы без приоритета (например, обычные глобальные объекты, функции конструктора без приоритета) будут инициализированы first . Другими словами, приоритет относится только к другим функциям с приоритетами, но настоящие первоклассные граждане - это те, у кого нет приоритета. Что еще хуже, это правило является противоположным в GCC до 4.7 из-за пункта (1) выше.

3) В Windows есть очень аккуратная и полезная функция точки входа общей библиотеки (DLL), называемая DllMain () , которая, если она определена, будет работать с параметром "fdwReason", равным DLL_PROCESS_ATTACH непосредственно после инициализации всех глобальных данных и до приложение-потребитель имеет возможность вызывать любые функции в DLL. В некоторых случаях это чрезвычайно полезно, и абсолютно не является аналогичным поведению на других платформах с GCC или Clang с C или C ++. Самое близкое, что вы найдете, - это создание функции конструктора с приоритетом (см. Пункт (2) выше), что совершенно не то же самое и не будет работать во многих случаях использования, для которых работает DllMain ().

4) Если вы используете CMake для генерации ваших систем сборки, что я часто делаю, я обнаружил, что порядок входных исходных файлов будет таким же, как у их результирующих объектных файлов, передаваемых компоновщику. Тем не менее, часто ваше приложение / DLL также связывается с другими библиотеками, и в этом случае эти библиотеки будут на линии связи после ваших исходных файлов. Если вы хотите, чтобы один из ваших глобальных объектов был самым первым для инициализации, то вам повезло, и вы можете поместить исходный файл, содержащий этот объект, первым в список исходных файлов. , Однако, если вы хотите, чтобы один из них был самым последним для инициализации (который может эффективно реплицировать поведение DllMain ()!), Тогда вы можете сделать вызов add_library () с этим одним исходным файлом для создания статическую библиотеку и добавьте полученную статическую библиотеку в качестве самой последней зависимости ссылки в вызове target_link_libraries () для вашего приложения / DLL. Будьте осторожны, в этом случае ваш глобальный объект может быть оптимизирован, и вы можете использовать флаг - whole-archive , чтобы заставить компоновщик не удалять неиспользуемые символы для этого конкретного крошечного архивного файла.

Закрывающий совет

Чтобы полностью узнать результирующий порядок инициализации вашей связанной приложения / разделяемой библиотеки, передайте --print-map в ld linker и grep для .init_array (или в GCC до 4.7, grep для .ctors). Каждый глобальный конструктор будет напечатан в том порядке, в котором он будет инициализирован, и помните, что в GCC этот порядок противоположен до 4.7 (см. Пункт (1) выше).

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

4 голосов
/ 17 октября 2008

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 - эта ссылка перемещается. этот один более стабилен, но вам придется его осмотреть.

edit: osgx предоставил лучшую ссылку .

1 голос
/ 17 октября 2008

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

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

0 голосов
/ 25 апреля 2015

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

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