Comment fonctionne exactement __attribute __ ((constructeur))?


347

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

  1. Quand cela fonctionne-t-il exactement?
  2. Pourquoi y a-t-il deux parenthèses?
  3. Est __attribute__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 __attribute__((destructor))fonctionne-t-il?

Exemple en Objective-C :

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

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

Réponses:


273
  1. Il s'exécute lorsqu'une bibliothèque partagée est chargée, généralement au démarrage du programme.
  2. Voilà comment sont tous les attributs 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 s'exécute lorsque la bibliothèque partagée est déchargée, généralement à la sortie du programme.

Ainsi, la façon dont les constructeurs et destructeurs fonctionnent est que le fichier 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 constructeur et destructeur, respectivement. Lorsque la bibliothèque est chargée / déchargée, le programme de chargeur dynamique (ld.so ou somesuch) vérifie si de telles sections existent et, dans l'affirmative, 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 afin que le même code soit exécuté au démarrage / à l'arrêt, que l'utilisateur choisisse un lien statique ou dynamique.


49
Les doubles crochets facilitent leur "macro-out" ( #define __attribute__(x)). Si vous avez plusieurs attributs, par exemple, __attribute__((noreturn, weak))il serait difficile de "macro-out" s'il n'y avait qu'un seul ensemble de crochets.
Chris Jester-Young

7
Cela ne se fait pas avec .init/.fini. (Vous pouvez valablement avoir plusieurs constructeurs et destructeurs dans une seule unité de traduction, plusieurs nevermind dans une seule bibliothèque - comment cela fonctionnerait-il?) Au lieu de cela, sur les plateformes utilisant le format binaire ELF (Linux, etc.), les constructeurs et destructeurs sont référencés dans les sections .ctorset .dtorsde l'en-tête. Certes, dans l'ancien temps, les fonctions nommées initet finiseraient exécutées lors du chargement et du déchargement dynamiques de la bibliothèque si elles existaient, mais c'est obsolète maintenant, remplacé par ce meilleur mécanisme.
éphémère

7
@jcayzac Non, car les macros variadiques sont une extension gcc, et la principale raison de la macro __attribute__est si vous n'utilisez pas gcc, car c'est aussi une extension gcc.
Chris Jester-Young

9
@ Les macros variadiques ChrisJester-Young sont une fonctionnalité C99 standard, pas une extension GNU.
jcayzac

4
"votre utilisation du présent (" faire "au lieu de" fait "- les doubles parenthèses les rendent encore plus faciles à supprimer. Vous avez aboyé le mauvais arbre pédant.
Jim Balter

64

.init/ .finin'est pas déconseillé. Cela fait toujours partie de la norme ELF et j'oserais dire que ce sera pour toujours. Le code dans .init/ .finiest exécuté par le chargeur / runtime-linker lorsque le code est chargé / déchargé. C'est-à-dire sur chaque chargement ELF (par exemple une bibliothèque partagée) le code .initsera exécuté. Il est toujours possible d'utiliser ce mécanisme pour obtenir à peu près la même chose qu'avec __attribute__((constructor))/((destructor)). C'est de la vieille école, mais cela présente certains avantages.

.ctors/ Le .dtorsmécanisme nécessite par exemple le support 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 embarqués où le code s'exécute sur du métal nu. C'est-à-dire que même s'il __attribute__((constructor))/((destructor))est pris en charge par GCC, il n'est pas certain qu'il fonctionnera car c'est à 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/ à la .finiplace, la manière la plus simple consiste à utiliser des indicateurs 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 un système prenant en charge les deux méthodes, un avantage possible est que le code .initentrant est exécuté avant .ctorset le code entré .finiaprè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 _initet une _finifonction par module chargeable et que vous devrez probablement fragmenter le code plus .soque motivé. Un autre est que lorsque vous utilisez la méthode de l'éditeur de liens décrite ci-dessus, on remplace les fonctions d'origine _init et _finipar défaut (fournies par crti.o). C'est là que se produisent généralement toutes sortes d'initialisations (sous Linux, c'est là que l'affectation des variables globales est initialisée). Un moyen de contourner cela 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. Le calldans l'assemblage en ligne est cependant x86 mnémoniques et appeler une fonction de montage serait complètement différent pour beaucoup d' autres architectures (comme ARM par exemple). C'est-à-dire que le code n'est pas transparent.

.initLes mécanismes / .finiet .ctors/ .detorssont similaires, mais pas tout à fait. Le code dans .init/ .finis'exécute "tel quel". C'est-à-dire que vous pouvez avoir plusieurs fonctions dans .init/ .fini, mais il est AFAIK syntaxiquement difficile de les mettre de manière totalement transparente en C pur sans casser le code dans de nombreux petits .sofichiers.

.ctors/ .dtorssont organisés différemment de .init/ .fini. .ctorsLes .dtorssections / ne 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'appelant en 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 .ctorsfonction au tableau de fonctions, 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 auto-inventée complètement différente. Un script de l'éditeur de liens modifié et une fonction supplémentaire imitant le chargeur .ctors/ la .dtorsboucle sont nécessaires dans ce cas. Mais avec cela, on peut obtenir un meilleur contrôle sur l'ordre d'exécution, ajouter des arguments et renvoyer le code de gestion eta (dans un projet C ++ par exemple, il serait utile s'il a besoin de quelque chose en cours d'exécution avant ou après les constructeurs globaux).

Je préfère __attribute__((constructor))/((destructor))autant que possible, c'est une solution simple et élégante même si on a l'impression de tricher. Pour les codeurs nus comme moi, ce n'est tout simplement pas toujours une option.

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


comment le chargeur peut-il appeler ces fonctions? ces fonctions peuvent utiliser des globaux et d'autres fonctions dans l'espace d'adressage du processus, mais le chargeur est un processus avec son propre espace d'adressage, n'est-ce pas?
user2162550

@ user2162550 Non, ld-linux.so.2 (l '"interpréteur" habituel, le chargeur des bibliothèques dynamiques qui s'exécute sur tous les exécutables liés dynamiquement) s'exécute dans l'espace d'adressage même de l'exécutable lui-même. En général, le chargeur de bibliothèque dynamique lui-même est quelque chose de spécifique à l'espace utilisateur, s'exécutant dans le contexte du thread qui tente d'accéder à une ressource de bibliothèque.
Paul Stelian

Lorsque j'appelle execv () à partir du code qui a __attribute__((constructor))/((destructor))le destructeur ne fonctionne pas. J'ai essayé peu de choses comme l'ajout d'une entrée à .dtor comme indiqué ci-dessus. Mais pas de succès. Le problème est facile à dupliquer en exécutant le code avec numactl. Par exemple, supposons que test_code contient le destructeur (ajoutez un printf aux fonctions constructeur et desctructeur pour déboguer le problème). Ensuite, courez LD_PRELOAD=./test_code numactl -N 0 sleep 1. Vous verrez que le constructeur est appelé deux fois mais le destructeur une seule fois.
B Abali

39

Cette page fournit une grande compréhension de la constructoret la destructormise en œuvre d'attributs et les sections à l' intérieur au sein de ELF qui leur permettent de travailler. Après avoir digéré les informations fournies ici, j'ai compilé un peu d'informations supplémentaires et (en empruntant l'exemple de section à Michael Ambrus ci-dessus) j'ai créé un exemple pour illustrer les concepts et aider mon apprentissage. Ces résultats sont fournis ci-dessous avec l'exemple de source.

Comme expliqué dans ce fil, les attributs constructoret destructorcréent des entrées dans la section .ctorset .dtorsdu 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 soit l' sectionattribut; (2) constructoret destructorattributs ou (3) avec un appel d'assemblage en ligne (comme référencé le lien dans la réponse d'Ambrus).

L'utilisation des attributs constructoret destructorvous permet en outre d'attribuer une priorité au constructeur / destructeur pour contrôler son ordre d'exécution avant main()son appel 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 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 des valeurs de priorité comprises entre 0 et 100 pour l'implémentation. Un constructorou destructorspécifié avec priorité s'exécute avant un constructorou destructorspécifié sans priorité.

Avec l'attribut 'section' ou avec l'assemblage en ligne, vous pouvez également placer des références de fonction dans la section de code ELF .initet .finiqui s'exécuteront respectivement avant tout constructeur et après tout destructeur. Toutes les fonctions appelées par la référence de fonction placée dans la .initsection s'exécuteront avant la référence de fonction elle-même (comme d'habitude).

J'ai essayé d'illustrer chacun de ceux de 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;
}

production:

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, j'espère qu'il sera également utile à d'autres.


Où avez-vous constaté que "les valeurs de priorité que vous donnez doivent être supérieures à 100"? Cette information n'est pas présente dans la documentation des attributs de la fonction GCC.
Justin

4
IIRC, il y avait quelques références, PATCH: Support Priority Argument for Constructor / Destructor Arguments ( MAX_RESERVED_INIT_PRIORITY), et qu'elles étaient les mêmes que C ++ ( init_priority) 7.7 C ++ - Variable Specific, Function, and Type Attributes . Ensuite , je l'ai essayé avec 99: warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99)));.
David C. Rankin

1
Ah. J'ai essayé des priorités <100 avec clang et cela semblait fonctionner, mais mon cas de test simple (une seule unité de compilation) était trop simple .
Justin

1
Quelle est la priorité des variables globales statiques (ctors statiques)?
dashesy

2
L'effet et la visibilité d'un global statique dépendront de la façon dont votre programme est structuré (par exemple, un seul fichier, plusieurs fichiers ( unités de traduction )) et dans lequel le global est déclaré Voir: Statique (mot-clé) , en particulier la description de la variable globale statique .
David C. Rankin

7

Voici un exemple "concret" (et peut - être utile ) de comment, pourquoi et quand utiliser ces constructions pratiques, mais disgracieuses ...

Xcode utilise un «utilisateur par défaut» «global» pour décider quelle XCTestObserverclasse crache le cœur à la console assiégée .

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

OTHER_LDFLAGS = -ldemure

Je veux..

  1. Lors du chargement (c'est-à-dire lors du XCTestchargement de mon lot de tests), remplacez la XCTestclasse "par défaut" "observateur" ... (via la constructorfonction) PS: Autant que je sache .. tout ce qui est fait ici pourrait être fait avec un effet équivalent dans mon + (void) load { ... }méthode de classe .

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

  3. Remettez la XCTestObserverclasse "globale" dans son état d'origine ... afin de ne pas gâcher d'autres XCTestpistes qui n'ont pas pris le train en marche (aka. Liées à libdemure.a). Je suppose que cela a toujours été fait en dealloc.. mais je ne vais pas commencer à jouer avec cette vieille sorcière.

Donc...

#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 ... (La police de la mode fourmille de Cupertino exigeant des représailles , mais le défaut d'Apple prévaut, comme on le souhaite, ici )

entrez la description de l'image ici

AVEC le -ldemure.adrapeau de l' éditeur de liens ... (Résultats compréhensibles, halètement ... "merci constructor/ destructor" ... Acclamations de la foule ) entrez la description de l'image ici


1

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 via udp. L'udp est géré par un thread qui 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
Un choix étrange si la bibliothèque est écrite en C ++, car les constructeurs de variables globales ordinaires sont le moyen idiomatique d'exécuter le code pré-principal en C ++.
Nicholas Wilson

@NicholasWilson La bibliothèque a en fait été écrite en c. Je ne sais pas comment j'ai tapé c ++ au lieu de c.
drlolly
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.