.init
/ .fini
non è deprecato. Fa ancora parte dello standard ELF e oserei dire che lo sarà per sempre. Il codice in .init
/ .fini
viene eseguito dal caricatore / runtime-linker quando il codice viene caricato / scaricato. Vale a dire su ogni carico ELF (ad esempio una libreria condivisa) .init
verrà eseguito il codice in . È ancora possibile utilizzare quel meccanismo per ottenere quasi la stessa cosa di __attribute__((constructor))/((destructor))
. È vecchia scuola ma ha alcuni vantaggi.
.ctors
Il .dtors
meccanismo / , ad esempio, richiede il supporto di system-rtl / loader / linker-script. Questo è tutt'altro che certo disponibile su tutti i sistemi, ad esempio i sistemi profondamente integrati in cui il codice viene eseguito su bare metal. Vale a dire anche se __attribute__((constructor))/((destructor))
è supportato da GCC, non è certo che verrà eseguito poiché spetta al linker organizzarlo e al caricatore (o in alcuni casi, codice di avvio) per eseguirlo. Per usare .init
/ .fini
invece, il modo più semplice è usare i flag di linker: -init & -fini (cioè dalla riga di comando di GCC, la sintassi sarebbe -Wl -init my_init -fini my_fini
).
Sul sistema che supporta entrambi i metodi, un possibile vantaggio è che il codice in .init
viene eseguito prima .ctors
e il codice in .fini
dopo .dtors
. Se l'ordine è rilevante, questo è almeno un modo semplice ma semplice per distinguere tra le funzioni init / exit.
Un grosso svantaggio è che non puoi avere facilmente più di una _init
e una _fini
funzione per ciascun modulo caricabile e probabilmente dovresti frammentare il codice in più .so
che motivato. Un altro è che quando si utilizza il metodo linker sopra descritto, si sostituiscono le funzioni originali _init e _fini
predefinite (fornite da crti.o
). Questo è dove di solito si verificano tutti i tipi di inizializzazione (su Linux è qui che viene inizializzata l'assegnazione delle variabili globali). Un modo per aggirare questo è descritto qui
Si noti nel collegamento sopra che _init()
non è necessario un collegamento in cascata all'originale in quanto è ancora in atto. L' call
assemblaggio in linea è tuttavia x86-mnemonico e la chiamata di una funzione dall'assemblaggio sembrerebbe completamente diversa per molte altre architetture (come ad esempio ARM). Cioè il codice non è trasparente.
.init
I meccanismi / .fini
e .ctors
/ .detors
sono simili, ma non del tutto. Il codice in .init
/ .fini
viene eseguito "così com'è". Vale a dire che puoi avere diverse funzioni in .init
/ .fini
, ma sintatticamente AFAIK è difficile metterle lì in modo completamente trasparente in C puro senza rompere il codice in molti piccoli .so
file.
.ctors
/ .dtors
sono organizzati diversamente da .init
/ .fini
. .ctors
Le .dtors
sezioni / sono entrambe tabelle con puntatori a funzioni e il "chiamante" è un ciclo fornito dal sistema che chiama indirettamente ciascuna funzione. Vale a dire il loop-caller può essere specifico dell'architettura, ma poiché fa parte del sistema (se esiste affatto, cioè) non importa.
Il frammento seguente aggiunge nuovi puntatori di .ctors
funzione all'array di funzioni, principalmente allo stesso modo __attribute__((constructor))
(il metodo può coesistere con __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;
Si possono anche aggiungere i puntatori di funzione a una sezione auto-inventata completamente diversa. In questo caso sono necessari uno script linker modificato e una funzione aggiuntiva che imita il caricatore .ctors
/ .dtors
loop. Ma con esso si può ottenere un migliore controllo sull'ordine di esecuzione, aggiungere l'argomento e la gestione del codice di ritorno eta (In un progetto C ++, ad esempio, sarebbe utile se fosse necessario eseguire qualcosa prima o dopo i costruttori globali).
Preferirei __attribute__((constructor))/((destructor))
dove possibile, è una soluzione semplice ed elegante anche se ti sembra di barare. Per i programmatori bare metal come me, questa non è sempre un'opzione.
Qualche buona referenza nel libro Linker e caricatori .
#define __attribute__(x)
). Se hai più attributi, ad esempio,__attribute__((noreturn, weak))
sarebbe difficile "eliminare" se esistesse solo un set di parentesi.