.init
/ .fini
Ist nicht veraltet. Es ist immer noch Teil des ELF-Standards und ich würde sagen, es wird für immer sein. Code in .init
/ .fini
wird vom Loader / Runtime-Linker ausgeführt, wenn Code geladen / entladen wird. Dh bei jedem ELF-Ladevorgang (z. B. einer gemeinsam genutzten Bibliothek) wird Code .init
ausgeführt. Es ist immer noch möglich, diesen Mechanismus zu verwenden, um ungefähr das Gleiche wie mit zu erreichen __attribute__((constructor))/((destructor))
. Es ist altmodisch, hat aber einige Vorteile.
.ctors
/ .dtors
Mechanismus zum Beispiel erfordern Unterstützung durch system-rtl / loader / linker-script. Dies ist keineswegs sicher auf allen Systemen verfügbar, beispielsweise auf tief eingebetteten Systemen, auf denen Code auf Bare-Metal-Basis ausgeführt wird. Das heißt, selbst wenn __attribute__((constructor))/((destructor))
es von GCC unterstützt wird, ist es nicht sicher, ob es ausgeführt wird, da es Sache des Linkers ist, es zu organisieren, und des Loaders (oder in einigen Fällen des Boot-Codes), es auszuführen. Um .init
/ .fini
stattdessen zu verwenden, ist es am einfachsten, Linker-Flags zu verwenden: -init & -fini (dh über die GCC-Befehlszeile wäre die Syntax -Wl -init my_init -fini my_fini
).
Auf einem System, das beide Methoden unterstützt, besteht ein möglicher Vorteil darin, dass Code in .init
vorher .ctors
und Code in .fini
nachher ausgeführt wird .dtors
. Wenn die Reihenfolge relevant ist, ist dies mindestens eine grobe, aber einfache Möglichkeit, zwischen Init / Exit-Funktionen zu unterscheiden.
Ein Hauptnachteil ist, dass Sie nicht einfach mehr als eine _init
und eine _fini
Funktion pro ladbarem Modul haben können und wahrscheinlich Code mehr .so
als motiviert fragmentieren müssten . Ein weiterer Grund ist, dass bei Verwendung der oben beschriebenen Linkermethode die ursprünglichen _init- und _fini
Standardfunktionen (bereitgestellt von crti.o
) ersetzt werden. Hier finden normalerweise alle Arten von Initialisierungen statt (unter Linux wird hier die globale Variablenzuweisung initialisiert). Ein Weg, um das zu umgehen, wird hier beschrieben
Beachten Sie im obigen Link, dass eine Kaskadierung zum Original _init()
nicht erforderlich ist, da es noch vorhanden ist. Die call
Inline-Assembly ist jedoch x86-mnemonisch, und das Aufrufen einer Funktion aus der Assembly würde für viele andere Architekturen (wie z. B. ARM) völlig anders aussehen. Dh Code ist nicht transparent.
.init
/ .fini
und .ctors
/ .detors
Mechanismen sind ähnlich, aber nicht ganz. Code in .init
/ .fini
läuft "wie es ist". Das heißt, Sie können mehrere Funktionen in .init
/ haben .fini
, aber es ist AFAIK syntaktisch schwierig, sie in reinem C vollständig transparent dort abzulegen, ohne den Code in vielen kleinen .so
Dateien aufzubrechen.
.ctors
/ .dtors
Werden anders als organisiert .init
/ .fini
. .ctors
/ .dtors
Abschnitte sind beide nur Tabellen mit Zeigern auf Funktionen, und der "Aufrufer" ist eine vom System bereitgestellte Schleife, die jede Funktion indirekt aufruft. Das heißt, der Schleifenaufrufer kann architekturspezifisch sein, aber da er Teil des Systems ist (falls überhaupt vorhanden, dh), spielt es keine Rolle.
Das folgende Snippet fügt dem Funktionsarray neue Funktionszeiger hinzu .ctors
, hauptsächlich auf die gleiche Weise wie __attribute__((constructor))
(Methode kann koexistieren mit __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;
Man kann die Funktionszeiger auch zu einem völlig anderen selbst erfundenen Abschnitt hinzufügen. In diesem Fall wird ein modifiziertes Linker-Skript und eine zusätzliche Funktion benötigt, die den Loader .ctors
/ die .dtors
Loop nachahmt . Aber damit kann man eine bessere Kontrolle über die Ausführungsreihenfolge erreichen, In-Argument hinzufügen und die Code-Behandlung eta zurückgeben (in einem C ++ - Projekt wäre es beispielsweise nützlich, wenn etwas vor oder nach globalen Konstruktoren ausgeführt werden muss).
Ich würde es vorziehen, __attribute__((constructor))/((destructor))
wenn es möglich ist, es ist eine einfache und elegante Lösung, auch wenn es sich wie Betrug anfühlt. Für Bare-Metal-Codierer wie mich ist dies nicht immer eine Option.
Einige gute Hinweise im Buch Linker & Loader .
#define __attribute__(x)
). Wenn Sie mehrere Attribute haben, z. B.__attribute__((noreturn, weak))
wäre es schwierig, ein "Makro zu erstellen", wenn nur ein Satz von Klammern vorhanden wäre.