web-dev-qa-db-fra.com

Comment fonctionne exactement __attribute __ ((constructeur))?

Il semble assez clair qu'il est censé mettre les choses en place.

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

Exemple en Objective-C :

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

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}
321
Casebash
  1. Il est exécuté quand une bibliothèque partagée est chargée, généralement lors du démarrage du programme.
  2. C'est comme ça que sont tous les attributs de GCC. sans doute pour les distinguer des appels de fonction.
  3. Syntaxe spécifique à GCC.
  4. Oui, cela fonctionne 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, le fonctionnement des constructeurs et des destructeurs est que le fichier d’objet partagé contient des sections spéciales (.ctors et .dtors sur ELF) qui contiennent des références aux fonctions marquées avec les attributs constructor et destructor, respectivement. Lorsque la bibliothèque est chargée/déchargée, le programme de chargement dynamique (ld.so ou autre) vérifie si de telles sections existent et, le cas échéant, appelle les fonctions qui y sont référencées.

À bien y penser, il existe probablement une magie similaire dans l’éditeur de liens statique normal, de sorte que le même code soit exécuté au démarrage/à l’arrêt, que l’utilisateur choisisse ou non des liens statiques ou dynamiques.

261
janneb

.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 le chargeur/l'éditeur de la liaison lorsque le code est chargé/déchargé. C'est à dire. à chaque chargement ELF (par exemple une bibliothèque partagée), le code dans .init sera exécuté. Il est encore possible d'utiliser ce mécanisme pour obtenir à peu près la même chose qu'avec __attribute__((constructor))/((destructor)). C'est la vieille école, mais cela présente certains avantages.

Le mécanisme .ctors/.dtors nécessite par exemple un support de la part de system-rtl/loader/linker-script. Ceci est loin d'être certain d'être disponible sur tous les systèmes, par exemple les systèmes profondément intégrés dans lesquels le code s'exécute sur du métal nu. C'est à dire. même si __attribute__((constructor))/((destructor)) est pris en charge par GCC, il n'est pas certain qu'il s'exécutera, il appartient à l'éditeur de liens de l'organiser et au chargeur (ou dans certains cas, au code de démarrage) de l'exécuter. Pour utiliser .init/.fini, le moyen le plus simple consiste à utiliser les drapeaux de l'éditeur de liens: -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 avantage possible est que le code dans .init est exécuté avant .ctors et le code dans .fini après .dtors. Si l’ordre est pertinent, c’est au moins un moyen simple mais simple de distinguer les fonctions init/exit.

Un inconvénient majeur est que vous ne pouvez pas facilement avoir plus d'une _init et une _fini fonction par module chargeable et que vous auriez probablement à fragmenter le code dans plus de .so que motivé. Une autre est que lorsque vous utilisez la méthode de l'éditeur de liens décrite ci-dessus, vous remplacez les fonctions _init et _fini par défaut d'origine (fournies par crti.o). C’est là que se produisent généralement toutes sortes d’initialisations (sous Linux, l’affectation des variables globales est initialisée). Un chemin qui est décrit ici

Notez dans le lien ci-dessus qu’une cascade vers l’original _init() n’est pas nécessaire car elle est toujours en place. La call dans l'assemblage en ligne est toutefois x86-mnémonique et appeler une fonction à partir de Assembly aurait une apparence complètement différente pour de nombreuses autres architectures (comme ARM, par exemple). C'est à dire. le code n'est pas transparent.

Les mécanismes .init/.fini et .ctors/.detors sont similaires, mais pas tout à fait. Le code dans .init/.fini s'exécute "en l'état". C'est à dire. vous pouvez avoir plusieurs fonctions dans .init/.fini, mais il est AFAIK syntaxiquement difficile de les insérer de manière totalement transparente en C pur sans briser le code dans de nombreux petits fichiers .so.

.ctors/.dtors sont organisés différemment de .init/.fini. Les sections .ctors/.dtors ne sont que des tableaux 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. l'appelant de la boucle peut être spécifique à l'architecture, mais comme cela fait partie du système (s'il existe, c'est-à-dire), cela n'a pas d'importance.

L'extrait suivant ajoute de nouveaux pointeurs de fonction au tableau de fonctions .ctors, principalement de la même manière que __attribute__((constructor)) (la méthode peut coexister avec __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;

On peut également ajouter les pointeurs de fonction à une section complètement différente inventée par soi-même. Un script de l'éditeur de liens modifié et une fonction supplémentaire imitant la boucle 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, ajouter des arguments en entrée et renvoyer le code gérant l'e.t.a. (Dans un projet C++, par exemple, il serait utile si vous devez exécuter quelque chose avant ou après les constructeurs globaux).

Je préférerais __attribute__((constructor))/((destructor)) si possible, c'est une solution simple et élégante même si on a l'impression de tricher. Pour les codeurs "bare metal" comme moi, ce n’est pas toujours une option.

Quelques bonnes références dans le livre Linkers & Loaders .

60
Michael Ambrus

Cette page fournit une excellente compréhension de la mise en œuvre des attributs constructor et destructor et des sections de leur fonctionnement dans ELF. Après avoir assimilé les informations fournies ici, j’ai rassemblé quelques informations supplémentaires et (en empruntant l’exemple de la section à 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 l'exemple de source.

Comme expliqué dans ce fil de discussion, les attributs constructor et destructor créent des entrées dans les sections _.ctors_ et _.dtors_ du fichier objet. Vous pouvez placer des références à des fonctions dans l'une ou l'autre section de trois manières. (1) en utilisant l’attribut section; (2) constructor et destructor attributs ou (3) avec un appel inline-Assembly (comme référencé le lien dans la réponse d'Ambrus).

L'utilisation des attributs constructor et destructor vous permet d'attribuer en outre une priorité au constructeur/destructeur afin de contrôler son ordre d'exécution avant que main() ne soit appelé ou après son retour. Plus la valeur de priorité donnée est basse, plus la priorité d'exécution est élevée (les priorités inférieures s'exécutent avant les priorités supérieures avant main () - et après les priorités supérieures après main ()). Les valeurs de priorité que vous donnez doivent être supérieures à _100_ car le compilateur réserve les valeurs de priorité entre 0 et 100 pour la mise en œuvre. Aconstructor ou destructor spécifié avec priorité s'exécute avant un constructor ou destructor spécifié sans priorité.

Avec l'attribut 'section' ou avec inline-Assembly, vous pouvez également placer des références de fonction dans les sections de code _.init_ et _.fini_ ELF qui s'exécuteront respectivement avant et après tout destructeur. Toute fonction appelée par la référence de fonction placée dans la section _.init_ sera exécutée avant la référence de fonction elle-même (comme d'habitude).

J'ai essayé d'illustrer chacun de ceux-ci dans l'exemple ci-dessous:

_#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;
}
_

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 permis de renforcer le comportement du constructeur/destructeur, espérons qu'il sera utile à d'autres également.

37
David C. Rankin

Voici un exemple "concret" (et éventuellement utile ) de comment, pourquoi et quand pour utiliser ces constructions pratiques, pourtant inesthétiques ...

Xcode utilise un "utilisateur" global "global" pour décider quelle classe XCTestObserver crache son coeur au console assiégée .

Dans cet exemple ... lorsque je charge implicitement cette bibliothèque psuedo, appelons-le ... libdemure.a, via un indicateur dans ma cible de test á la ..

OTHER_LDFLAGS = -ldemure

Je veux..

  1. À la charge (c'est-à-dire lorsque XCTest charge mon ensemble de tests), remplacez la classe "par défaut" XCTest "observateur" ... (via la fonction constructor) PS: dans la mesure du possible peut dire .. tout ce qui est fait ici peut être fait avec un effet équivalent dans la méthode + (void) load { ... } de ma classe.

  2. lancer mes tests .... dans ce cas, avec moins de verbosité dans les journaux (implémentation sur demande)

  3. Remettez la classe "globale" XCTestObserver à son état initial .. afin de ne pas gêner les autres courses XCTest qui n'ont pas suivi le mouvement (aka. Lié à libdemure.a). Je suppose que cela a été fait historiquement dans dealloc .. mais je ne suis pas sur le point de commencer à jouer avec cette vieille sorcière.

Alors...

#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

Sans le drapeau de l'éditeur de liens ... (l'essaim de policiers et de policiers Cupertino demande de rétribution, mais le défaut d'Apple prévaut, comme souhaité, ici )

enter image description here

AVEC le drapeau -ldemure.a de l'éditeur de liens ... (résultats compréhensibles, halète ... "merci constructor/destructor "... La foule applaudit enter image description here

7
Alex Gray

Voici un autre exemple concret. Il s’agit d’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. Le UDP est géré par un thread qui DOIT doit être démarré au moment de l’initialisation.

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

  }

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

1
drlolly