448 Stimmen

Wie genau funktioniert __attribute__((constructor))?

Es scheint ziemlich klar zu sein, dass es dazu dient, die Dinge zu regeln.

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

Beispiel in Objective-C :

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

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}

357voto

janneb Punkte 33959
  1. Es wird ausgeführt, wenn eine gemeinsam genutzte Bibliothek geladen wird, normalerweise während des Programmstarts.
  2. Das ist bei allen GCC-Attributen der Fall, 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 funktionieren so, dass die gemeinsam genutzte Objektdatei spezielle Abschnitte (.ctors und .dtors bei ELF) enthält, die Verweise auf die mit den Konstruktor- bzw. Destruktorattributen gekennzeichneten Funktionen enthalten. Wenn die Bibliothek geladen/entladen wird, prüft das dynamische Ladeprogramm (ld.so o.ä.), ob solche Abschnitte existieren, und wenn ja, ruft es die darin referenzierten Funktionen auf.

Wenn ich so darüber nachdenke, gibt es wahrscheinlich einen ähnlichen Zauber im normalen statischen Linker, so dass derselbe Code beim Starten/Herunterfahren ausgeführt wird, unabhängig davon, ob der Benutzer statisches oder dynamisches Linken wählt.

76voto

Michael Ambrus Punkte 924

.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 .

58voto

David C. Rankin Punkte 75069

Diese Seite bietet einen guten Einblick in die constructor y destructor Attribut-Implementierung und die Abschnitte innerhalb von ELF, die es ihnen ermöglichen, zu arbeiten. Nachdem ich die hier zur Verfügung gestellten Informationen verdaut hatte, stellte ich einige zusätzliche Informationen zusammen und erstellte (in Anlehnung an das obige Abschnittsbeispiel von Michael Ambrus) ein Beispiel, um die Konzepte zu veranschaulichen und mein Lernen zu erleichtern. Die Ergebnisse finden Sie weiter unten zusammen mit dem Quelltext des Beispiels.

Wie in diesem Thema erläutert, ist die constructor y destructor Attribute erzeugen Einträge in der .ctors y .dtors Abschnitt der Objektdatei. Sie können Verweise auf Funktionen in einem der beiden Abschnitte auf eine von drei Arten platzieren. (1) entweder mit der Option section Attribut; (2) constructor y destructor Attributen oder (3) mit einem Inline-Assembly-Aufruf (wie im Link in Ambrus' Antwort angegeben).

Die Verwendung von constructor y destructor Attribute ermöglichen es Ihnen, dem Konstruktor/Destruktor zusätzlich eine Priorität zuzuweisen, um die Reihenfolge der Ausführung vor main() aufgerufen wird oder nachdem er zurückkehrt. 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 Prioritätswerte, die Sie angeben muss größer sein als 100 da der Compiler Prioritätswerte zwischen 0-100 für die Implementierung reserviert. A constructor o destructor mit Priorität angegeben wird, wird vor einer constructor o destructor ohne Priorität angegeben.

Mit dem Attribut 'section' oder mit inline-assembly können Sie auch Funktionsreferenzen in der .init y .fini ELF-Codeabschnitt, der vor jedem Konstruktor bzw. nach jedem Destruktor ausgeführt wird. Alle Funktionen, die durch die Funktionsreferenz im Abschnitt .init wird (wie üblich) vor dem eigentlichen Funktionsverweis ausgeführt.

Ich habe versucht, jeden dieser Punkte in dem 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 dazu beigetragen, das Konstruktor/Destruktor-Verhalten zu festigen, und ich hoffe, dass es auch für andere nützlich sein wird.

8voto

Alex Gray Punkte 15268

Hier ist eine "konkrete" (und möglicherweise nützlich ) Beispiel für wie, warum und wann diese praktischen, aber dennoch unansehnlich Konstrukte...

Xcode verwendet einen "globalen" "Benutzerstandard", um zu entscheiden, welche XCTestObserver Klasse kotzt sich das Herz aus dem Leib zum bedrängt 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 möchte

  1. Unter Last (d. h. wenn XCTest lädt mein Testbündel), überschreiben Sie den "Standard" XCTest "Beobachter"-Klasse... (über die constructor Funktion) PS: Soweit ich das beurteilen kann, kann alles, was hier gemacht wird, mit gleichem Effekt in meiner Klasse gemacht werden. + (void) load { ... } Methode.

  2. meine Tests.... in diesem Fall mit weniger sinnlosem Geschwafel in den Protokollen ausführen (Implementierung auf Anfrage)

  3. Rückgabe der "globalen" XCTestObserver Klasse in ihren ursprünglichen Zustand zurückversetzen, um nicht andere XCTest Läufe, die nicht auf den Zug aufgesprungen sind (aka. verlinkt mit libdemure.a ). Ich vermute, dass dies historisch gesehen in dealloc aber ich werde mich nicht mit der alten Hexe anlegen.

Also...

#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... (Die Modepolizei schwärmt in Cupertino Vergeltung fordernd doch die Vorgabe von Apple setzt sich durch, wie gewünscht, hier )

enter image description here

MIT dem -ldemure.a Linker-Flag... (Nachvollziehbare Ergebnisse, keuchen ... "Danke constructor / destructor "... Die Menge jubelt ) enter image description here

1voto

drlolly Punkte 149

Hier ein weiteres konkretes Beispiel. Es bezieht sich auf eine gemeinsam genutzte Bibliothek. Die Hauptfunktion der gemeinsam genutzten Bibliothek ist die Kommunikation mit einem Chipkartenleser, aber sie kann auch zur Laufzeit "Konfigurationsinformationen" über UDP empfangen. Das UDP wird von einem Thread verarbeitet, der MUSS zum Zeitpunkt der Einleitung gestartet werden.

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

  }

Die Bibliothek wurde in C geschrieben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X