Mis à jour, voir ci-dessous!
J'ai entendu et lu que C ++ 0x permet à un compilateur d'imprimer "Hello" pour l'extrait suivant
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
Cela a apparemment quelque chose à voir avec les threads et les capacités d'optimisation. Il me semble que cela peut surprendre beaucoup de gens.
Quelqu'un a-t-il une bonne explication de la raison pour laquelle cela était nécessaire pour permettre? Pour référence, le projet le plus récent de C ++ 0x indique à6.5/5
Une boucle qui, en dehors de l'instruction for-init dans le cas d'une instruction for,
- ne fait aucun appel aux fonctions d'E / S de la bibliothèque, et
- n'accède ni ne modifie les objets volatils, et
- n'effectue aucune opération de synchronisation (1.10) ou opération atomique (Article 29)
peut être supposé par la mise en œuvre se terminer. [Remarque: Ceci est destiné à permettre les transformations du compilateur, telles que la suppression de boucles vides, même lorsque la terminaison ne peut pas être prouvée. - note de fin]
Éditer:
Cet article perspicace parle de ce texte de normes
Malheureusement, les mots «comportement indéfini» ne sont pas utilisés. Cependant, chaque fois que le standard dit "le compilateur peut supposer P", il est sous-entendu qu'un programme qui a la propriété not-P a une sémantique non définie.
Est-ce correct et le compilateur est-il autorisé à imprimer "Bye" pour le programme ci-dessus?
Il y a un fil encore plus perspicace ici , qui concerne un changement analogue à C, commencé par le gars qui a fait l'article lié ci-dessus. Entre autres faits utiles, ils présentent une solution qui semble s'appliquer également à C ++ 0x ( mise à jour : cela ne fonctionnera plus avec n3225 - voir ci-dessous!)
endless:
goto endless;
Un compilateur n'est pas autorisé à optimiser cela, semble-t-il, car ce n'est pas une boucle, mais un saut. Un autre gars résume le changement proposé dans C ++ 0x et C201X
En écrivant une boucle, le programmeur affirme soit que la boucle fait quelque chose avec un comportement visible (effectue des E / S, accède à des objets volatils, ou effectue une synchronisation ou des opérations atomiques), soit qu'elle finit par se terminer. Si je viole cette hypothèse en écrivant une boucle infinie sans effets secondaires, je mens au compilateur et le comportement de mon programme n'est pas défini. (Si j'ai de la chance, le compilateur peut m'en avertir.) Le langage ne fournit pas (ne fournit plus?) Un moyen d'exprimer une boucle infinie sans comportement visible.
Mise à jour du 3.1.2011 avec n3225: le Comité a déplacé le texte au 1.10 / 24 et dit
L'implémentation peut supposer que n'importe quel thread effectuera éventuellement l'une des opérations suivantes:
- mettre fin,
- appeler une fonction d'E / S de bibliothèque,
- accéder ou modifier un objet volatil, ou
- effectuer une opération de synchronisation ou une opération atomique.
L' goto
astuce ne fonctionnera plus!
la source
while(1) { MyMysteriousFunction(); }
doit être compilable indépendamment sans connaître la définition de cette fonction mystérieuse, non? Alors, comment pouvons-nous déterminer s'il appelle des fonctions d'E / S de bibliothèque? En d'autres termes: cette première puce qui pourrait être formulée ne fait certainement aucun appel aux fonctions .int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;
enfor(int i = 0; i < 10; ++i) do_something(&i); int x = 2;
? Ou peut-être l'inverse, enx
étant initialisé2
avant la boucle. Il peut diredo_something
ne se soucie pas de la valeur dex
, donc c'est une optimisation parfaitement sûre, sido_something
cela ne fait pasi
changer la valeur de de telle sorte que vous vous retrouvez dans une boucle infinie.main() { start_daemon_thread(); while(1) { sleep(1000); } }
pourrait simplement se terminer immédiatement au lieu d'exécuter mon démon dans un thread d'arrière-plan?Réponses:
Oui, Hans Boehm fournit une justification à cela dans N1528: Pourquoi un comportement indéfini pour des boucles infinies? , bien qu'il s'agisse du document WG14, la justification s'applique également au C ++ et le document fait référence à la fois au WG14 et au WG21:
La seule différence majeure avec C est que C11 fournit une exception pour contrôler les expressions qui sont des expressions constantes qui diffère de C ++ et rend votre exemple spécifique bien défini en C11.
la source
do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);
code Hoisting après la boucle qui ne dépendrait pas dex
cela, semble sûr et raisonnable, mais permettre à un compilateur d'assumer unx==23
tel code semble plus dangereux qu'utile.Pour moi, la justification pertinente est:
Vraisemblablement, c'est parce qu'il est difficile de prouver la terminaison mécaniquement , et l'incapacité de prouver la terminaison gêne les compilateurs qui pourraient autrement faire des transformations utiles, telles que déplacer des opérations non dépendantes d'avant la boucle à après ou vice versa, effectuer des opérations post-boucle dans un thread tout en la boucle s'exécute dans une autre, et ainsi de suite. Sans ces transformations, une boucle peut bloquer tous les autres threads pendant qu'ils attendent que l'un des threads termine ladite boucle. (J'utilise "thread" de manière approximative pour désigner toute forme de traitement parallèle, y compris les flux d'instructions VLIW séparés.)
EDIT: Exemple stupide:
Ici, il serait plus rapide pour un thread de faire le
complex_io_operation
pendant que l'autre effectue tous les calculs complexes de la boucle. Mais sans la clause que vous avez citée, le compilateur doit prouver deux choses avant de pouvoir effectuer l'optimisation: 1) quicomplex_io_operation()
ne dépend pas des résultats de la boucle, et 2) que la boucle se terminera . Prouver 1) est assez facile, prouver que 2) est le problème d'arrêt. Avec la clause, il peut supposer que la boucle se termine et obtenir une victoire de parallélisation.J'imagine aussi que les concepteurs ont considéré que les cas où des boucles infinies se produisent dans le code de production sont très rares et sont généralement des choses comme des boucles événementielles qui accèdent aux E / S d'une manière ou d'une autre. En conséquence, ils ont pessimisé le cas rare (boucles infinies) au profit de l'optimisation du cas le plus courant (boucles non infinies, mais difficiles à prouver mécaniquement non infinies).
Cela signifie cependant que les boucles infinies utilisées dans les exemples d'apprentissage en souffriront et soulèveront des pièges dans le code débutant. Je ne peux pas dire que ce soit une bonne chose.
EDIT: en ce qui concerne l'article perspicace que vous liez maintenant, je dirais que "le compilateur peut supposer X à propos du programme" est logiquement équivalent à "si le programme ne satisfait pas X, le comportement n'est pas défini". Nous pouvons le montrer comme suit: supposons qu'il existe un programme qui ne satisfait pas la propriété X. Où serait défini le comportement de ce programme? La norme définit uniquement le comportement en supposant que la propriété X est vraie. Bien que le Standard ne déclare pas explicitement le comportement indéfini, il l'a déclaré non défini par omission.
Considérez un argument similaire: "le compilateur peut supposer qu'une variable x n'est affectée au plus qu'une fois entre les points de séquence" équivaut à "attribuer à x plus d'une fois entre les points de séquence n'est pas défini".
la source
complex_io_operation
.Je pense que l'interprétation correcte est celle de votre édition: les boucles infinies vides sont un comportement indéfini.
Je ne dirais pas que c'est un comportement particulièrement intuitif, mais cette interprétation a plus de sens que l'alternative, à savoir que le compilateur est arbitrairement autorisé à ignorer des boucles infinies sans invoquer UB.
Si les boucles infinies sont UB, cela signifie simplement que les programmes sans terminaison ne sont pas considérés comme significatifs: selon C ++ 0x, ils n'ont pas de sémantique.
Cela a également un certain sens. Il s'agit d'un cas particulier, où un certain nombre d'effets secondaires ne se produisent tout simplement plus (par exemple, rien n'est jamais retourné
main
), et un certain nombre d'optimisations du compilateur sont entravées par la nécessité de conserver des boucles infinies. Par exemple, déplacer des calculs à travers la boucle est parfaitement valide si la boucle n'a pas d'effets secondaires, car finalement, le calcul sera effectué dans tous les cas. Mais si la boucle ne se termine jamais, nous ne pouvons pas réorganiser le code en toute sécurité, car nous pourrions simplement changer les opérations réellement exécutées avant que le programme ne se bloque. À moins que nous ne traitions un programme suspendu comme UB, c'est-à-dire.la source
while(1){...}
. Ils l'utilisent même régulièrementwhile(1);
pour invoquer une réinitialisation assistée par chien de garde.Je pense que cela va dans le sens de ce type de question , qui fait référence à un autre fil . L'optimisation peut parfois supprimer des boucles vides.
la source
X
et motif de bitsx
, le compilateur peut démontrer que la boucle ne se termine pas sansX
maintenir le motif de bitsx
. C'est complètement vrai. OnX
pourrait donc considérer que n'importe quel motif de bits est maintenu, et c'est aussi mauvais que UB en ce sens que pour le malX
etx
cela en causera rapidement. Je pense donc que vous devez être plus précis dans votre standardese. Il est difficile de parler de ce qui se passe "à la fin" d'une boucle infinie et de le montrer équivalent à une opération finie.Le problème pertinent est que le compilateur est autorisé à réorganiser le code dont les effets secondaires ne sont pas en conflit. L'ordre d'exécution surprenant pourrait se produire même si le compilateur produisait du code machine sans fin pour la boucle infinie.
Je pense que c'est la bonne approche. La spécification du langage définit les moyens d'appliquer l'ordre d'exécution. Si vous voulez une boucle infinie qui ne peut pas être réorganisée, écrivez ceci:
la source
unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);
le premierfprintf
pourrait s'exécuter, mais pas le second (le compilateur pourrait déplacer le calculdummy
entre les deuxfprintf
, mais pas au-delà de celui qui imprime sa valeur).Je pense que le problème pourrait peut-être mieux être énoncé, comme suit: «Si un morceau de code ultérieur ne dépend pas d'un morceau de code antérieur et que le morceau de code antérieur n'a aucun effet secondaire sur aucune autre partie du système, la sortie du compilateur peut exécuter le dernier morceau de code avant, après ou mélangé avec l'exécution du premier, même si le premier contient des boucles, sans se soucier du moment ou du fait que l'ancien code se terminerait réellement . Par exemple, le compilateur pourrait réécrire:
comme
Généralement pas déraisonnable, bien que je puisse craindre que:
deviendrait
En demandant à un processeur de gérer les calculs et à un autre de gérer les mises à jour de la barre de progression, la réécriture améliorerait l'efficacité. Malheureusement, cela rendrait les mises à jour de la barre de progression moins utiles qu'elles ne devraient l'être.
la source
Ce n'est pas décidable pour le compilateur pour les cas non triviaux s'il s'agit d'une boucle infinie.
Dans différents cas, il peut arriver que votre optimiseur atteigne une meilleure classe de complexité pour votre code (par exemple, c'était O (n ^ 2) et vous obtiendrez O (n) ou O (1) après optimisation).
Donc, inclure une telle règle qui interdit la suppression d'une boucle infinie dans le standard C ++ rendrait de nombreuses optimisations impossibles. Et la plupart des gens ne veulent pas de ça. Je pense que cela répond tout à fait à votre question.
Autre chose: je n'ai jamais vu d'exemple valide où vous avez besoin d'une boucle infinie qui ne fait rien.
Le seul exemple dont j'ai entendu parler était un horrible piratage qui devrait vraiment être résolu autrement: il s'agissait de systèmes embarqués où le seul moyen de déclencher une réinitialisation était de geler l'appareil afin que le chien de garde le redémarre automatiquement.
Si vous connaissez un exemple valide / bon où vous avez besoin d'une boucle infinie qui ne fait rien, veuillez me le dire.
la source
Je pense qu'il vaut la peine de souligner que des boucles qui seraient infinies, à l'exception du fait qu'elles interagissent avec d'autres threads via des variables non volatiles et non synchronisées peuvent maintenant donner un comportement incorrect avec un nouveau compilateur.
En d'autres termes, rendez vos globaux volatils - ainsi que les arguments passés dans une telle boucle via un pointeur / référence.
la source
volatile
n'est ni nécessaire ni suffisant, et cela nuit considérablement aux performances.