.init
/ .fini
ist nicht veraltet. Es ist immer noch Teil des ELF-Standards und ich wage zu behaupten, dass es für immer sein wird. Code in .init
/ .fini
wird vom Loader/Runtime-Linker ausgeführt, wenn Code geladen/entladen wird. D.h. bei jedem ELF-Laden (z.B. einer Shared Library) wird Code in .init
ausgeführt werden. Es ist immer noch möglich, diesen Mechanismus zu verwenden, um ungefähr das Gleiche zu erreichen wie mit __attribute__((constructor))/((destructor))
. Das ist zwar altmodisch, aber es hat einige Vorteile.
.ctors
/ .dtors
Mechanismus erfordern zum Beispiel die Unterstützung durch system-rtl/loader/linker-script. Es ist bei weitem nicht sicher, dass dies auf allen Systemen verfügbar ist, z. B. auf tief eingebetteten Systemen, bei denen der Code auf blankem Metall ausgeführt wird. D.h. selbst wenn __attribute__((constructor))/((destructor))
von GCC unterstützt wird, ist es nicht sicher, dass es läuft, da es vom Linker abhängt, es zu organisieren und vom Lader (oder in einigen Fällen vom Boot-Code), es auszuführen. Zur Verwendung .init
/ .fini
stattdessen ist es am einfachsten, Linker-Flags zu verwenden: -init & -fini (d.h. von der GCC-Kommandozeile aus, die Syntax wäre -Wl -init my_init -fini my_fini
).
Auf Systemen, die beide Methoden unterstützen, besteht ein möglicher Vorteil darin, dass Code in .init
ausgeführt wird, bevor .ctors
und Code in .fini
nach .dtors
. Wenn die Reihenfolge relevant ist, ist das zumindest eine grobe, aber einfache Möglichkeit, zwischen init/exit-Funktionen zu unterscheiden.
Ein großer Nachteil ist, dass man nicht einfach mehr als eine Person haben kann. _init
und eine _fini
Funktion für jedes ladbare Modul und müsste den Code wahrscheinlich in mehrere Teile zerlegen. .so
als motiviert. Ein weiterer Grund ist, dass man bei der oben beschriebenen Linker-Methode die ursprünglichen _init und _fini
Standardfunktionen (bereitgestellt von crti.o
). Hier werden normalerweise alle Arten von Initialisierungen vorgenommen (unter Linux wird hier die Zuweisung globaler Variablen initialisiert). Eine Möglichkeit, dies zu umgehen, wird beschrieben aquí
Beachten Sie im obigen Link, dass eine Kaskadierung zum Original _init()
ist nicht erforderlich, da sie noch vorhanden ist. Die call
in der Inline-Assemblierung ist jedoch x86-mnemonisch und der Aufruf einer Funktion aus der Assemblierung würde für viele andere Architekturen (wie z.B. ARM) völlig anders aussehen. D.h. der Code ist nicht transparent.
.init
/ .fini
y .ctors
/ .detors
Mechanismen sind ähnlich, aber nicht ganz. Code in .init
/ .fini
läuft "wie es ist". D.h. Sie können mehrere Funktionen in .init
/ .fini
, aber es ist AFAIK syntaktisch schwierig, sie dort völlig transparent in reinem C unterzubringen, ohne den Code in viele kleine Teile zu zerlegen. .so
Dateien.
.ctors
/ .dtors
sind anders organisiert als .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. D.h. der Schleifenaufruf kann architekturspezifisch sein, aber da er Teil des Systems ist (wenn er überhaupt existiert), spielt das keine Rolle.
Das folgende Snippet fügt neue Funktionszeiger zu den .ctors
Funktionsarray, im Prinzip auf die gleiche Weise wie __attribute__((constructor))
tut (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 in einem ganz anderen, selbst erfundenen Abschnitt unterbringen. Ein modifiziertes Linkerskript und eine zusätzliche Funktion, die den Lader nachahmt .ctors
/ .dtors
Schleife ist in einem solchen Fall erforderlich. Aber damit kann man eine bessere Kontrolle über die Ausführungsreihenfolge erreichen, In-Argumente und Rückgabewertbehandlung hinzufügen usw. (In einem C++-Projekt wäre es zum Beispiel nützlich, wenn etwas vor oder nach globalen Konstruktoren ausgeführt werden muss).
Ich würde es vorziehen __attribute__((constructor))/((destructor))
Wenn möglich, ist dies eine einfache und elegante Lösung, auch wenn es sich wie Betrug anfühlt. Für Bare-Metal-Programmierer wie mich ist das nicht immer eine Option.
Einige gute Referenzen im Buch Linker & Lader .