.init/ .finiIst nicht veraltet. Es ist immer noch Teil des ELF-Standards und ich würde sagen, es wird für immer sein. Code in .init/ .finiwird vom Loader / Runtime-Linker ausgeführt, wenn Code geladen / entladen wird. Dh bei jedem ELF-Ladevorgang (z. B. einer gemeinsam genutzten Bibliothek) wird Code .initausgefü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/ .dtorsMechanismus 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/ .finistattdessen 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 .initvorher .ctorsund Code in .fininachher 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 _initund eine _finiFunktion pro ladbarem Modul haben können und wahrscheinlich Code mehr .soals motiviert fragmentieren müssten . Ein weiterer Grund ist, dass bei Verwendung der oben beschriebenen Linkermethode die ursprünglichen _init- und _finiStandardfunktionen (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 callInline-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/ .finiund .ctors/ .detorsMechanismen sind ähnlich, aber nicht ganz. Code in .init/ .finilä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 .soDateien aufzubrechen.
.ctors/ .dtorsWerden anders als organisiert .init/ .fini. .ctors/ .dtorsAbschnitte 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 .dtorsLoop 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.