Il semble assez clair qu'il est censé mettre les choses en place.
__attribute__
est-il une fonction? Une macro? Syntaxe?__attribute__((destructor))
s'exécute-t-il?__attribute__((constructor))
static void initialize_navigationBarImages() {
navigationBarImages = [[NSMutableDictionary alloc] init];
}
__attribute__((destructor))
static void destroy_navigationBarImages() {
[navigationBarImages release];
}
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.
.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 .
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.
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..
À 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.
lancer mes tests .... dans ce cas, avec moins de verbosité dans les journaux (implémentation sur demande)
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 )
AVEC le drapeau -ldemure.a
de l'éditeur de liens ... (résultats compréhensibles, halète ... "merci constructor
/destructor
"... La foule applaudit )
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.