- Es wird ausgeführt, wenn eine gemeinsam genutzte Bibliothek geladen wird, normalerweise während des Programmstarts.
- So sind alle GCC-Attribute; vermutlich, um sie von Funktionsaufrufen zu unterscheiden.
- GCC-spezifische Syntax.
- Ja, das funktioniert in C und C ++.
- Nein, die Funktion muss nicht statisch sein.
- Der Destruktor wird ausgeführt, wenn die gemeinsam genutzte Bibliothek entladen wird, normalerweise beim Beenden des Programms.
Die Konstruktoren und Destruktoren funktionieren also so, dass die gemeinsam genutzte Objektdatei spezielle Abschnitte (.ctors und .dtors in ELF) enthält, die Verweise auf die Funktionen enthalten, die mit den Konstruktor- bzw. Destruktorattributen gekennzeichnet sind. Wenn die Bibliothek geladen / entladen wird, prüft das dynamische Ladeprogramm (ld.so oder so), ob solche Abschnitte vorhanden sind, und ruft in diesem Fall die darin genannten Funktionen auf.
Wenn Sie sich das vorstellen, gibt es wahrscheinlich eine ähnliche Magie im normalen statischen Linker, so dass beim Starten / Herunterfahren derselbe Code ausgeführt wird, unabhängig davon, ob der Benutzer statische oder dynamische Verknüpfung wählt.
#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..init/.fini
. (Sie können gültig mehrere Konstruktoren und Destruktoren in einer einzigen Übersetzungseinheit haben, niemals mehrere in einer einzigen Bibliothek - wie würde das funktionieren?) Stattdessen werden auf Plattformen, die das ELF-Binärformat (Linux usw.) verwenden, auf die Konstruktoren und Destruktoren verwiesen in den.ctors
und.dtors
Abschnitten der Kopfzeile. Zwar wurden früher Funktionen benanntinit
undfini
beim Laden und Entladen dynamischer Bibliotheken ausgeführt, wenn sie vorhanden waren, aber das ist jetzt veraltet und wird durch diesen besseren Mechanismus ersetzt.__attribute__
besteht, dass Sie gcc nicht verwenden, da auch dies eine gcc-Erweiterung ist..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 voncrti.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 beschriebenBeachten Sie im obigen Link, dass eine Kaskadierung zum Original
_init()
nicht erforderlich ist, da es noch vorhanden ist. Diecall
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)))
.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 .
quelle
__attribute__((constructor))/((destructor))
läuft der Destruktor nicht. Ich habe einige Dinge ausprobiert, wie das Hinzufügen eines Eintrags zu .dtor, wie oben gezeigt. Aber kein Erfolg. Das Problem kann leicht dupliziert werden, indem der Code mit numactl ausgeführt wird. Angenommen, test_code enthält den Destruktor (fügen Sie den Konstruktor- und Desktorfunktionen ein printf hinzu, um das Problem zu beheben). Dann renneLD_PRELOAD=./test_code numactl -N 0 sleep 1
. Sie werden sehen, dass der Konstruktor zweimal aufgerufen wird, der Destruktor jedoch nur einmal.Diese Seite bietet ein gutes Verständnis für die
constructor
unddestructor
Implementierung von Attributen sowie der Abschnitte in ELF, in denen sie funktionieren können. Nachdem ich die hier bereitgestellten Informationen verdaut hatte, stellte ich einige zusätzliche Informationen zusammen und erstellte (aus dem obigen Abschnittsbeispiel von Michael Ambrus) ein Beispiel, um die Konzepte zu veranschaulichen und mein Lernen zu erleichtern. Diese Ergebnisse werden unten zusammen mit der Beispielquelle bereitgestellt.Wie in diesem Thread erläutert, die
constructor
unddestructor
erstellen Attribute Einträge in der.ctors
und.dtors
Abschnitt der Objektdatei. Sie können Verweise auf Funktionen in beiden Abschnitten auf drei Arten platzieren. (1) Verwenden eines dersection
Attribute; (2)constructor
unddestructor
Attribute oder (3) mit einem Inline-Assembly-Aufruf (wie auf den Link in Ambrus 'Antwort verwiesen).Durch die Verwendung von
constructor
unddestructor
Attributen können Sie dem Konstruktor / Destruktor zusätzlich eine Priorität zuweisen, um seine Ausführungsreihenfolge vor demmain()
Aufruf oder nach der Rückkehr zu steuern . Je niedriger der angegebene Prioritätswert ist, desto höher ist die Ausführungspriorität (niedrigere Prioritäten werden vor höheren Prioritäten vor main () ausgeführt - und nach höheren Prioritäten nach main ()). Die von Ihnen angegebenen Prioritätswerte müssen größer sein als,100
da der Compiler Prioritätswerte zwischen 0 und 100 für die Implementierung reserviert. Einconstructor
oderdestructor
mit Priorität angegeben wird vor einemconstructor
oderdestructor
ohne Priorität angegeben ausgeführt.Mit dem Attribut 'section' oder mit der Inline-Assembly können Sie auch Funktionsreferenzen in den Abschnitt
.init
und den.fini
ELF-Codeabschnitt einfügen, die vor jedem Konstruktor bzw. nach jedem Destruktor ausgeführt werden. Alle Funktionen, die von der im.init
Abschnitt platzierten Funktionsreferenz aufgerufen werden , werden (wie üblich) vor der Funktionsreferenz selbst ausgeführt.Ich habe versucht, diese im folgenden Beispiel zu veranschaulichen:
Ausgabe:
Das Beispiel hat dazu beigetragen, das Konstruktor- / Destruktorverhalten zu festigen, hoffentlich ist es auch für andere nützlich.
quelle
MAX_RESERVED_INIT_PRIORITY
), und dass sie mit C ++ (init_priority
) identisch waren. 7.7 C ++ - Spezifische Variablen-, Funktions- und Typattribute . Dann habe ich es versucht mit99
:warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99)));
.Hier ist ein "konkretes" (und möglicherweise nützliches ) Beispiel dafür, wie, warum und wann diese praktischen, aber unansehnlichen Konstrukte verwendet werden sollen ...
Xcode verwendet einen "globalen" "Benutzerstandard", um zu entscheiden, welche
XCTestObserver
Klasse der bedrängten Konsole das Herz ausspuckt .In diesem Beispiel ... wenn ich diese Pseudobibliothek implizit lade, nennen wir sie ...
libdemure.a
über ein Flag in meinem Testziel á la ..Ich will..
XCTest
Überschreiben Sie beim Laden (dh beim Laden meines Testpakets) die "Standard"XCTest
-Klasse "Beobachter" ... (über dieconstructor
Funktion) PS: Soweit ich das beurteilen kann, kann alles, was hier getan wird, mit gleichem Effekt in meinem Klasse '+ (void) load { ... }
Methode.Führen Sie meine Tests aus .... in diesem Fall mit weniger irrsinniger Ausführlichkeit in den Protokollen (Implementierung auf Anfrage)
Bringen Sie die "globale"
XCTestObserver
Klasse in ihren ursprünglichen Zustand zurück, um andereXCTest
Läufe, die nicht auf den Zug gekommen sind (auch bekannt als "verknüpft mit"libdemure.a
), nicht zu beschmutzen . Ich denke, das wurde historisch indealloc
... gemacht, aber ich werde nicht anfangen, mich mit dieser alten Hexe anzulegen.Damit...
Ohne die Linker-Flagge ... (Cupertino, ein Schwarm der Modepolizei, fordert Vergeltung , doch hier herrscht, wie gewünscht, Apples Standard vor. )
MIT der
-ldemure.a
Linker-Flagge ... (Verständliche Ergebnisse, keuchen ... "dankeconstructor
/destructor
" ... Crowd Cheers )quelle
Hier ist ein weiteres konkretes Beispiel. Es handelt sich um eine gemeinsam genutzte Bibliothek. Die Hauptfunktion der gemeinsam genutzten Bibliothek besteht in der Kommunikation mit einem Smartcard-Leser. Es kann aber auch 'Konfigurationsinformationen' zur Laufzeit über udp empfangen. Die udp wird von einem Thread verarbeitet , die MUSS bei init Zeit gestartet werden.
Die Bibliothek wurde in c geschrieben.
quelle