2008-10-17 6 views
47

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

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

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

ответ

59

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

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

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

Для GCC: Проверьте л.д.

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

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

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

  • Ленивая инициализация.
  • Schwarz Counter
  • Поместите все сложные глобальные переменные внутри одного и того же модуля компиляции.
+5

Мои предпочтения касаются всех глобальных переменных в одном модуле компиляции ... :-) – paercebal 2008-10-17 09:00:13

+9

Предпочтительно вообще не нужны глобальные переменные. – 2008-10-17 09:02:32

+4

Вы оба правы, но, к сожалению, это было неизвестно поколениям программистов, которые написали тонны библиотек и сторонний код, который мы использовали для использования ... – 2009-12-24 08:34:13

16

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

Однако GCC действительно позволяет вам use init_priority to explicitly specify the ordering для глобальных 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.

1

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

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

0

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

5

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

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

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

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

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

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

Закрытие Совет

Чтобы быть абсолютно уверены в результате порядок инициализации вашего связанного приложения/разделяемой библиотеки, проходят --print-карту Л.Д. линкера и Grep для .init_array (или в GCC до 4,7, grep для .ctors). Каждый глобальный конструктор будет напечатан в том порядке, в котором он будет инициализирован, и помните, что порядок противоположный в GCC до 4,7 (см. Пункт (1) выше).

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

Смежные вопросы