Как назвать разделы/группы для трех объектов C++ при использовании init_seg?

Я использую init_seg для управления созданием трех объектов класса C++. Каждый объект находится в другом исходном файле/единице перевода. Отладка показывает, что объекты создаются, как и ожидалось, во время инициализации CRT.

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

#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )

Похоже, что использование lib и section-name является взаимоисключающим, поэтому мне не ясно, как использовать init_seg(lib) и указать имя раздела/группы, чтобы получить правильный алфавитный порядок.

Когда я пытаюсь использовать алфавитную строку для управления порядком:

#pragma init_seg(lib, "01")

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

warning C4081: expected ')'; found ','

Когда я пытаюсь вставить непосредственно в код запуска CRT напрямую, используя ".CRT$XCB", ".CRT$001" и ".CRT$XCB001" (и другие варианты использования алфавита):

#pragma init_seg(".CRT$XCB")

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

warning C4075: initializers put in unrecognized initialization area

Я нашел один вопрос об этом в Stack Overflow, но ответ был предположением и не охватывает несколько единиц перевода. Я также нашел архив KB104248 на Wayback Machine, но это тоже не очень помогает, потому что показывает только использование compiler, lib и user.

Итак, мой вопрос: как мне использовать init_seg для управления точным порядком создания моих трех объектов в трех разных исходных файлах?


person jww    schedule 20.03.2017    source источник
comment
Вы видели пример кода в MSDN? Я думаю, что могу сделать разумное предположение, основываясь на этом коде, как вы будете делать то, о чем просите, но похоже, что вы ищете ответ, основанный на личном опыте?   -  person Harry Johnston    schedule 20.03.2017
comment
Спасибо, Гарри. Какой пример кода вы имеете в виду? Я просмотрел код на странице init_priority, но мне нужно признаться, я не устанавливаю связь между mine$a и .mine$z, распределениями и заменой кода запуска. Стандартный запуск ЭЛТ кажется адекватным. Мне нужно только вставить три моих объекта в определенном порядке.   -  person jww    schedule 20.03.2017
comment
Если я правильно читаю документы, когда вы используете именованные разделы, CRT не будет инициализировать или уничтожать статические объекты для вас, вы должны явно вызывать конструкторы и деструкторы. Преимущество этого в том, что он позволяет вам контролировать порядок. Этот пример кода, похоже, использует знание внутренних компонентов компилятора для вызова конструкторов для любых объектов, находящихся в именованных разделах, но я думаю, что в вашем случае это ненужное усложнение - вы знаете, что такое объекты, поэтому вы можете создавать и уничтожать их по имени.   -  person Harry Johnston    schedule 20.03.2017
comment
Если вы затем поместите статический объект внутрь init_seg(lib), конструктор и деструктор для этого объекта будут запущены автоматически, и оттуда вы сможете вызывать конструкторы и деструкторы для различных именованных разделов в желаемом порядке.   -  person Harry Johnston    schedule 20.03.2017
comment
Упс, на самом деле похоже, что компилятор уничтожит статические объекты за вас, если только вы не укажете func-name в вызове init_seg. Это позволяет вам контролировать разрушение, но кажется необязательным. Вам, наверное, это не нужно. Просто вызовите конструкторы при запуске и позвольте CRT вызывать деструкторы, как обычно.   -  person Harry Johnston    schedule 20.03.2017


Ответы (2)


Вот что я обнаружил в ходе тестирования на XP и VS2002/VS2003, Vista и VS2005/VS2008, Windows 7 и VS2008/VS2010, Windows 8 и VS2010/VS2012/VS2013 и Windows 10 с использованием VS2015. #pragma_init(<name>) доступен с версии VC++ 1.0 дней. MS не публикует слишком много информации об этом, но мы знаем, что это задокументировано из VC++1.0 (в архиве KB104248) через VS2017.

  1. #pragma init_seg(lib) почти идеально. Однако объектные файлы расположены в алфавитном порядке в VS2008 и более ранних версиях, поэтому порядок инициализации — a-b-c (нежелательный), а не c-b-a (желательный). Это нормально на VS2010 и выше. Что не очевидно, так это то, что порядок выложен точно так же, как c-b-a в файлах vcproj.

  2. #pragma init_seg(".CRT$XCB-0NN") вроде сработало. Наши std::strings STRING_A и STRING_B были созданы раньше (и объекты были в правильном порядке), но STRING_B вызвал сбой на suhutdown. Адрес был 0x0000000d, и похоже, что std::string (и его виртуальная таблица) были уничтожены слишком рано.

  3. #pragma init_seg(".CRT$XCU-0NN") работал как положено во время запуска и завершения работы. Если я правильно проанализировал то, что прочитал, то U в имени группы XCU указывает на определенные пользователем объекты. Это означает, что наши объекты были созданы где-то между тем, что обеспечивают #pragma init_seg(lib) и #pragma init_seg(user).

Итак, вот как инициализировать объект C, затем объект B, затем объект A из исходных файлов a.cpp, b.cpp и c.cpp.

Исходный файл a.cpp:

class A
{
    ...
};

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-030")
A a;    // created 3rd
#pragma warning(default: 4075)

Исходный файл b.cpp:

class B
{
    ...
};

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-020")
const B b;    // created 2nd
#pragma warning(default: 4075)

Исходный файл c.cpp:

#pragma warning(disable: 4075)
#pragma init_seg(".CRT$XCU-010")
const std::string c;    // created 1st
const std::string d;    // created 1st
#pragma warning(default: 4075)

Наш вариант использования состоял в том, чтобы создать три объекта только для чтения и избежать проблем со статической инициализацией C++ . фиаско заказа и локальное хранилище потоков от Microsoft.

Этот метод позволяет избежать отсутствия динамических инициализаторов C++ в C++03. Это также обходит стороной неспособность Microsoft предоставить C++11 Динамическая инициализация и уничтожение с параллелизмом (или, точнее, неспособность Microsoft предоставить базовую языковую функцию в течение 10 лет).

Вот ссылка на проблему с Thread Local Storage (TLS) на MSDN:

В операционных системах Windows до Windows Vista __declspec(thread) имеет некоторые ограничения. Если DLL объявляет какие-либо данные или объект как __declspec(поток), это может вызвать ошибку защиты при динамической загрузке. После того, как DLL загружается с помощью LoadLibrary, это вызывает системный сбой всякий раз, когда код ссылается на данные __declspec(thread). Поскольку пространство глобальной переменной для потока выделяется во время выполнения, размер этого пространства основан на расчете требований приложения и требований всех статически связанных библиотек DLL. Когда вы используете LoadLibrary, вы не можете расширить это пространство, чтобы разрешить локальные переменные потока, объявленные с помощью __declspec( thread ). Используйте API-интерфейсы TLS, такие как TlsAlloc, в своей библиотеке DLL для выделения TLS, если библиотека DLL может быть загружена с помощью LoadLibrary.

Также стоит отметить, что нет ограничения на количество символов в названии раздела или группы. В архиве КБ 104248 используется имя "user_defined_segment_name" из 26 символов.

person jww    schedule 20.03.2017
comment
Указанное имя раздела становится разделом в исполняемом файле. Окончательное имя состоит из 8 символов или меньше. Все, что длиннее, усекается, а лишние символы или все, что стоит после $, используется для относительного упорядочения в указанном разделе. - person Anthony Williams; 20.03.2017
comment
Я думаю, что Инициализация CRT актуальна, она описывает раздел .CRT. Я не уверен, является ли это обещанием поддерживать ту же архитектуру в будущих версиях CRT, но, по крайней мере, вы не находитесь на полностью недокументированной территории. :-) - person Harry Johnston; 20.03.2017
comment
Я не уверен, какое значение имеет здесь локальное хранилище потоков. Вы имеете в виду, что возможность контролировать порядок инициализации означает, что вам не нужно использовать библиотеки DLL? - person Harry Johnston; 20.03.2017
comment
@Harry - Мы создаем библиотеку. Другие могут использовать его для создания DLL. Мы должны разработать вариант использования. - person jww; 20.03.2017
comment
Хорошо, но как поможет использование init_seg? Насколько я понимаю, ограничение на __declspec(thread) все еще будет действовать. - person Harry Johnston; 20.03.2017

Вам нужно использовать #pragma section (https://msdn.microsoft.com/en-us/library/50bewfwa.aspx), чтобы указать атрибуты раздела, если вы используете собственное имя раздела.

#pragma section("foo",long,read,write)
#pragma init_seg("foo")

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

// tu1.cpp
#pragma section("foo$1",long,read,write)
#pragma init_seg("foo$1")

// tu2.cpp
#pragma section("foo$2",long,read,write)
#pragma init_seg("foo$2")

Данные из tu1.cpp теперь будут раньше данных из tu2.cpp.

Вы можете упорядочить вещи относительно библиотеки времени выполнения C, добавив суффикс к сегментам CRT.

// tu1.cpp
#pragma section(".CRT$XCU1",long,read,write)
#pragma init_seg("foo$1")

// tu2.cpp
#pragma section(".CRT$XCU2",long,read,write)
#pragma init_seg("foo$2")

TU1 теперь стоит перед TU2 и сгруппирован с другими данными.

person Anthony Williams    schedule 20.03.2017
comment
Спасибо Энтони. Мне не ясно, как это решает проблему. Помещает ли #pragma init_seg("foo") объект после инициализации библиотеки Microsoft C++ в crt0.dat, но до создания пользовательского объекта? Кроме того, как нам сделать это x3 для трех объектов C++ в трех разных исходных файлах? - person jww; 20.03.2017
comment
Я расширил свой ответ, чтобы решить эту проблему - person Anthony Williams; 20.03.2017