Question

C ++ garantit que les variables d'une unité de compilation (fichier .cpp) sont initialisées dans l'ordre de la déclaration. Pour le nombre d’unités de compilation, cette règle s’applique séparément pour chacune d’elles (je veux dire des variables statiques en dehors des classes).

Cependant, l'ordre d'initialisation des variables n'est pas défini pour les différentes unités de compilation.

Où puis-je trouver des explications sur cette commande pour gcc et MSVC (je sais que s’y fier est une très mauvaise idée - c’est juste pour comprendre les problèmes que nous pourrions avoir avec le code hérité lorsqu’on passe à la nouvelle système d'exploitation différent)?

Était-ce utile?

La solution

Comme vous le dites, l'ordre n'est pas défini entre différentes unités de compilation.

Dans la même unité de compilation, l'ordre est bien défini: même ordre que définition.

Cela est dû au fait que cela n'est pas résolu au niveau de la langue, mais au niveau de l'éditeur de liens. Vous devez donc consulter la documentation de l’éditeur de liens. Bien que je doute vraiment que cela aidera de manière utile.

Pour gcc: consultez ld

J'ai constaté que même le fait de modifier l'ordre des objets liés aux fichiers liés pouvait modifier l'ordre d'initialisation. Vous devez donc vous préoccuper non seulement de votre éditeur de liens, mais également de la manière dont il est appelé par votre système de construction. Même essayer de résoudre le problème est pratiquement un non-démarreur.

C’est généralement un problème uniquement lors de l’initialisation de globales se référant les uns aux autres au cours de leur propre initialisation (de sorte que seuls les objets dotés de constructeurs sont concernés).

Il existe des techniques pour résoudre le problème.

  • Initialisation paresseuse.
  • Schwarz Counter
  • Placez toutes les variables globales complexes dans la même unité de compilation.
  • Note 1: globals:
    Utilisé de manière vague pour faire référence aux variables de durée de stockage statique potentiellement initialisées avant main () .
  • Note 2: Potentiellement
    Dans le cas général, nous nous attendons à ce que les variables de durée de stockage statique soient initialisées avant principal, mais le compilateur est autorisé à différer l’initialisation dans certaines situations (les règles sont complexes, voir norme pour plus de détails).

Autres conseils

Je suppose que l'ordre du constructeur entre les modules est principalement fonction de l'ordre dans lequel vous transmettez les objets à l'éditeur de liens.

Cependant, GCC vous laisse utiliser init_priority spécifier explicitement la commande pour les acteurs globaux:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

génère "ABC" comme vous le souhaitiez, mais

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

renvoie 'BAC'.

Puisque vous savez déjà que vous ne devriez pas vous fier à cette information à moins que cela ne soit absolument nécessaire, la voici. Mon observation générale à travers différentes chaînes d'outils (MSVC, gcc / ld, clang / llvm, etc.) est que l'ordre dans lequel les fichiers d'objet sont transmis à l'éditeur de liens correspond à l'ordre dans lequel ils seront initialisés.

Il existe des exceptions à cela, et je ne prétends pas à toutes, mais voici celles que j'ai rencontrées moi-même:

1) Les versions de GCC antérieures à la version 4.7 sont en réalité initialisées dans l'ordre inverse de la ligne de liaison. Ce ticket dans GCC correspond à la date à laquelle le changement s'est produit. des programmes dépendant de l'ordre d'initialisation (y compris le mien!).

2) Dans GCC et Clang, utilisation de constructeur la priorité des fonctions peut modifier l’ordre d’initialisation. Notez que cela ne s'applique qu'aux fonctions déclarées comme "constructeurs". (c’est-à-dire qu’ils devraient être exécutés exactement comme le serait un constructeur d’objets globaux). J'ai essayé d'utiliser des priorités comme celle-ci et j'ai constaté que même avec la priorité la plus élevée sur une fonction constructeur, tous les constructeurs sans priorité (par exemple, les objets globaux normaux, les fonctions constructeur sans priorité) seront initialisés premier . En d’autres termes, la priorité n’est relative qu’aux autres fonctions ayant des priorités, mais les vrais citoyens de première classe sont ceux qui n’ont pas de priorité. Pour aggraver les choses, cette règle est effectivement l’inverse dans GCC avant 4.7 en raison du point 1 ci-dessus.

3) Sous Windows, il existe une fonction de point d'entrée de bibliothèque partagée (DLL) très soignée et utile appelée DllMain () , qui, s'il est défini, sera exécuté avec le paramètre " fdwReason " égal à DLL_PROCESS_ATTACH directement après l'initialisation de toutes les données globales et avant avant que l'application ne puisse appeler aucune fonction de la DLL. Ceci est extrêmement utile dans certains cas, et il n’existe absolument pas de comportement similaire à celui d’autres plates-formes utilisant GCC ou Clang avec C ou C ++. Le plus proche que vous trouverez est de créer une fonction constructeur avec priorité (voir le point (2) ci-dessus), ce qui n’est absolument pas la même chose et ne fonctionnera pas pour beaucoup des cas d’utilisation pour lesquels DllMain () fonctionne.

4) Si vous utilisez CMake pour générer vos systèmes de construction, ce que je fais souvent, j’ai constaté que l’ordre des fichiers source en entrée correspond à l’ordre des fichiers objets résultants donnés à l’éditeur de liens. Cependant, votre application / DLL est souvent liée dans d'autres bibliothèques, auquel cas ces bibliothèques se trouveront sur la ligne de liaison après vos fichiers source d'entrée. Si vous souhaitez qu'un de vos objets globaux soit le tout premier à initialiser, vous avez de la chance et vous pouvez placer le fichier source contenant cet objet au premier de la liste des sources. des dossiers. Cependant, si vous souhaitez en faire un tout dernier à initialiser (ce qui permet de répliquer efficacement le comportement de DllMain ()!), Vous pouvez alors appeler add_library () avec ce fichier source. produisez une bibliothèque statique et ajoutez la bibliothèque statique résultante en tant que dernière dépendance de lien dans votre appel target_link_libraries () pour votre application / DLL. Veillez à ce que votre objet global puisse être optimisé dans ce cas et vous pouvez utiliser le - archive entière pour obliger l'éditeur de liens à ne pas supprimer les symboles inutilisés de ce fichier d'archive spécifique.

Conseil de clôture

Pour connaître absolument l'ordre d'initialisation résultant de votre application / bibliothèque partagée liée, passez --print-map à l'éditeur de liens ld et à grep pour .init_array (ou dans GCC avant la version 4.7, grep pour .ctors). Tous les constructeurs globaux seront imprimés dans l'ordre dans lequel ils seront initialisés, et rappelez-vous que l'ordre est inverse dans GCC avant 4.7 (voir le point (1) ci-dessus).

Le facteur de motivation pour écrire cette réponse est que je devais connaître cette information, n'avoir d'autre choix que de m'appuyer sur l'ordre d'initialisation, et ne trouver que peu d'éléments de cette information dans d'autres posts SO et forums Internet. La plupart de ces connaissances ont été apprises après de nombreuses expériences et j'espère que cela fera gagner du temps à certaines personnes!

http://www.parashift.com/c++ -faq-lite / ctors.html # faq-10.12 - ce lien se déplace. Ce un est plus stable mais vous devrez le rechercher.

edit: osgx a fourni un meilleur lien .

Outre les commentaires de Martin, issus du contexte C, je pense toujours aux variables statiques comme faisant partie de l’exécutable du programme, de l’espace incorporé et alloué dans le segment de données. Ainsi, les variables statiques peuvent être considérées comme initialisées lors du chargement du programme, avant l'exécution du code. Vous pouvez déterminer l’ordre exact dans lequel cela se produit en regardant le segment de données du fichier de carte généré par l’éditeur de liens, mais dans la plupart des cas, l’initialisation est simultanée.

Modifier: en fonction de l'ordre de construction des objets statiques, ceux-ci risquent d'être non portables et devraient probablement être évités.

Si vous voulez vraiment connaître l'ordre final, je vous recommande de créer une classe dont le constructeur enregistre l'horodatage en cours et de créer plusieurs instances statiques de la classe dans chacun de vos fichiers cpp afin de connaître l'ordre d'initialisation final. . Assurez-vous de mettre quelques opérations fastidieuses dans le constructeur pour ne pas avoir le même horodatage pour chaque fichier.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top