448 votes

Comment fonctionne exactement __attribut__((constructeur)) ?

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

  1. Quand est-ce qu'il fonctionne exactement ?
  2. Pourquoi y a-t-il deux parenthèses ?
  3. Est __attribute__ une fonction ? Une macro ? Une syntaxe ?
  4. Cela fonctionne-t-il en C ? C++ ?
  5. La fonction avec laquelle il fonctionne doit-elle être statique ?
  6. Quand est-ce que __attribute__((destructor)) courir ?

Exemple en Objective-C :

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

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

356voto

janneb Points 17303
  1. Il s'exécute lorsqu'une bibliothèque partagée est chargée, généralement lors du démarrage du programme.
  2. C'est ainsi que sont tous les attributs GCC ; vraisemblablement 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 les destructeurs fonctionnent est que le fichier d'objets partagés 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 chargement dynamique (ld.so ou autre) vérifie si ces sections existent, et si c'est le cas, appelle les fonctions qui y sont référencées.

En y réfléchissant, il y a probablement une magie similaire dans le linker statique normal pour que le même code soit exécuté au démarrage/à l'arrêt, que l'utilisateur choisisse la liaison statique ou dynamique.

63 votes

Les doubles parenthèses permettent de les "macroser" facilement ( #define __attribute__(x) ). Si vous avez plusieurs attributs, par exemple, __attribute__((noreturn, weak)) il serait difficile de faire une "macro" s'il n'y avait qu'un seul jeu de crochets.

7 votes

Ce n'est pas fait avec .init/.fini . (Vous pouvez valablement avoir plusieurs constructeurs et destructeurs dans une seule unité de traduction, sans parler de plusieurs dans une seule bibliothèque - comment cela fonctionnerait-il ?) Au lieu de cela, sur les plates-formes utilisant le format binaire ELF (Linux, etc.), les constructeurs et les destructeurs sont référencés dans la balise .ctors et .dtors sections de l'en-tête. Il est vrai que dans le passé, les fonctions nommées init et fini serait exécuté lors du chargement et du déchargement de la bibliothèque dynamique s'il existait, mais cette méthode n'est plus utilisée, remplacée par ce meilleur mécanisme.

0 votes

@ephemient : Merci, j'avais oublié cette nouvelle façon de faire améliorée. Réponse mise à jour en conséquence.

76voto

Michael Ambrus Points 161

.init / .fini n'est pas déprécié. Il fait toujours partie du standard ELF et j'ose dire qu'il le sera toujours. Code en .init / .fini est exécuté par le loader/runtime-linker lorsque le code est chargé/déchargé. C'est à dire qu'à chaque chargement ELF (par exemple une bibliothèque partagée) le code dans le fichier .init sera exécuté. Il est toujours possible d'utiliser ce mécanisme pour réaliser à peu près la même chose qu'avec __attribute__((constructor))/((destructor)) . C'est de la vieille école mais ça a des avantages.

.ctors / .dtors par exemple nécessitent un support par system-rtl/loader/linker-script. Il est loin d'être certain que cela soit disponible sur tous les systèmes, par exemple les systèmes profondément embarqués où le code s'exécute sur le métal nu. C'est-à-dire que même si __attribute__((constructor))/((destructor)) est supporté par GCC, il n'est pas certain qu'il fonctionnera car c'est au linker de l'organiser et au loader (ou dans certains cas, au boot-code) de l'exécuter. Pour utiliser .init / .fini à la place, le moyen le plus simple est d'utiliser les drapeaux de liaison : -init & -fini (i.e. à partir de la ligne de commande de GCC, la syntaxe serait la suivante -Wl -init my_init -fini my_fini ).

Sur les systèmes supportant les deux méthodes, un avantage possible est que le code en .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 rudimentaire mais facile de distinguer les fonctions init/exit.

L'inconvénient majeur est qu'il n'est pas facile d'en avoir plusieurs. _init et un _fini pour chaque module chargeable et il faudrait probablement fragmenter le code en plusieurs fonctions. .so que motivé. Une autre raison est que lorsque l'on utilise la méthode de liaison décrite ci-dessus, on remplace les fichiers originaux _init et _fini les fonctions par défaut (fournies par crti.o ). C'est là que toutes sortes d'initialisations se produisent habituellement (sous Linux, c'est là que l'affectation des variables globales est initialisée). Une façon de contourner cela est décrite ici

Remarquez dans le lien ci-dessus qu'une cascade vers l'original _init() n'est pas nécessaire puisqu'il est toujours en place. Le site call dans l'assemblage en ligne est cependant mnémotechnique x86 et l'appel d'une fonction à partir de l'assemblage serait complètement différent pour de nombreuses autres architectures (comme ARM par exemple). En d'autres termes, le code n'est pas transparent.

.init / .fini et .ctors / .detors Les mécanismes sont similaires, mais pas tout à fait. Le code en .init / .fini fonctionne "tel quel". C'est-à-dire que vous pouvez avoir plusieurs fonctions dans le fichier .init / .fini mais il est difficile, d'un point de vue syntaxique, de les intégrer de manière totalement transparente en C pur sans casser le code en de nombreux petits morceaux. .so des fichiers.

.ctors / .dtors sont organisées différemment que .init / .fini . .ctors / .dtors sont toutes deux de simples tableaux contenant des pointeurs vers des fonctions, et l'"appelant" est une boucle fournie par le système qui appelle chaque fonction indirectement. En d'autres termes, l'appelant de la boucle peut être spécifique à l'architecture, mais comme il fait partie du système (si tant est qu'il existe), cela n'a pas d'importance.

L'extrait suivant ajoute de nouveaux pointeurs de fonction à l'objet .ctors principalement de la même manière que la fonction __attribute__((constructor)) fait (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 linker modifié script et une fonction supplémentaire imitant le loader. .ctors / .dtors Une boucle est nécessaire dans ce cas. Mais avec elle, on peut obtenir un meilleur contrôle de l'ordre d'exécution, ajouter la gestion du code in-argument et du code de retour, etc. (dans un projet C++, par exemple, elle serait utile si l'on avait besoin d'exécuter quelque chose avant ou après les constructeurs globaux).

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

Quelques bonnes références dans le livre Liaisons et chargeurs .

0 votes

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

1 votes

@user2162550 Non, ld-linux.so.2 (l'"interpréteur" habituel, le chargeur de bibliothèques dynamiques qui s'exécute sur tous les exécutables liés dynamiquement) s'exécute dans l'espace d'adressage 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.

0 votes

Lorsque j'appelle execv() à partir du code qui a __attribute__((constructor))/((destructor)) le destructeur ne s'exécute pas. J'ai essayé plusieurs choses comme l'ajout d'une entrée dans le fichier .dtor comme indiqué ci-dessus. Mais sans 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 constructor et desctructor pour déboguer le problème). Ensuite, exécutez LD_PRELOAD=./test_code numactl -N 0 sleep 1 . Vous verrez que le constructeur est appelé deux fois mais le destructeur une seule fois.

57voto

David C. Rankin Points 2674

Cette page permet de mieux comprendre le constructor et destructor et les sections de l'ELF qui leur permettent de fonctionner. 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 de 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 la source de l'exemple.

Comme expliqué dans ce fil de discussion, le constructor et destructor créent des entrées dans le .ctors et .dtors du fichier objet. Vous pouvez placer des références à des fonctions dans l'une ou l'autre section de l'une des trois manières suivantes. (1) en utilisant soit le section attribut ; (2) constructor et destructor ou (3) avec un appel à l'assemblage en ligne (comme référencé dans le lien de la réponse d'Ambrus).

L'utilisation de constructor et destructor vous permettent d'attribuer une priorité supplémentaire au constructeur/destructeur afin de contrôler son ordre d'exécution avant l'exécution de l'action. main() est 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 doit être supérieure à 100 car le compilateur réserve des valeurs de priorité entre 0 et 100 pour l'implémentation. A constructor ou destructor spécifié avec une priorité s'exécute avant une constructor ou destructor spécifié sans priorité.

À l'aide de l'attribut "section" ou de l'assemblage en ligne, vous pouvez également placer des références à des fonctions dans le champ .init et .fini Section de code ELF qui sera exécutée 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 s'exécutera avant la référence de la fonction elle-même (comme d'habitude).

J'ai essayé d'illustrer chacun d'entre eux 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 cimenter le comportement des constructeurs/destructeurs, en espérant qu'il sera également utile à d'autres.

0 votes

Où avez-vous trouvé que "les valeurs de priorité que vous donnez doivent être supérieures à 100" ? Cette information n'est pas présente sur le Documentation sur les attributs des fonctions GCC.

7 votes

IIRC, il y avait quelques références, PATCH : Support de l'argument de priorité pour les arguments des constructeurs/destructeurs ( MAX_RESERVED_INIT_PRIORITY ), et qu'ils étaient les mêmes que C++ ( init_priority ) 7.7 Attributs de variables, de fonctions et de types spécifiques au C++ . Puis j'ai essayé avec 99 : warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99))); .

1 votes

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

12voto

Quinn Taylor Points 29688

Excellente réponse de @janneb, très utile. Juste pour être complet, voici un lien vers les documents GCC correspondants .

Je suis content d'être tombé sur cette question, ça m'a permis de éliminer deux douzaines de lignes de code passe-partout et des appels de fonction redondants pour effectuer une initialisation à l'échelle de la bibliothèque dans mon cadre Objective-C.

NOTE : Bien que cette syntaxe soit spécifique à GCC, LLVM et Clang ont été écrits pour le supporter également, et le code compilé à l'aide de Clang-LLVM semble fonctionner tout comme le code compilé par GCC.

8voto

alex gray Points 5089

Voici un exemple "concret" (et éventuellement utile ) exemple de comment, pourquoi et quand d'utiliser ces outils pratiques, mais disgracieux les constructions...

Xcode utilise une valeur "globale" "par défaut de l'utilisateur" pour décider de l'identité de l'utilisateur. XCTestObserver classe crache son cœur à la assiégé console.

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

OTHER_LDFLAGS = -ldemure

Je veux

  1. En charge (c'est-à-dire lorsque XCTest charge mon paquet de test), remplacez l'option "par défaut". XCTest la classe "observateur"... (via la classe constructor ) PS : Pour autant que je puisse dire tout ce qui est fait ici pourrait être fait avec un effet équivalent dans ma classe'. + (void) load { ... } méthode.

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

  3. Retourner le "global" XCTestObserver à son état originel pour ne pas salir les autres. XCTest qui n'ont pas pris le train en marche (c'est-à-dire qui sont liées à libdemure.a ). Je suppose que cela a été fait historiquement dans dealloc mais je ne suis pas prêt à 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... (La police de la mode envahit Cupertino) exigeant des représailles Pourtant, le défaut d'Apple prévaut, comme on le souhaite, ici )

enter image description here

Avec le -ldemure.a linker flag... (Résultats compréhensibles, halètement ... "merci constructor / destructor "... La foule applaudit ) enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X