Mis à jour, voir ci-dessous !
J'ai entendu et lu que C++0x permet au compilateur d'imprimer "Hello" pour le snippet suivant
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
Cela a apparemment quelque chose à voir avec les fils et les capacités d'optimisation. Il me semble que cela peut surprendre beaucoup de gens cependant.
Quelqu'un a-t-il une bonne explication de la raison pour laquelle il était nécessaire d'autoriser cela ? Pour référence, le projet C++0x le plus récent dit à 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 pas aux objets volatils et ne les modifie pas, et
- n'effectue pas d'opérations de synchronisation (1.10) ou d'opérations atomiques (Clause 29)
peut être supposé par l'implémentation se terminer. [Note : Ceci a pour but de permettre des transformations du compilateur, telles que la suppression des boucles vides, même lorsque l'implémentation se termine. mations du compilateur, telles que la suppression des boucles vides, même lorsque la terminaison ne peut être prouvée. - note de fin ]
Editar:
Cet article perspicace dit à propos de ce texte sur les normes
Malheureusement, les mots "comportement indéfini" ne sont pas utilisés. Cependant, chaque fois que la norme dit "le compilateur peut supposer P", il est sous-entendu qu'un programme qui a la propriété not-P a une sémantique indéfinie.
Est-ce correct, et le compilateur est-il autorisé à imprimer "Bye" pour le programme ci-dessus ?
Il y a un encore plus perspicace fil ici qui traite d'un changement analogue au C, lancé par le type de l'article 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 plus bas !)
endless:
goto endless;
Il semble qu'un compilateur ne soit pas autorisé à optimiser cela, car il ne s'agit pas d'une boucle, mais d'un saut. Un autre type 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 (effectuer des E/S, accéder à des objets volatils, ou effectue des opérations de synchronisation ou atomiques), o qu'il finit par se terminer. Si je viole cette hypothèse en écrivant une boucle infinie sans effet secondaire, je mens au compilateur et compilateur, et le comportement de mon programme est indéfini. (Si j'ai de la chance, le compilateur pourrait m'avertir à ce sujet). Le langage ne fournit pas (ne fournit plus ?) un moyen d'exprimer une boucle infinie sans comportement visible. comportement visible.
Mise à jour le 3.1.2011 avec n3225 : Le comité a déplacé le texte à 1.10/24 et dit
L'implémentation peut supposer que tout thread fera finalement l'une des choses suivantes :
- résilier,
- faire un appel à une fonction d'E/S de la bibliothèque,
- accéder ou modifier un objet volatil, ou
- effectuer une opération de synchronisation ou une opération atomique.
El goto
l'astuce sera no plus travailler !
0 votes
L'auto-parallélisation peut-être ? J'aimerais en savoir plus mais éliminer cette boucle est certainement équivalent à l'exécuter sur un thread parallèle qui ne rapporte jamais. D'un autre côté, un thread qui génère des informations pour l'appelant serait imprégné d'une sorte de synchronisation. Et un thread avec des effets secondaires appropriés ne pourrait pas être parallélisé.
0 votes
Dans le cas général, cette boucle serait une erreur de programmation.
4 votes
while(1) { MyMysteriousFunction(); }
doit pouvoir être compilé indépendamment sans connaître la définition de cette mystérieuse fonction, non ? Alors comment pouvons-nous déterminer si elle fait appel à des fonctions d'E/S de la bibliothèque ? En d'autres termes, le premier point pourrait sûrement être formulé ainsi ne fait aucun appel aux fonctions .20 votes
@Daniel : S'il a accès à la définition de la fonction, il peut prouver beaucoup de choses. Il existe une chose telle que l'optimisation interprocédurale.
0 votes
L'article que vous avez cité explique pourquoi cela est autorisé. Vous ne l'avez pas comprise, vous n'êtes pas d'accord avec elle, ou elle ne vous a pas satisfait d'une autre manière ?
0 votes
@Philip : les boucles qui ne font absolument rien, mais qui n'ont pas de cas de terminaison facile, sont peu courantes et ne constituent pas un problème majeur pour l'optimisation.
0 votes
@Philip je ne comprends pas. Quelles sont les transformations du compilateur que cette optimisation facilite ? Et pourquoi est-ce seulement pour C++0x que cela est autorisé ? Maintenant, je suis un gros noob du threading, donc je n'ai aucune idée à ce sujet.
4 votes
Actuellement, dans C++03, un compilateur est-il autorisé à modifier
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 dans l'autre sens, avecx
étant initialisé à2
avant la boucle. Il peut indiquerdo_something
ne se soucie pas de la valeur dex
Il s'agit donc d'une optimisation parfaitement sûre, sido_something
n'entraîne pas la valeur dei
à changer de telle sorte que vous vous retrouvez dans une boucle infinie.5 votes
Cela veut-il dire que
main() { start_daemon_thread(); while(1) { sleep(1000); } }
pourrait simplement sortir immédiatement au lieu de faire tourner mon démon dans un fil d'arrière-plan ?2 votes
"Cet article perspicace" suppose qu'un comportement spécifique est un comportement non défini simplement parce qu'il n'existe pas de comportement explicite et défini. C'est une supposition incorrecte. En général, lorsque la norme laisse ouvert un nombre fini de comportements, une implémentation doit choisir l'un de ces comportements ( Non spécifié comportement). Cela ne doit pas nécessairement être déterministe. La fin d'une boucle de type "do-nothing" est sans doute un choix booléen ; soit elle se termine, soit elle ne se termine pas. Faire quelque chose d'autre n'est pas autorisé.
0 votes
@Gabe. J'en doute. Ce que l'optimisation signifie, c'est que tout ce qui se passe après que votre
while(1)
pourrait être déplacée avant la boucle par le compilateur. Le sitewhile(1)
s'exécute toujours, elle peut simplement s'exécuter avant ou après d'autres instructions. La vraie question est de savoir ce qui se passe si vous lancezexit(EXIT_SUCCESS)
après la boucle.0 votes
@kts non, cela signifie que le compilateur peut supposer que la boucle se termine. Si une boucle qui n'a pas d'effet secondaire se termine, vous ne pouvez pas remarquer combien d'itérations elle a eu, à part un certain délai d'exécution qui est entièrement spécifique à la performance de votre ordinateur. En vertu de la règle as-if, le compilateur est alors autorisé à optimiser la boucle entièrement. Donc je pense, dans le commentaire ci-dessus, que si
sleep
Si la définition de @Gabe fait des appels d'E/S ou si l'implémentation définit "sleep" comme une fonction d'E/S elle-même, la boucle dans le commentaire de @Gabe ne peut pas être supposée se terminer.2 votes
@kts : Le code après la boucle qui n'affecte ni n'est affecté par la boucle peut être déplacé avant celle-ci. Le code après la boucle qui affecterait ou serait affecté par la boucle doit attendre que la boucle se termine. Je m'attendrais donc à ce que le lancer se produise lorsque la boucle se termine, si jamais elle se termine.
0 votes
C'est fascinant mais existe-t-il un cas pratique où un compilateur rejette une boucle qui fait réellement quelque chose ? Imaginez que vous déposiez un bogue auprès d'une équipe de compilateurs et que vous obteniez une réponse négative parce que la norme stipule que la boucle doit être rejetée. Il est peu probable que les compilateurs analysent le flux et traitent les inconnus comme des mauvais cas.
2 votes
Puis-je suggérer de réorganiser ce post pour qu'il ne contienne que des citations de C++11 (ou C++14) ; la progression historique de la règle à travers les projets n'est pas cruciale.
2 votes
@Gabe : Non. sleep() s'étend à un appel système donc il ne va pas l'optimiser.
0 votes
Cela semble dangereux/problématique. Je ne veux pas que le compilateur "optimise" mes boucles si elles étaient intentionnelles, même si elles comportent peut-être un bug logique. Je devrais être responsable de l'identification et de la résolution de mon bug, et ne pas dépendre du compilateur pour faire des hypothèses sur le comportement de mon algorithme.
0 votes
@h0r53 : Si par exemple un programme définit une fonction comme
unsigned normalize_lsb(unsigned x) { while(!(x & 1)) x>>=1; return x;}
et qu'un compilateur peut s'assurer que la valeur de retour d'un appel particulier n'est jamais utilisée, devrait-on exiger d'un compilateur qu'il génère du code qui se bloque six
est zéro ? Pouvoir ignorer la valeur dex
dans les cas où la valeur de retour n'est pas utilisée (et utiliser le fait quex
est ignoré pour omettre à son tour le code dont le seul but serait de le calculer) est une optimisation utile, sur les compilateurs dont le comportement est limité à proprement omettre ou différer ces boucles.