web-dev-qa-db-fra.com

Ordre d'initialisation des variables statiques

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 fonctionne séparément pour chacune d'entre elles (j'entends des variables statiques en dehors des classes).

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

Où puis-je voir quelques explications sur cette commande pour gcc et MSVC (je sais que cela est une très mauvaise idée - il s'agit simplement de comprendre les problèmes que nous pouvons avoir avec le code hérité lorsque nous passons à un nouveau système d'exploitation majeur et différent de GCC) ?

56
Dmitry Khalatov

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

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

En effet, 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: Check out ld

J'ai constaté que même en modifiant l'ordre des objets, les fichiers liés peuvent 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 génération. Même essayer de résoudre le problème est pratiquement un non-démarreur.

Ce n'est généralement qu'un problème lors de l'initialisation globale qui se référencent pendant leur propre initialisation (de sorte que seuls les objets avec constructeurs sont concernés).

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

  • Initialisation paresseuse.
  • Comptoir Schwarz
  • Placez toutes les variables globales complexes dans la même unité de compilation.
67
Martin York

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 permet de utiliser init_priority de spécifier explicitement le classement pour les acteurs globaux:

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

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

sorties 'ABC' comme on peut s'y attendre, mais

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

sorties 'BAC'.

17
Mike F

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 y a 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 à 4.7 s’initialisent dans l’ordre inverse de la ligne de liaison. Ce ticket dans GCC est le moment où le changement a eu lieu et il a cassé beaucoup de programmes qui dépendaient de l'ordre d'initialisation (y compris le mien!).

2) Dans GCC et Clang, l’utilisation de priorité de la fonction constructeur peut modifier l’ordre d’initialisation. Notez que cela ne s'applique qu'aux fonctions déclarées comme "constructeurs" (c'est-à-dire qu'elles doivent être exécutées exactement comme le serait un constructeur d'objet global). 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é ne concerne que les autres fonctions ayant des priorités, mais les véritables 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 très propre et utile dans la bibliothèque partagée (DLL) appelée DllMain () , qui, si elle est définie, sera exécutée avec le paramètre "fdwReason" égal à DLL_PROCESS_ATTACH directement après toutes les données globales. a été initialisé et avant l’application consommatrice a la possibilité d’appeler n’importe quelle fonction de la DLL. Ceci est extrêmement utile dans certains cas, et il y a absolument n'est pas comportement analogue à celui-ci sur d'autres plateformes avec 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 la plupart 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. Si vous souhaitez que l'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 fichiers source. . Cependant, si vous souhaitez en avoir un comme le le dernier à initialiser (ce qui peut effectivement répliquer le comportement de DllMain ()!), Vous pouvez alors appeler add_library () avec ce fichier source à produire. 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 l'indicateur --whole-archive pour forcer l'éditeur de liens à ne pas supprimer les symboles inutilisés de ce fichier d'archive spécifique.

Astuce de fermeture

Pour connaître absolument l'ordre d'initialisation résultant de votre application/bibliothèque partagée liée, transmettez --print-map à l'éditeur de liens ld et à grep pour .init_array (ou dans GCC avant la version 4.7, grep pour .ctors). Chaque constructeur global sera imprimé dans l'ordre dans lequel il sera initialisé, 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 SO posts et forums Internet. La plupart d’entre elles ont été apprises après de nombreuses expériences et j’espère que cela fera gagner du temps à certaines personnes!

12
Nicholas Smith

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

edit: osgx fourni un meilleur link .

4
Ray Tayek

Outre les commentaires de Martin, issus de l'arrière-plan 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 étant initialisées au chargement du programme, avant l'exécution du code. L’ordre exact dans lequel cela se produit peut être déterminé 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.

Éditer: En fonction de l'ordre de construction des objets statiques, ils risquent d'être non portables et devraient probablement être évités.

1
SmacL

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

0
Darien Pardinas