Поделиться через


Инициализация 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