Инициализация CRT
В этой статье описывается, как CRT инициализирует глобальное состояние в машинном коде.
По умолчанию компоновщик включает библиотеку CRT, которая предоставляет собственный код запуска. Этот код запуска инициализирует библиотеку CRT, вызывает глобальные инициализаторы и затем вызывает предоставленную пользователем функцию main
для консольных приложений.
Однако это возможно, но не рекомендуется использовать поведение компоновщика для конкретного майкрософт, чтобы вставить собственные глобальные инициализаторы в определенном порядке. Этот код не переносим и поставляется с некоторыми важными оговорками.
Инициализация глобального объекта
Рассмотрим следующий код C++ (C не разрешает этот код, так как он не разрешает вызов функции в константном выражении).
int func(void)
{
return 3;
}
int gi = func();
int main()
{
return gi;
}
В соответствии со стандартом C/C++, функцию func()
необходимо вызывать перед выполнением функции main()
. Однако кто ее вызывает?
Одним из способов определения вызывающего средства является установка точки останова, func()
отладка приложения и проверка стека. Возможно, так как исходный код CRT включен в Visual Studio.
При просмотре функций в стеке вы увидите, что CRT вызывает список указателей функций. Эти функции похожи на func()
конструкторы для экземпляров классов.
CRT получает список указателей функций из компилятора Microsoft C++. Когда компилятор видит глобальный инициализатор, он создает динамический инициализатор в .CRT$XCU
разделе, где CRT
имя раздела и XCU
является именем группы. Чтобы получить список динамических инициализаторов, выполните команду dumpbin /all main.obj
и выполните поиск в .CRT$XCU
разделе. Команда применяется только в том случае, если main.cpp
компилируется как файл C++, а не C-файл. Он должен быть похож на этот пример:
SECTION HEADER #6
.CRT$XCU name
0 physical address
0 virtual address
4 size of raw data
1F2 file pointer to raw data (000001F2 to 000001F5)
1F6 file pointer to relocation table
0 file pointer to line numbers
1 number of relocations
0 number of line numbers
40300040 flags
Initialized Data
4 byte align
Read Only
RAW DATA #6
00000000: 00 00 00 00 ....
RELOCATIONS #6
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- -------
00000000 DIR32 00000000 C ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))
CRT определяет два указателя:
__xc_a
в.CRT$XCA
.__xc_z
в.CRT$XCZ
.
Ни у одной группы нет других символов, кроме __xc_a
и __xc_z
.
Теперь, когда компоновщик считывает различные .CRT
подразделы (часть после), $
она объединяет их в одном разделе и упорядочивает их в алфавитном порядке. Это означает, что определяемые пользователем глобальные инициализаторы (которые компилятор Microsoft C++ помещает в .CRT$XCU
) всегда приходят после .CRT$XCA
и раньше .CRT$XCZ
.
Раздел должен выглядеть примерно так:
.CRT$XCA
__xc_a
.CRT$XCU
Pointer to Global Initializer 1
Pointer to Global Initializer 2
.CRT$XCZ
__xc_z
Библиотека CRT использует и __xc_a
__xc_z
для определения начала и окончания списка глобальных инициализаторов из-за способа, в котором они размещаются в памяти после загрузки образа.
Функции компоновщика для инициализации
Стандарт C++ не предоставляет соответствующий способ указать относительный порядок между единицами перевода для инициализатора, предоставленного пользователем. Однако, так как компоновщик Майкрософт упорядочивает .CRT
подразделы в алфавитном порядке, можно воспользоваться этим заказом, чтобы указать порядок инициализации. Мы не рекомендуем использовать этот метод, определенный корпорацией Майкрософт, и он может нарушиться в будущем выпуске. Мы задокументировали его только для того, чтобы не создавать код, который нарушается в сложных способах диагностики.
Чтобы предотвратить проблемы в коде, начиная с Visual Studio 2019 версии 16.11, мы добавили два новых предупреждения по умолчанию: C5247 и C5248. Включите эти предупреждения для обнаружения проблем при создании собственных инициализаторов.
Вы можете добавить инициализаторы в неиспользуемые зарезервированные имена разделов, чтобы создать их в определенном относительном порядке для созданных динамических инициализаторов компилятора:
#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;
#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;
Имена .CRT$XCT
и .CRT$XCV
не используются компилятором или библиотекой CRT прямо сейчас, но в будущем они останутся неиспользуемыми. И переменные по-прежнему могут быть оптимизированы компилятором. Перед принятием этого метода рассмотрите потенциальные проблемы проектирования, обслуживания и переносимости.
См. также
_initterm, _initterm_e
Файлы среды выполнения C (CRT) и стандартной библиотеки C++ (STL) .lib