.init
/ .fini
nie jest przestarzałe. Nadal jest częścią standardu ELF i śmiem twierdzić, że będzie na zawsze. Kod w .init
/ .fini
jest uruchamiany przez moduł ładujący / wykonawczy-linker, gdy kod jest ładowany / rozładowywany. Tj. Na każdym ładowaniu ELF (na przykład w bibliotece współdzielonej) .init
zostanie uruchomiony. Nadal można użyć tego mechanizmu, aby osiągnąć mniej więcej to samo, co w przypadku __attribute__((constructor))/((destructor))
. Jest oldschoolowy, ale ma pewne zalety.
.ctors
.dtors
Na przykład mechanizm / wymaga obsługi przez skrypt system-rtl / loader / linker-script. Jest to dalekie od pewności, że będzie dostępne we wszystkich systemach, na przykład głęboko osadzonych systemach, w których kod jest wykonywany na czystym metalu. To __attribute__((constructor))/((destructor))
znaczy, nawet jeśli jest obsługiwany przez GCC, nie jest pewne, czy będzie działać, ponieważ to do konsolidatora należy go zorganizować i do modułu ładującego (lub w niektórych przypadkach kodu rozruchowego), aby go uruchomić. Aby użyć .init
/ .fini
zamiast, najprostszym sposobem jest użycie flag linkera: -init & -fini (tj. Z linii poleceń GCC, składnia byłaby -Wl -init my_init -fini my_fini
).
W systemie obsługującym obie metody jedną z możliwych korzyści jest to, że kod .init
jest uruchamiany przed, .ctors
a kod .fini
później .dtors
. Jeśli kolejność jest istotna, to przynajmniej jeden prosty, ale łatwy sposób na rozróżnienie między funkcjami inicjowania / kończenia.
Główną wadą jest to, że nie można łatwo mieć więcej niż jednej _init
i jednej _fini
funkcji na każdy moduł, który można załadować, i prawdopodobnie trzeba by fragmentować kod w więcej .so
niż motywowany. Innym jest fakt, że podczas korzystania z metody linkera opisanej powyżej, zastępuje się oryginalną _fini
funkcję _init i funkcje domyślne (dostarczone przez crti.o
). W tym miejscu zwykle dochodzi do różnego rodzaju inicjalizacji (w Linuksie inicjowane jest przypisywanie zmiennych globalnych). Sposób na to opisano tutaj
Zauważ w powyższym linku, że kaskadowanie do oryginału _init()
nie jest potrzebne, ponieważ nadal jest na swoim miejscu. W call
zestawie wbudowanym jest jednak mnemonik x86, a wywołanie funkcji z zestawu wyglądałoby zupełnie inaczej dla wielu innych architektur (na przykład ARM). Tj. Kod nie jest przejrzysty.
.init
Mechanizmy / .fini
i .ctors
/ .detors
są podobne, ale niezupełnie. Kod w .init
/ .fini
działa „jak jest”. To znaczy , możesz mieć kilka funkcji w .init
/ .fini
, ale AFAIK trudno syntaktycznie umieścić je tam w pełni transparentnie w czystym C bez rozbijania kodu w wielu małych .so
plikach.
.ctors
/ .dtors
są inaczej zorganizowane niż .init
/ .fini
. Sekcje .ctors
/ .dtors
są tylko tabelami ze wskaźnikami do funkcji, a „obiekt wywołujący” to dostarczona przez system pętla, która wywołuje każdą funkcję pośrednio. To znaczy, że osoba wywołująca pętlę może być specyficzna dla architektury, ale ponieważ jest częścią systemu (jeśli w ogóle istnieje, tj.), Nie ma to znaczenia.
Poniższy fragment kodu dodaje nowe wskaźniki funkcji do .ctors
tablicy funkcji, zasadniczo w ten sam sposób, co __attribute__((constructor))
robi (metoda może współistnieć z __attribute__((constructor)))
.
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
Można również dodać wskaźniki funkcji do zupełnie innej wymyślonej przez siebie sekcji. W takim przypadku potrzebny jest zmodyfikowany skrypt linkera i dodatkowa funkcja naśladująca moduł ładujący .ctors
/ .dtors
pętlę. Ale dzięki niemu można uzyskać lepszą kontrolę nad kolejnością wykonywania, dodawać argumenty argumentu i obsługiwać kod powrotu (np. W projekcie C ++ byłoby przydatne, gdyby potrzebował czegoś działającego przed lub po globalnych konstruktorach).
Wolę __attribute__((constructor))/((destructor))
tam, gdzie to możliwe, jest to proste i eleganckie rozwiązanie, nawet jeśli czuję się jak oszukiwanie. W przypadku koderów bez systemu operacyjnego, takich jak ja, nie zawsze jest to opcja.
Dobre referencje w książce Łączniki i ładowarki .
#define __attribute__(x)
). Jeśli masz wiele atrybutów, np__attribute__((noreturn, weak))
. Trudno byłoby „wyprowadzić makro”, gdyby istniał tylko jeden zestaw nawiasów.