Comment fonctionne exactement __atsortingbute __ ((constructeur))?

Il semble assez clair que c’est censé mettre les choses en place.

  1. Quand exactement ça marche?
  2. Pourquoi y a-t-il deux parenthèses?
  3. Est-ce que __atsortingbute__ est une fonction? Une macro? Syntaxe?
  4. Est-ce que cela fonctionne en C? C ++?
  5. La fonction avec laquelle elle fonctionne doit-elle être statique?
  6. Quand __atsortingbute__((destructor)) exécuté?

Exemple dans l’objective C :

 __atsortingbute__((constructor)) static void initialize_navigationBarImages() { navigationBarImages = [[NSMutableDictionary alloc] init]; } __atsortingbute__((destructor)) static void destroy_navigationBarImages() { [navigationBarImages release]; } 

  1. Il est exécuté lorsqu’une bibliothèque partagée est chargée, généralement au démarrage du programme.
  2. C’est comme ça que tous les atsortingbuts GCC sont; probablement pour les distinguer des appels de fonction.
  3. Syntaxe spécifique au GCC.
  4. Oui, cela fonctionne aussi en C et C ++.
  5. Non, la fonction n’a pas besoin d’être statique.
  6. Le destructeur est exécuté lorsque la bibliothèque partagée est déchargée, généralement à la sortie du programme.

Ainsi, la manière dont fonctionnent les constructeurs et les destructeurs est que le fichier object partagé contient des sections spéciales (.ctors et .dtors sur ELF) qui contiennent des références aux fonctions marquées avec les atsortingbuts constructeur et destructeur, respectivement. Lorsque la bibliothèque est chargée / déchargée, le programme du chargeur dynamic (ld.so ou somesuch) vérifie si de telles sections existent et, si tel est le cas, appelle les fonctions qui y sont référencées.

En y pensant, il y a probablement une magie similaire dans l’éditeur de liens statiques normal, de sorte que le même code est exécuté au démarrage / à l’arrêt, que l’utilisateur choisisse un lien statique ou dynamic.

.init / .fini n’est pas obsolète. Cela fait toujours partie de la norme ELF et j’oserais dire que ce sera pour toujours. Le code dans .init / .fini est exécuté par l’éditeur de liens loader / runtime lorsque le code est chargé / déchargé. Par exemple, sur chaque chargement ELF (par exemple une bibliothèque partagée), le code dans .init sera exécuté. Il est toujours possible d’utiliser ce mécanisme pour obtenir à peu près la même chose qu’avec __atsortingbute__((constructor))/((destructor)) . C’est de la vieille école mais ça a des avantages.

.ctors mécanisme .ctors / .dtors , par exemple, nécessite un support par system-rtl / loader / linker-script. Ceci est loin d’être disponible sur tous les systèmes, par exemple sur les systèmes profondément embarqués où le code s’exécute sur le bare metal. C’est-à-dire que même si __atsortingbute__((constructor))/((destructor)) est pris en charge par GCC, il n’est pas certain que l’éditeur de liens l’organise et le charge (ou, dans certains cas, code d’amorçage) exécuter. Pour utiliser .init / .fini , la méthode la plus simple consiste à utiliser des indicateurs de lieur: -init & -fini (c’est-à-dire à partir de la ligne de commande GCC, la syntaxe serait -Wl -init my_init -fini my_fini ).

Sur le système prenant en charge les deux méthodes, un des avantages possibles est que le code dans .init est exécuté avant le .ctors et le code dans .fini après .dtors . Si l’ordre est pertinent, il existe au moins un moyen simple mais brut de distinguer les fonctions init / exit.

Un inconvénient majeur est que vous ne pouvez pas facilement avoir plus d’une fonction _init et une fonction _fini par module chargeable et que vous devrez probablement fragmenter le code de manière plus que motivée. Une autre est que lorsque vous utilisez la méthode de l’éditeur de liens décrite ci-dessus, on remplace les fonctions par défaut d’origine _init et _fini (fournies par crti.o ). C’est là que se produisent généralement toutes sortes d’initialisation (sous Linux, c’est là que l’initialisation des variables globales est initialisée). Un moyen de contourner cela est décrit ici

Notez que dans le lien ci-dessus, une cascade vers l’original _init() n’est pas nécessaire car elle est toujours en place. L’ call dans l’assemblage en ligne est cependant x86-mnémonique et l’appel d’une fonction à partir de l’assembly serait complètement différent pour beaucoup d’autres architectures (comme ARM par exemple). Ie code n’est pas transparent.

.init / .fini et .ctors / .detors sont similaires, mais pas tout à fait. Le code dans .init / .fini s’exécute “tel .fini “. C’est-à-dire que vous pouvez avoir plusieurs fonctions dans .init / .fini , mais il est difficile, d’un sharepoint vue syntaxique, de les placer de manière totalement transparente en C pur sans rompre le code dans de nombreux petits fichiers .so .

.ctors / .dtors sont organisés différemment de .init / .fini . .ctors sections .ctors / .dtors sont que des tables avec des pointeurs vers des fonctions, et “l’appelant” est une boucle fournie par le système qui appelle chaque fonction indirectement. C’est-à-dire que l’appel de boucle peut être spécifique à l’architecture, mais comme il fait partie du système (s’il existe), cela n’a pas d’importance.

L’extrait suivant ajoute de nouveaux pointeurs de fonctions au tableau de fonctions .ctors , principalement de la même manière que __atsortingbute__((constructor)) (la méthode peut coexister avec __atsortingbute__((constructor))) .

 #define SECTION( S ) __atsortingbute__ ((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; 

On peut également append les pointeurs de fonction à une section complètement différente. Un script de lieur modifié et une fonction supplémentaire imitant la .ctors loader .ctors / .dtors sont nécessaires dans ce cas. Mais avec cela, on peut obtenir un meilleur contrôle de l’ordre d’exécution, append des arguments et traiter le code retour eta (Dans un projet C ++ par exemple, il serait utile d’avoir besoin de quelque chose qui tourne avant ou après les constructeurs globaux).

Je préférerais __atsortingbute__((constructor))/((destructor)) , dans la mesure du possible, c’est une solution simple et élégante, même si on a l’impression de sortingcher. Pour les codeurs nus comme moi, ce n’est pas toujours une option.

Une bonne référence dans le livre Linkers & Loaders .

Cette page fournit une excellente compréhension de l’implémentation des atsortingbuts constructor et destructor et des sections dans ELF qui leur permettent de fonctionner. Après avoir digéré les informations fournies ici, j’ai compilé un peu d’informations supplémentaires (en empruntant l’exemple de la section de Michael Ambrus ci-dessus), j’ai créé un exemple pour illustrer les concepts et faciliter mon apprentissage. Ces résultats sont fournis ci-dessous avec la source de l’exemple.

Comme expliqué dans cette discussion, les atsortingbuts constructor et destructor créent des entrées dans la section .ctors et .dtors du fichier object. Vous pouvez placer des références à des fonctions dans l’une ou l’autre des sections de trois manières différentes. (1) en utilisant soit l’atsortingbut section ; (2) constructor atsortingbuts constructor et destructor ou (3) avec un appel d’assemblage en ligne (comme indiqué dans le lien dans la réponse d’Ambrus).

L’utilisation des atsortingbuts constructor et destructor vous permet en outre d’atsortingbuer une priorité au constructeur / destructeur pour contrôler son ordre d’exécution avant l’appel de main() ou après son retour. Plus la valeur de priorité donnée est faible, plus la priorité d’exécution est élevée (les priorités inférieures s’exécutent avant les priorités plus élevées avant main () – et après les priorités plus élevées après main ()). Les valeurs de priorité que vous indiquez doivent être supérieures à 100 car le compilateur réserve des valeurs de priorité comsockets entre 0 et 100 pour la mise en œuvre. Un constructor ou un destructor spécifié avec priorité s’exécute avant un constructor ou un destructor spécifié sans priorité.

Avec l’atsortingbut ‘section’ ou avec inline-assembly, vous pouvez également placer des références de fonction dans les .init code .init et .fini ELF qui s’exécuteront avant tout constructeur et après tout destructeur, respectivement. Toutes les fonctions appelées par la référence de fonction placée dans la section .init seront .init avant la référence de la fonction (comme d’habitude).

J’ai essayé d’illustrer chacun de ces exemples dans l’exemple ci-dessous:

 #include  #include  /* test function utilizing atsortingbute 'section' ".ctors"/".dtors" to create constuctors/destructors without assigned priority. (provided by Michael Ambrus in earlier answer) */ #define SECTION( S ) __atsortingbute__ ((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 atsortingbutes 'constructor' and 'destructor' to create prioritized ensortinges in the .ctors, .dtors ELF sections, respectively. NOTE: priorities 0-100 are reserved */ void construct1 () __atsortingbute__ ((constructor (101))); void construct2 () __atsortingbute__ ((constructor (102))); void destruct1 () __atsortingbute__ ((destructor (101))); void destruct2 () __atsortingbute__ ((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; } 

sortie:

 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 

L’exemple a aidé à cimenter le comportement constructeur / destructeur, en espérant qu’il sera utile aux autres.

Voici un exemple “concret” (et peutêtre utile ) de comment, pourquoi et quand utiliser ces constructions pratiques mais inesthétiques

Xcode utilise un “défaut utilisateur” “global” pour décider XCTestObserver classe XCTestObserver crache son coeur sur la console en difficulté .

Dans cet exemple … quand je charge implicitement cette psuedo-library, appelons-la … libdemure.a , via un drapeau dans ma cible de test ..

 OTHER_LDFLAGS = -ldemure 

Je veux..

  1. A la charge (c.-à-d. Lorsque XCTest charge mon paquet de test), remplacez la XCTest “observateur” XCTest “par défaut” … (via la fonction constructor ) PS: Autant que je XCTest tout peut être fait avec un équivalent effet dans ma classe ‘ + (void) load { ... } méthode.

  2. exécuter mes tests …. dans ce cas, avec moins de verbosité inane dans les logs (implémentation sur demande)

  3. XCTestObserver classe “global” de XCTestObserver à son état d’origine, afin de ne pas endommager les autres XCTest qui n’ont pas été XCTest dans le train en marche (également lié à libdemure.a ). Je suppose que cela a toujours été fait dans le cadre de dealloc .. mais je ne suis pas sur le sharepoint commencer à jouer avec cette vieille hag.

Alors…

 #define USER_DEFS NSUserDefaults.standardUserDefaults @interface DemureTestObserver : XCTestObserver @end @implementation DemureTestObserver __atsortingbute__((constructor)) static void hijack_observer() { /*! here I totally hijack the default logging, but you CAN use multiple observers, just CSV them, ie "@"DemureTestObserverm,XCTestLog" */ [USER_DEFS setObject:@"DemureTestObserver" forKey:@"XCTestObserverClass"]; [USER_DEFS synchronize]; } __atsortingbute__((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 

Sans le drapeau linker … (La police de la mode essaie la résortingbution avec Cupertino, pourtant le défaut d’Apple prévaut, comme on le souhaite, ici )

entrer la description de l'image ici

AVEC le drapeau linker -ldemure.a … (résultats compréhensibles, gasp … “merci constructor / destructor ” … acclamations de la foule ) entrer la description de l'image ici

Voici un autre exemple concret. C’est pour une bibliothèque partagée. La fonction principale de la bibliothèque partagée est de communiquer avec un lecteur de carte à puce. Mais il peut également recevoir des informations de configuration lors de l’exécution sur udp. L’UDP est géré par un thread qui DOIT être démarré au moment de l’initialisation.

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

La bibliothèque a été écrite en c.