64
votes

En C ++, l'initialisation d'une variable globale avec elle-même a-t-elle un comportement non défini?

int i = i;

int main() { 
 int a = a;
 return 0;
} 
int  a = a surely has undefined behaviour (UB), and more details on it is in
Is reading an uninitialized value always an undefined behaviour? Or are there exceptions to it?.But what about int i = i? In C++ we are allowed to assign nonconstant values to globals. i is declared and zero initialized (since it has file scope) before the declaration is encountered. In which case we are assigning 0 to it later in the definition.
Is it safe to say this does not have UB?

3 commentaires

Oui, c'est sûr comme vous le dites parce que les objets de durée de stockage statique sont zéro-initialisés avant toute autre initialisation


La portée du fichier est un concept de C. Le concept correspondant dans C ++ est Scope de l'espace de noms .


La raison pour laquelle les initialiseurs ont une visibilité sur l'identifiant initialisé est que les références récursives / circulaires sont possibles comme struct circular_list x = {& x, & x} . C'est à cela que c'est.


3 Réponses :


57
votes

Étonnamment, ce n'est pas un comportement non défini.

Initialisation statique [Basic.Start.Static]

Une initialisation constante est effectuée si un objet variable ou temporaire Avec le stockage statique ou du thread, la durée de stockage est constante. si L'initialisation constante n'est pas réalisée, une variable avec statique La durée de stockage ou le fil de filetage La durée de stockage est zéro-initialisée . Ensemble, zéro-initialisation et initialisation constante sont appelées initialisation statique; Toute autre initialisation est dynamique initialisation. Toute initialisation statique se produit fortement avant tout Initialisation dynamique .

parties importantes audacieuses. "Initialisation statique" inclut l'initialisation des variables globales, "Durée de stockage statique" inclut les variables globales, et la clause ci-dessus est applicable ici:

int i = i;

Ce n'est pas une initialisation constante. Par conséquent, la zéro-initialisation est effectuée en fonction de la clause ci-dessus (pour les types de base entiers, l'initialisation zéro signifie, sans surprise, qu'elle est définie sur 0). La clause ci-dessus spécifie également que l'initialisation zéro doit avoir lieu avant l'initialisation dynamique.

Donc, que se passe-t-il ici:

  1. i est initialisé à 0.
  2. i est ensuite initialisé dynamiquement, de lui-même, il reste donc 0.


19 commentaires

C'est hilarant. Cela vous permet de créer un objet non constructible! struct s {s () = delete; } s = s;


@Raymondchen en fait utile pour le pré C ++ 20 Lambdas sans capture, qui n'ont pas de constructeur par défaut: stackoverflow.com/a/57012585/ 1896169 .


Comment cela changerait-il pour un alias mondial? int & i = i;


Ci-dessus est une bonne question. Aime également connaître sa différence dans la portée des fichiers vs la portée du bloc.


@Dan dans la portée du bloc, sur la pile, il n'est pas zéro-initialisé mais les ordures laissées. Ainsi, vous lisez à partir d'une variable non initialisée,


@ Jdługosz donc c'est juste une référence à elle-même. Fonctionne comme une variable normale. À l'échelle mondiale, la référence est zéro initialisée, localement est la valeur des ordures et UB?


@Dan voir en.cppreference.com/w/cpp/language/lifetime et en.cppreference.com/w/cpp/language/storage_duration .


@Raymondchen Cela semble également déclencher un mauvais avertissement de clang :) godbolt.org/z/hjg7o7aee


Mais quand commence la durée de vie de i ? Lorsque l'initiation statique est terminée ou lorsque l'initiation dynamique est terminée? Si c'est le premier, alors qu'en est-il, par exemple un std :: string s; ? Tenter de le lire avant que Dynamic Init se termine d'une manière ou d'une autre non UB?


@Raymondchen: N'avons-nous pas la même chose avec l'initialisation de Brace? Comme struct s {s () = delete; }; S s {};


@HolyBlackCat La Lifetime commence au début de l'exécution du programme. Vous confondez-vous avec Scope ? Ces mots ont des définitions précises en C ++ et si vous n'utilisez pas la bonne terminologie, vous vous retrouvez avec une salade de mots. La scope de i commence après l'analyse de son le déclarant . Autrement dit, lorsqu'il trouve le = .


Nous ne savons pas si i est toujours nul à l'heure dynamique, car les constructeurs globaux auraient pu le changer, non? C'est comme si une affectation i = i se produit au moment de l'initialisation dynamique. Puisqu'il s'agit d'un NOOP, il pourrait être optimisé.


@ Jdługosz ma terminologie me semble correct. "La durée de vie commence au début de l'exécution du programme" citation nécessaire. Voir ma réponse pour un devis standard, qui indique qu'il commence lorsque l'initialisation de i est terminée.


@Holyblackcat La vie est quand (dans le temps) la valeur est valide. La Scope est où dans le code source auquel le nom peut être mentionné. Pour le i dans le code op, qui est un int dans la portée de l'espace de noms (pas autre i défini dans le thread des commentaires), la durée de vie Démarrer lorsque le programme se charge, avant que tout code ne soit réellement exécuté. Il sera toujours un int valide et sera chargé comme 0 avec l'image de code.


@ Jdługosz Oui, je voulais dire "vie". Encore une fois, pouvez-vous fournir la source pour "La durée de vie démarre lorsque le programme se charge" ? Vous pourriez être intéressé par cette question .


Voici initialisation constante , "Il est garanti qu'il est complet avant toute autre initialisation de Un objet statique ou local de filetage commence, et il peut être effectué au moment de la compilation. " Donc, si vous vous demandez si "chargé de l'image" est une norme formelle, je suis d'accord que bien que ce soit ce qui se passe sur les compilateurs réels, la norme ne peut pas utiliser de tels termes, mais plutôt là, peu importe à quel point le code accède tôt ce.


@ Jdługosz "se demander si" chargé de l'image "est formel" pas vraiment, je remets en question autre chose. Veuillez lire le lien dans mon commentaire précédent, il explique mes doutes.


@Holyblackcat: IMHO, la façon dont la norme C ++ tente de décrire la durée de vie des objets de couche standard est une abstraction qui fuit inutilement qui conduit à des cas d'angle ambigus et cassés. Je pense qu'il a été adopté parce que spécifier que chaque région de stockage qui ne contient aucun objet de mise en page non standard contient simultanément tous les objets standard qui s'adapteront, et le modification affectera d'autres qui partagent le même stockage, inhiberait l'optimisation, Mais il aurait été préférable d'écrire des règles sur ce que les objets peuvent être abordés ou accessibles quand adopter ...


... une abstraction qui fuit sur les objets "existent". Je doute que les personnes qui ont formulé l'abstraction C ++ aient vraiment considéré tous les cas d'angle possibles, ou auraient une réponse consensuelle quant à ce que la norme est censée signifier dans tous.



4
votes

7 commentaires

Non ce n'est pas. int i = i; est égal à int i; i = i; . stackoverflow.com/a/67663586/14940626


Intéressant. La définition de la durée de vie a changé entre C ++ 17 et C ++ 20, et cette note de bas de page a changé pour correspondre. Cela contredit le Exemple dans [Basic.start. Dynamic] / 5, mais les exemples sont également non normatifs, et il semble probable qu'ils ont juste raté la mise à jour de cet exemple.


@Dan cette question concerne C, et int i; i = i; n'est même pas légal dans la portée de l'espace de noms.


@aschepler pouvez-vous expliquer la contradiction?


Si quoi que ce soit, je pense que cette note de bas de page (non normative) contredit la note (non normative) dans [basic.start.static] / 3 .


@dfrib argh. Édité.


Les définitions ont changé ... est logique, car nous avons maintenant des constructeurs constexpr . Vous ne pouvez donc pas dire que les types primitifs fonctionnent dans un sens et que les types avec les constructeurs ont une allocation et une initialisation de stockage distinctes.



0
votes

Il me semble int i = i; a un comportement non défini, n'est pas causé par la valeur indéterminée. Le terme valeur indéterminée est conçu pour les objets qui ont durée de stockage automatique ou dynamique .

[Basic.indet # 1]

Lorsque le stockage d'un objet avec durée de stockage automatique ou dynamique est obtenu, l'objet a une valeur indéterminée , et si aucune initialisation n'est effectuée pour l'objet, cet objet conserve une valeur indéterminée jusqu'à ce que cette valeur soit remplacée ([expr.ass]).

[Basic.indet # 2]

Si une valeur indéterminée est produite par une évaluation, le comportement n'est pas défini sauf dans les cas suivants ...

Dans votre exemple, l'objet nommé i a une durée de stockage statique, il n'est donc pas dans l'étendue de parler de la valeur indéterminée. Et, un tel objet a une zéro-initialisation qui se produit avant toute initialisation dynamique selon [Basic.Start.Static # 2]

Ensemble, la zéro-initialisation et l'initialisation constante sont appelées initialisation statique; Toute autre initialisation est une initialisation dynamique. Toute l'initialisation statique se produit fortement avant ([intro.Races]) toute initialisation dynamique.

Par conséquent, sa valeur initiale est nul. Lorsque i est utilisé comme initialiseur pour s'initialiser. qui est une initialisation dynamique et il obéit à [dcl.init].

Sinon, la valeur initiale de l'objet initialisé est la valeur (éventuellement convertie) de l'expression de l'initialisation.

Il viole la règle dans [Basic.lifetime]

Le programme a un comportement non défini si:

  • La glvalue est utilisée pour accéder à l'objet, ou

0 commentaires