Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

Существуют методы, позволяющие обойти эту проблему.

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

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

Другие советы

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

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

Закрывающий Наконечник

Чтобы точно знать результирующий порядок инициализации вашего связанного приложения / общей библиотеки, передайте --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 - эта ссылка перемещается по кругу.это один он более стабилен, но вам придется поискать его по сторонам.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top