Initialisation du CRT
Cet article décrit comment le CRT initialise l’état global dans le code natif.
Par défaut, l’éditeur de liens inclut la bibliothèque CRT, qui fournit son propre code de démarrage. Ce code de démarrage initialise la bibliothèque CRT, appelle les initialiseurs globaux, puis appelle la fonction main
fournie par l’utilisateur pour les applications console.
Il est possible, bien que non recommandé, de tirer parti du comportement de l’éditeur de liens spécifique à Microsoft pour insérer vos propres initialiseurs globaux dans un ordre spécifique. Ce code n’est pas portable et est fourni avec des avertissements importants.
Initialisation d’un objet global
Considérez le code C++ suivant (C n’autorise pas ce code, car il n’autorise pas un appel de fonction dans une expression constante).
int func(void)
{
return 3;
}
int gi = func();
int main()
{
return gi;
}
Selon la norme C/C++, func()
doit être appelé avant l’exécution de main()
. Mais qui l’appelle ?
L’une des façons de déterminer l’appelant consiste à définir un point d’arrêt dans func()
, à déboguer l’application et à examiner la pile. Il est possible, car le code source CRT est inclus dans Visual Studio.
Lorsque vous parcourez les fonctions sur la pile, vous verrez que le CRT appelle une liste de pointeurs de fonction. Ces fonctions sont similaires à func()
celles des constructeurs pour les instances de classe.
Le CRT obtient la liste des pointeurs de fonction du compilateur Microsoft C++. Lorsque le compilateur voit un initialiseur global, il génère un initialiseur dynamique dans la .CRT$XCU
section où CRT
se trouve le nom de la section et XCU
le nom du groupe. Pour obtenir la liste des initialiseurs dynamiques, exécutez la commande dumpbin /all main.obj
, puis recherchez la .CRT$XCU
section. La commande s’applique uniquement lorsqu’elle main.cpp
est compilée en tant que fichier C++, et non en tant que fichier C. Il doit être similaire à cet exemple :
SECTION HEADER #6
.CRT$XCU name
0 physical address
0 virtual address
4 size of raw data
1F2 file pointer to raw data (000001F2 to 000001F5)
1F6 file pointer to relocation table
0 file pointer to line numbers
1 number of relocations
0 number of line numbers
40300040 flags
Initialized Data
4 byte align
Read Only
RAW DATA #6
00000000: 00 00 00 00 ....
RELOCATIONS #6
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- -------
00000000 DIR32 00000000 C ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))
La bibliothèque CRT définit deux pointeurs :
__xc_a
dans.CRT$XCA
__xc_z
dans.CRT$XCZ
Aucun groupe n’a d’autres symboles définis à l’exception __xc_a
et __xc_z
.
Maintenant, lorsque l’éditeur de liens lit différentes .CRT
sous-sections (la partie après le $
), il les combine dans une section et les trie par ordre alphabétique. Cela signifie que les initialiseurs globaux définis par l’utilisateur (que le compilateur Microsoft C++ place) .CRT$XCU
viennent toujours après .CRT$XCA
et avant .CRT$XCZ
.
La section doit ressembler à cet exemple :
.CRT$XCA
__xc_a
.CRT$XCU
Pointer to Global Initializer 1
Pointer to Global Initializer 2
.CRT$XCZ
__xc_z
La bibliothèque CRT utilise les deux __xc_a
et __xc_z
détermine le début et la fin de la liste des initialiseurs globaux en raison de la façon dont elles sont disposées en mémoire une fois l’image chargée.
Fonctionnalités de l’éditeur de liens pour l’initialisation
La norme C++ ne fournit pas un moyen conforme de spécifier l’ordre relatif entre les unités de traduction pour un initialiseur global fourni par l’utilisateur. Toutefois, étant donné que l’éditeur de liens Microsoft trie les .CRT
sous-sections par ordre alphabétique, il est possible de tirer parti de cet ordre pour spécifier l’ordre d’initialisation. Nous vous déconseillons cette technique spécifique à Microsoft et elle peut s’interrompre dans une prochaine version. Nous l’avons documenté uniquement pour vous empêcher de créer du code rompu de manière difficile à diagnostiquer.
Pour éviter les problèmes dans votre code, à partir de Visual Studio 2019 version 16.11, nous avons ajouté deux nouveaux nouveaux avertissements par défaut : C5247 et C5248. Activez ces avertissements pour détecter les problèmes lors de la création de vos propres initialiseurs.
Vous pouvez ajouter des initialiseurs aux noms de section réservée inutilisés pour les créer dans un ordre relatif spécifique pour les initialiseurs dynamiques générés par le compilateur :
#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;
#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;
Les noms .CRT$XCT
et .CRT$XCV
ne sont pas utilisés par le compilateur ou la bibliothèque CRT pour l’instant, mais il n’est pas garanti qu’ils restent inutilisés à l’avenir. Et vos variables peuvent toujours être optimisées par le compilateur. Prenez en compte les problèmes potentiels d’ingénierie, de maintenance et de portabilité avant d’adopter cette technique.
Voir aussi
_initterm, _initterm_e
Fichiers C runtime (CRT) et bibliothèque standard C++ (STL) .lib