можно ли управлять порядком визова статических конструкторов спомощю линкера
Здравствуйте Andrij, Вы писали:
A>можно ли управлять порядком визова статических конструкторов спомощю линкера
Нет, линкер понятия не имеет где у тебя статический конструктор, где C, где C++, а где ASM. Более того, порядок вызова конструкторов статических объектов реализован по разному разными версиями компиляторов. В частности VC++ делает это примерно следующим образом. Компилятор генерирует в каждом модуле одну или несколько процеду, которые собственно и вызывают конструкторы статических объектов. Далее адреса этих процедур помещаются в специальный сегмент с определённым именем. На C++ это могло бы выглядеть примерно так:
struct CTest {
CTest(int n)
{
::Sleep(n);
}
} g_test(1000);
void __cdecl CTestInit()
{
g_test.CTest(1000); // это типа вызов конструктора для g_test
}
typedef void (__cdecl *_PVFV)(void);
#pragma data_seg(".CRT$XCU")
_PVFV _init_module[] = { CTestInit };
#pragma data_seg()
Далее, в инициализирующем коде RTL имеется примерно такой код:
typedef void (__cdecl *_PVFV)(void);
#pragma data_seg(".CRT$XIA") /* C initializers */
_PVFV __xi_a[] = { NULL };
#pragma data_seg(".CRT$XIZ")
_PVFV __xi_z[] = { NULL };
#pragma data_seg(".CRT$XCA") /* C++ initializers */
_PVFV __xc_a[] = { NULL };
#pragma data_seg(".CRT$XCZ")
_PVFV __xc_z[] = { NULL };
#pragma data_seg(".CRT$XPA") /* C pre-terminators */
_PVFV __xp_a[] = { NULL };
#pragma data_seg(".CRT$XPZ")
_PVFV __xp_z[] = { NULL };
#pragma data_seg(".CRT$XTA") /* C terminators */
_PVFV __xt_a[] = { NULL };
#pragma data_seg(".CRT$XTZ")
_PVFV __xt_z[] = { NULL };
#pragma data_seg() /* reset */
void __cdecl _initterm (_PVFV * pfbegin,_PVFV * pfend)
{
/*
* walk the table of function pointers from the bottom up, until
* the end is encountered. Do not skip the first entry. The initial
* value of pfbegin points to the first valid entry. Do not try to
* execute what pfend points to. Only entries before pfend are valid.
*/
while ( pfbegin < pfend )
{
/*
* if current table entry is non-NULL, call thru it.
*/
if ( *pfbegin != NULL )
(**pfbegin)();
++pfbegin;
}
}
... _DllMainCRTStartup(...)
{
// ...
/*
* Do runtime startup initializers.
*/
_initterm( &__xi_a, &__xi_z );
/*
* do C++ constructors (initializers) specific to this DLL
*/
_initterm( &__xc_a, &__xc_z );
// ...
}
Что здесь происходит?
Адреса всех процедур, вызывающих конструкторы и производящих другую инициализацию (типа CTestInit из первого примера), компилятор помещает в сегменты с именами от
".CRT$XCA" до
".CRT$XCZ". Затем при сборке линкер их тупо собирает в одну кучу и сортирует по именам сегментов, в результате получается массив указателей на функции. Инициализирующему коду остаётся только вызвать по очереди все функции, при этом порядок вызова этих функций неопределён и повлиять ты на него никак не можешь.
Но! Если нельзя, но очень хочется, то можно
Зная детали процесса инициализации, можно вполне их использовать. В частности можно написать свои процедуры, которые будут вызываться до или после отработки конструкторов статических объектов:
#include "stdafx.h"
#include <windows.h>
#pragma comment(lib,"user32")
struct CTest {
CTest()
{
::MessageBox(NULL,TEXT("CTest"),TEXT("test"),0);
}
} g_test;
void __cdecl f2()
{
::MessageBox(NULL,TEXT("f2"),TEXT("test"),0);
}
void __cdecl f1()
{
::MessageBox(NULL,TEXT("f1"),TEXT("test"),0);
}
typedef void (__cdecl *_PVFV)(void);
#pragma data_seg(".CRT$XIZ")
static _PVFV _xi_z[] = { f1 };
#pragma data_seg(".CRT$XCZ")
static _PVFV _xc_z[] = { f2 };
#pragma data_seg()
int main(int argc, char* argv[])
{
return 0;
}
В этом примере
f1 отработает до конструктора
CTest, а
f2 после.
На BC++ это решается примерно таким же образом, только там есть специальная прагма, позволяющая задавать приоритет вызова для конкретных процедур. Если мне не изменяет мой склероз, то это что-то типа
#pragma startup startfuncname priority
#pragma exit exitfuncname priority
Здравствуйте IT, Вы писали:
A>>можно ли управлять порядком визова статических конструкторов спомощю линкера
IT>Нет, линкер понятия не имеет где у тебя статический конструктор, где C, где C++, а где ASM.
[... превосходное разъяснение безжалостно пропущено ...]
IT>typedef void (__cdecl *_PVFV)(void);
IT>#pragma data_seg(".CRT$XIZ")
IT>static _PVFV _xi_z[] = { f1 };
IT>#pragma data_seg(".CRT$XCZ")
IT>static _PVFV _xc_z[] = { f2 };
IT>#pragma data_seg()
Боже, ну зачем так сложно, в Visual C++ тоже есть прагмы для этого:
#pragma init_seg(compiler)
#pragma init_seg(lib)
#pragma init_seg(user)
Здравствуйте Alex Fedotov, Вы писали:
AF>Боже, ну зачем так сложно, в Visual C++ тоже есть прагмы для этого:
AF>#pragma init_seg(compiler)
AF>#pragma init_seg(lib)
AF>#pragma init_seg(user)
Это да, но это только для
C++, и только для
init. Для
exit, конечно, можно и даже лучше использовать
atexit. Но управлять приоритетом возможности практически нет,

хотя на практике, в C++ (на C были прецеденты), мне это так и не понадобилось.
Здравствуйте IT, Вы писали:
IT>Здравствуйте Alex Fedotov, Вы писали:
AF>>Боже, ну зачем так сложно, в Visual C++ тоже есть прагмы для этого:
AF>>#pragma init_seg(compiler)
AF>>#pragma init_seg(lib)
AF>>#pragma init_seg(user)
IT>Это да, но это только для C++, и только для init. Для exit, конечно, можно и даже лучше использовать atexit. Но управлять приоритетом возможности практически нет,
хотя на практике, в C++ (на C были прецеденты), мне это так и не понадобилось.
Погоди, в языке C до main ничего не выполняется, или я что-то путаю? Это же только в C++ появилось
// at the global scope
int n = my_cool_function();
Что касается порядка вызова деструкторов, то он обратный порядку вызова конструкторов. На самом деле реализация статической инициализации в Visual C++ вызывает atexit для регистрации деструкторов.
Здравствуйте Alex Fedotov, Вы писали:
AF>Погоди, в языке C до main ничего не выполняется, или я что-то путаю? Это же только в C++ появилось
Пришлось поднять свои древние исходники... Действительно, боролся я с порядком деинициализации
AF>Что касается порядка вызова деструкторов, то он обратный порядку вызова конструкторов. На самом деле реализация статической инициализации в Visual C++ вызывает atexit для регистрации деструкторов.
Тем не менее VCRTL использует atexit в редких случаях. И опять же, вопрос стоял о приоритетах вызова всего этого безобразия.