wake-up-neo.com

Wie genau funktioniert __attribute __ ((Konstruktor))?

Es scheint ziemlich klar, dass es Dinge einrichten soll.

  1. Wann genau läuft es?
  2. Warum gibt es zwei Klammern?
  3. Ist __attribute__ Eine Funktion? Ein Makro? Syntax?
  4. Funktioniert das in C? C++?
  5. Muss die Funktion, mit der es arbeitet, statisch sein?
  6. Wann wird __attribute__((destructor)) ausgeführt?

Beispiel in Objective-C :

__attribute__((constructor))
static void initialize_navigationBarImages() {
  navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}
321
Casebash
  1. Es wird ausgeführt, wenn eine gemeinsam genutzte Bibliothek geladen wird, normalerweise während des Programmstarts.
  2. So sind alle GCC-Attribute. vermutlich, um sie von Funktionsaufrufen zu unterscheiden.
  3. GCC-spezifische Syntax.
  4. Ja, das funktioniert in C und C++.
  5. Nein, die Funktion muss nicht statisch sein.
  6. Der Destruktor wird ausgeführt, wenn die gemeinsam genutzte Bibliothek entladen wird, normalerweise beim Beenden des Programms.

Die Konstruktoren und Destruktoren arbeiten 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 Attributen constructor bzw. destructor gekennzeichnet sind. Beim Laden/Entladen der Bibliothek prüft das Dynamic Loader-Programm (ld.so oder so), ob solche Abschnitte vorhanden sind, und ruft in diesem Fall die darin referenzierten Funktionen auf.

Wenn Sie sich das vorstellen, gibt es wahrscheinlich eine ähnliche Magie im normalen statischen Linker, sodass beim Starten/Herunterfahren derselbe Code ausgeführt wird, unabhängig davon, ob der Benutzer statische oder dynamische Verknüpfungen wählt.

261
janneb

.init/.fini Ist nicht mehr aktuell. Es ist immer noch Teil des ELF-Standards und ich würde es wagen zu sagen, es wird für immer sein. Code in .init/.fini Wird vom Loader/Runtime-Linker ausgeführt, wenn Code geladen/entladen wird. Das heißt Bei jedem ELF-Ladevorgang (zum Beispiel einer gemeinsam genutzten Bibliothek) wird Code in .init ausgeführt. Es ist immer noch möglich, diesen Mechanismus zu verwenden, um ungefähr dasselbe wie mit __attribute__((constructor))/((destructor)) zu erreichen. Es ist altmodisch, hat aber einige Vorteile.

Der Mechanismus .ctors/.dtors Erfordert beispielsweise die Unterstützung durch system-rtl/loader/linker-script. Dies ist alles andere als sicher, dass es auf allen Systemen verfügbar ist, zum Beispiel auf tief eingebetteten Systemen, auf denen Code auf Bare Metal ausgeführt wird. Das heißt Selbst wenn __attribute__((constructor))/((destructor)) von GCC unterstützt wird, ist es nicht sicher, ob es ausgeführt wird, da es dem Linker überlassen ist, es zu organisieren und dem Loader (oder in einigen Fällen dem Boot-Code), um es auszuführen. Um stattdessen .init/.fini Zu verwenden, ist es am einfachsten, Linker-Flags zu verwenden: -init & -fini (d. H. In der GCC-Befehlszeile wäre die Syntax -Wl -init my_init -fini my_fini).

Bei Systemen, die beide Methoden unterstützen, besteht ein möglicher Vorteil darin, dass Code in .init Vor .ctors Und Code in .fini Nach .dtors Ausgeführt wird. Wenn die Reihenfolge relevant ist, gibt es mindestens eine einfache Möglichkeit, zwischen Init/Exit-Funktionen zu unterscheiden.

Ein großer Nachteil ist, dass Sie nicht einfach mehr als eine _init - und eine _fini - Funktion pro ladbares Modul haben können und wahrscheinlich mehr .so Als motiviert Code fragmentieren müssten . Zum anderen werden bei Verwendung der oben beschriebenen Linkermethode die ursprünglichen Standardfunktionen _init und _fini (Bereitgestellt durch crti.o) Ersetzt. Hier finden normalerweise alle Arten von Initialisierungen statt (unter Linux wird hier die globale Variablenzuweisung initialisiert). Ein Umweg wird beschrieben hier

Beachten Sie im obigen Link, dass keine Kaskadierung zum ursprünglichen _init() erforderlich ist, da es noch vorhanden ist. Das call in der Inline-Assembly ist jedoch eine x86-Mnemonik, und der Aufruf einer Funktion aus Assembly würde für viele andere Architekturen völlig anders aussehen (wie z. B. ARM). Code ist das nicht transparent.

Die Mechanismen .init/.fini Und .ctors/.detors Sind ähnlich, aber nicht ganz. Code in .init/.fini Wird "so wie er ist" ausgeführt. Das heißt Sie können mehrere Funktionen in .init/.fini haben, aber es ist syntaktisch schwierig, sie vollständig transparent in reinem C zu platzieren, ohne den Code in vielen kleinen .so - Dateien aufzubrechen.

.ctors/.dtors Sind anders organisiert als .init/.fini. Die Abschnitte .ctors/.dtors 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 Loop-Caller kann architekturspezifisch sein, aber da er Teil des Systems ist (wenn er überhaupt existiert, d. h.), spielt er keine Rolle.

Das folgende Snippet fügt dem Funktionsarray .ctors Neue Funktionszeiger hinzu, im Prinzip genauso wie __attribute__((constructor)) (Methode kann mit __attribute__((constructor))) koexistieren).

#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 die Loader-Schleife .ctors/.dtors Nachahmt. Aber mit ihm kann man eine bessere Kontrolle über die Ausführungsreihenfolge erreichen, In-Argumente hinzufügen und den Code für e.t.a. (In einem C++ - Projekt wäre es beispielsweise nützlich, wenn vor oder nach globalen Konstruktoren etwas ausgeführt werden muss.).

Ich würde __attribute__((constructor))/((destructor)) nach Möglichkeit vorziehen, es ist eine einfache und elegante Lösung, auch wenn es sich nach Betrug anfühlt. Für Bare-Metal-Codierer wie mich ist dies nicht immer eine Option.

Einige gute Hinweise im Buch Linkers & Loader .

60
Michael Ambrus

Diese Seite bietet umfassende Informationen zur Implementierung der Attribute constructor und destructor sowie zu den Abschnitten innerhalb von ELF, in denen sie ausgeführt werden können. Nachdem ich die hier bereitgestellten Informationen verarbeitet hatte, stellte ich einige zusätzliche Informationen zusammen und erstellte (ausgehend vom obigen Abschnittsbeispiel von Michael Ambrus) ein Beispiel, um die Konzepte zu veranschaulichen und mein Lernen zu erleichtern. Diese Ergebnisse werden im Folgenden zusammen mit der Beispielquelle bereitgestellt.

Wie in diesem Thread erläutert, erstellen die Attribute constructor und destructor Einträge im Abschnitt .ctors Und .dtors Der Objektdatei. Sie können in jedem Abschnitt auf drei Arten Verweise auf Funktionen platzieren. (1) Verwenden entweder des section Attributs; (2) constructor und destructor Attribute oder (3) mit einem Inline-Assembly-Aufruf (wie auf den Link in Ambrus 'Antwort verwiesen).

Mit den Attributen constructor und destructor können Sie dem Konstruktor/Destruktor zusätzlich eine Priorität zuweisen, um die Ausführungsreihenfolge zu steuern, bevor main() aufgerufen wird oder nachdem es zurückgegeben wird. 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 () und nach höheren Prioritäten nach main () ausgeführt). Die von Ihnen angegebenen Prioritätswerte müssen größer als 100 Sein, da der Compiler Prioritätswerte zwischen 0 und 100 für die Implementierung reserviert. Ein mit Priorität angegebenes constructor oder destructor wird ausgeführt, bevor ein ohne Priorität angegebenes constructor oder destructor ausgeführt wird.

Mit dem Attribut 'section' oder mit Inline-Assembly können Sie auch Funktionsreferenzen in die ELF-Codeabschnitte .init Und .fini Einfügen, die vor jedem Konstruktor bzw. nach jedem Destruktor ausgeführt werden. Alle Funktionen, die von der im Abschnitt .init Platzierten Funktionsreferenz aufgerufen werden, werden (wie üblich) vor der Funktionsreferenz selbst ausgeführt.

Ich habe versucht, diese im folgenden Beispiel zu veranschaulichen:

#include <stdio.h>
#include <stdlib.h>

/*  test function utilizing attribute 'section' ".ctors"/".dtors"
    to create constuctors/destructors without assigned priority.
    (provided by Michael Ambrus in earlier answer)
*/

#define SECTION( S ) __attribute__ ((section ( S )))

void test (void) {
printf("\n\ttest() utilizing -- (.section .ctors/.dtors) w/o priority\n");
}

void (*funcptr1)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;

/*  functions constructX, destructX use attributes 'constructor' and
    'destructor' to create prioritized entries in the .ctors, .dtors
    ELF sections, respectively.

    NOTE: priorities 0-100 are reserved
*/
void construct1 () __attribute__ ((constructor (101)));
void construct2 () __attribute__ ((constructor (102)));
void destruct1 () __attribute__ ((destructor (101)));
void destruct2 () __attribute__ ((destructor (102)));

/*  init_some_function() - called by elf_init()
*/
int init_some_function () {
    printf ("\n  init_some_function() called by elf_init()\n");
    return 1;
}

/*  elf_init uses inline-Assembly to place itself in the ELF .init section.
*/
int elf_init (void)
{
    __asm__ (".section .init \n call elf_init \n .section .text\n");

    if(!init_some_function ())
    {
        exit (1);
    }

    printf ("\n    elf_init() -- (.section .init)\n");

    return 1;
}

/*
    function definitions for constructX and destructX
*/
void construct1 () {
    printf ("\n      construct1() constructor -- (.section .ctors) priority 101\n");
}

void construct2 () {
    printf ("\n      construct2() constructor -- (.section .ctors) priority 102\n");
}

void destruct1 () {
    printf ("\n      destruct1() destructor -- (.section .dtors) priority 101\n\n");
}

void destruct2 () {
    printf ("\n      destruct2() destructor -- (.section .dtors) priority 102\n");
}

/* main makes no function call to any of the functions declared above
*/
int
main (int argc, char *argv[]) {

    printf ("\n\t  [ main body of program ]\n");

    return 0;
}

ausgabe:

init_some_function() called by elf_init()

    elf_init() -- (.section .init)

    construct1() constructor -- (.section .ctors) priority 101

    construct2() constructor -- (.section .ctors) priority 102

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        [ main body of program ]

        test() utilizing -- (.section .ctors/.dtors) w/o priority

    destruct2() destructor -- (.section .dtors) priority 102

    destruct1() destructor -- (.section .dtors) priority 101

Das Beispiel hat geholfen, das Konstruktor-/Destruktor-Verhalten zu festigen. Hoffentlich ist es auch für andere nützlich.

37
David C. Rankin

Hier ist ein "konkretes" (und möglicherweise nützlich) Beispiel für wie, warum und wann, um diese handlichen und doch nansehnlichen zu verwenden = Konstrukte ...

Xcode verwendet eine "globale" "Benutzervorgabe", um zu entscheiden, welche XCTestObserver Klasse das Herz ausspuckt an die Belagerte Konsole.

In diesem Beispiel ... wenn ich diese psuedo-Bibliothek implizit lade, nennen wir sie ... libdemure.a Über ein Flag in meinem Testziel á la ..

OTHER_LDFLAGS = -ldemure

Ich will..

  1. Überschreiben Sie beim Laden (dh wenn XCTest mein Testpaket lädt) die Klasse "default" XCTest "observer" ... (über die Funktion constructor) PS: As Soweit ich das beurteilen kann, kann alles, was hier gemacht wird, mit der gleichen Wirkung innerhalb der Methode + (void) load { ... } meiner Klasse gemacht werden.

  2. führen Sie meine Tests aus ... in diesem Fall mit weniger unsauberer Ausführlichkeit in den Protokollen (Implementierung auf Anfrage)

  3. Bringe die "globale" XCTestObserver Klasse in ihren ursprünglichen Zustand zurück, um andere XCTest Läufe, die nicht auf dem Zug waren (auch bekannt als libdemure.a). Ich schätze, dies wurde historisch in dealloc gemacht, aber ich werde nicht damit anfangen, mich mit dieser alten Hexe herumzuschlagen.

Damit...

#define USER_DEFS NSUserDefaults.standardUserDefaults

@interface      DemureTestObserver : XCTestObserver @end
@implementation DemureTestObserver

__attribute__((constructor)) static void Hijack_observer() {

/*! here I totally Hijack the default logging, but you CAN
    use multiple observers, just CSV them, 
    i.e. "@"DemureTestObserverm,XCTestLog"
*/
  [USER_DEFS setObject:@"DemureTestObserver" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

__attribute__((destructor)) static void reset_observer()  {

  // Clean up, and it's as if we had never been here.
  [USER_DEFS setObject:@"XCTestLog" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

...
@end

Ohne die Linker-Flagge ... (Modepolizeischwarm Cupertino Forderung nach Vergeltung, dennoch herrscht Apples Vorgabe vor, wie gewünscht, hier)

enter image description here

MIT dem Linker-Flag -ldemure.a ... (Verständliche Ergebnisse, keuchen ... "Danke constructor/destructor" ... = Crowd Cheersenter image description here

7
Alex Gray

Hier ist ein weiteres konkretes Beispiel. Es ist für eine gemeinsam genutzte Bibliothek. Die Hauptfunktion der gemeinsam genutzten Bibliothek besteht in der Kommunikation mit einem Chipkartenleser. Es kann aber auch 'Konfigurationsinformationen' zur Laufzeit über udp empfangen. Das udp wird von einem Thread behandelt, der [~ # ~] [~ # ~] zur Init-Zeit gestartet werden muss.

__attribute__((constructor))  static void startUdpReceiveThread (void) {
    pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
    return;

  }

Die Bibliothek wurde in c geschrieben.

1
drlolly