Порядок инициализации статических переменных

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

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

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

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

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

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

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

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

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

Есть способы обойти проблему.

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

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

Однако 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».

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

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

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

2) В GCC и Clang использование приоритета функции конструктора может изменить порядок инициализации. Обратите внимание, что это относится только к функциям, объявленным как «конструкторы» (т. Е. Они должны выполняться так же, как и глобальный конструктор объектов). Я попытался использовать такие приоритеты и обнаружил, что даже с наивысшим приоритетом функции-конструктора сначала будут инициализированы все конструкторы без приоритета (например, обычные глобальные объекты, функции-конструкторы без приоритета). Другими словами, приоритет относится только к другим функциям с приоритетами, но настоящими гражданами первого classа являются те, кто не имеет приоритета. Чтобы это ухудшилось, это правило фактически является противоположным в 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 и интернет-форумах. Большая часть его была получена благодаря большому количеству экспериментов, и я надеюсь, что это спасет некоторых людей от времени!

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

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

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

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

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

  • вызов нестатического метода в статическом методе в Java
  • Можно ли заставить CMake создавать статическую и общую версию одной и той же библиотеки?
  • Статический инициализатор в Java
  • Создание объектов: конструкторы или статические заводские методы
  • Статические виртуальные элементы C ++?
  • Изменение частного статического конечного поля с использованием отражения Java
  • Почему все поля в интерфейсе неявно статичны и окончательны?
  • Статические блоки инициализации
  • Почему статические поля не инициализируются во времени?
  • Возвращает указатель на статическую локальную переменную?
  • Из статической библиотеки MinGW (.a) в статическую библиотеку Visual Studio (.lib)
  • Давайте будем гением компьютера.