Optimisation d'un "while (1)"; en C ++ 0x

153

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' gotoastuce ne fonctionnera plus!

Johannes Schaub - litb
la source
4
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 .
Daniel Earwicker
19
@Daniel: S'il a accès à la définition de la fonction, il peut prouver beaucoup de choses. L'optimisation interprocédurale existe.
Potatoswatter
3
À l'heure actuelle, en C ++ 03, un compilateur est-il autorisé à se transformer int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;en for(int i = 0; i < 10; ++i) do_something(&i); int x = 2;? Ou peut-être l'inverse, en xétant initialisé 2avant la boucle. Il peut dire do_somethingne se soucie pas de la valeur de x, donc c'est une optimisation parfaitement sûre, si do_something cela ne fait pas ichanger la valeur de de telle sorte que vous vous retrouvez dans une boucle infinie.
Dennis Zickefoose
4
Cela signifie-t-il que cela 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?
Gabe
2
«Cet article perspicace» suppose qu'un comportement spécifique est un comportement indéfini simplement parce qu'il n'y a pas de comportement explicite et défini. C'est une hypothèse incorrecte. En général, lorsque le standard laisse ouvert un nombre fini de comportements, une implémentation doit choisir l'un de ceux-ci ( Comportement non spécifié ). Cela n'a pas besoin d'être déterministe. La fin d'une boucle ne faisant rien est sans doute un choix booléen; soit il le fait, soit il ne le fait pas. Faire autre chose n'est pas autorisé.
MSalters

Réponses:

33

Quelqu'un a-t-il une bonne explication de la raison pour laquelle cela était nécessaire pour permettre?

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:

Comme N1509 le souligne correctement, le projet actuel donne essentiellement un comportement indéfini à des boucles infinies dans 6.8.5p6. Un problème majeur pour ce faire est que cela permet au code de se déplacer à travers une boucle potentiellement sans terminaison. Par exemple, supposons que nous ayons les boucles suivantes, où count et count2 sont des variables globales (ou ont eu leur adresse prise), et p est une variable locale, dont l'adresse n'a pas été prise:

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

Ces deux boucles pourraient-elles être fusionnées et remplacées par la boucle suivante?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

Sans la dispense spéciale du 6.8.5p6 pour les boucles infinies, cela serait interdit: si la première boucle ne se termine pas parce que q pointe vers une liste circulaire, l'original n'écrit jamais dans count2. Ainsi, il pourrait être exécuté en parallèle avec un autre thread qui accède ou met à jour count2. Ce n'est plus sûr avec la version transformée qui accède à count2 malgré la boucle infinie. Ainsi, la transformation introduit potentiellement une course aux données.

Dans des cas comme celui-ci, il est très peu probable qu'un compilateur puisse prouver la terminaison de boucle; il faudrait comprendre que q pointe vers une liste acyclique, ce qui, à mon avis, dépasse les capacités de la plupart des compilateurs traditionnels, et souvent impossible sans des informations complètes sur le programme.

Les restrictions imposées par les boucles sans terminaison sont une restriction sur l'optimisation des boucles de terminaison pour lesquelles le compilateur ne peut pas prouver la terminaison, ainsi que sur l'optimisation des boucles réellement non terminales. Les premiers sont beaucoup plus courants que les seconds et souvent plus intéressants à optimiser.

Il existe clairement aussi des boucles for avec une variable de boucle entière dans lesquelles il serait difficile pour un compilateur de prouver la terminaison, et il serait donc difficile pour le compilateur de restructurer les boucles sans 6.8.5p6. Même quelque chose comme

for (i = 1; i != 15; i += 2)

ou

for (i = 1; i <= 10; i += j)

semble non trivial à manipuler. (Dans le premier cas, une théorie des nombres de base est nécessaire pour prouver la terminaison, dans le second cas, nous devons savoir quelque chose sur les valeurs possibles de j pour ce faire. Le bouclage pour les entiers non signés peut compliquer encore une partie de ce raisonnement. )

Ce problème semble s'appliquer à presque toutes les transformations de restructuration de boucle, y compris la parallélisation du compilateur et les transformations d'optimisation du cache, qui sont toutes deux susceptibles de gagner en importance et sont déjà souvent importantes pour le code numérique. Cela semble susceptible de se transformer en un coût substantiel au profit de pouvoir écrire des boucles infinies de la manière la plus naturelle possible, d'autant plus que la plupart d'entre nous écrivons rarement des boucles intentionnellement infinies.

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.

Shafik Yaghmour
la source
1
Y a-t-il des optimisations sûres et utiles qui sont facilitées par le langage actuel qui ne seraient pas aussi bien facilitées en disant "Si la fin d'une boucle dépend de l'état de n'importe quel objet, le temps nécessaire pour exécuter la boucle n'est pas considéré effet secondaire observable, même si ce temps se trouve être infini ". Compte tenu du do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);code Hoisting après la boucle qui ne dépendrait pas de xcela, semble sûr et raisonnable, mais permettre à un compilateur d'assumer un x==23tel code semble plus dangereux qu'utile.
supercat
47

Pour moi, la justification pertinente est:

Ceci est destiné à permettre les transformations du compilateur, telles que la suppression des boucles vides, même lorsque la terminaison ne peut pas être prouvée.

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:

while (complicated_condition()) {
    x = complicated_but_externally_invisible_operation(x);
}
complex_io_operation();
cout << "Results:" << endl;
cout << x << endl;

Ici, il serait plus rapide pour un thread de faire le complex_io_operationpendant 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) qui complex_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".

Philip Potter
la source
«Prouver 1) est assez facile» - en fait, cela ne découle-t-il pas immédiatement des 3 conditions pour que le compilateur soit autorisé à assumer la terminaison de boucle selon la clause que Johannes demande? Je pense qu'ils reviennent à «la boucle n'a aucun effet observable, sauf peut-être tourner pour toujours», et la clause garantit que «tourner pour toujours» n'est pas un comportement garanti pour de telles boucles.
Steve Jessop
@Steve: c'est facile si la boucle ne se termine pas; mais si la boucle se termine, alors elle peut avoir un comportement non trivial qui affecte le traitement du complex_io_operation.
Philip Potter
Oups, oui, j'ai raté le fait que cela pourrait modifier les locaux / alias / tout ce qui sont utilisés dans l'opération IO. Vous avez donc raison: même si cela ne suit pas nécessairement, il existe de nombreux cas dans lesquels les compilateurs peuvent prouver et prouvent qu'aucune modification de ce type ne se produit.
Steve Jessop
"Cela signifie cependant que les boucles infinies utilisées dans l'apprentissage des exemples en souffriront et soulèveront des pièges dans le code du débutant. Je ne peux pas dire que ce soit une bonne chose."
Compilez
1
@supercat: Ce que vous décrivez est ce qui va se passer dans la pratique, mais ce n'est pas ce que le projet de norme exige. Nous ne pouvons pas supposer que le compilateur ne sait pas si une boucle se terminera. Si le compilateur ne sait la boucle ne sera pas fin, il peut faire ce qu'il aime. Le DS9K va créer des démons nasales pour une boucle infinie sans E / S etc. ( Par conséquent, les résout DS9K le problème de l' arrêt.)
Philip Potter
15

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.

jalf
la source
7
«Les boucles infinies vides sont un comportement indéfini»? Alan Turing ne serait pas d'accord, mais seulement quand il se remettrait de tourner dans sa tombe.
Donal Fellows
11
@Donal: Je n'ai jamais rien dit sur sa sémantique dans une machine de Turing. Nous discutons de la sémantique d'une boucle infinie sans effet secondaire en C ++ . Et comme je l'ai lu, C ++ 0x choisit de dire que de telles boucles ne sont pas définies.
jalf
Des boucles infinies vides seraient idiotes, et il n'y aurait aucune raison d'avoir des règles spéciales pour elles. La règle est conçue pour traiter des boucles utiles d'une durée illimitée (espérons-le pas infinie), qui calculent quelque chose qui sera nécessaire à l'avenir mais pas immédiatement.
supercat le
1
Cela signifie-t-il que C ++ 0x n'est pas adapté aux périphériques intégrés? Presque tous les périphériques embarqués sont sans terminaison et font leur travail dans une grosse masse while(1){...}. Ils l'utilisent même régulièrement while(1);pour invoquer une réinitialisation assistée par chien de garde.
vsz
1
@vsz: la première forme est bonne. Les boucles infinies sont parfaitement bien définies, tant qu'elles ont une sorte de comportement observable. La deuxième forme est plus délicate, mais je peux penser à deux solutions très simples: (1) un compilateur ciblant les périphériques embarqués pourrait simplement choisir de définir un comportement plus strict dans ce cas, ou (2) vous créez un corps qui appelle une fonction de bibliothèque factice . Tant que le compilateur ne sait pas ce que fait cette fonction, il doit supposer qu'elle peut avoir un effet secondaire, et donc il ne peut pas jouer avec la boucle.
jalf
8

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.

linuxuser27
la source
3
Bonne question. On dirait que ce type a eu exactement le problème que ce paragraphe permet à ce compilateur de causer. Dans la discussion liée à l'une des réponses, il est écrit que "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 le la propriété not-P a une sémantique non définie. " . Cela me surprend. Cela signifie-t-il que mon exemple de programme ci-dessus a un comportement indéfini et peut simplement se séparer de nulle part?
Johannes Schaub - litb
@Johannes: le texte "peut être supposé" n'apparaît nulle part ailleurs dans le brouillon que j'ai sous la main, et "peut supposer" n'apparaît que quelques fois. Bien que j'aie vérifié cela avec une fonction de recherche qui ne correspond pas aux sauts de ligne, j'en ai donc peut-être manqué. Je ne suis donc pas sûr que la généralisation de l'auteur soit justifiée par les preuves, mais en tant que mathématicien, je dois admettre la logique de l'argument, que si le compilateur suppose quelque chose de faux, alors en général, il peut déduire n'importe quoi ...
Steve Jessop
... Permettre une contradiction dans le raisonnement du compilateur sur le programme fait certainement allusion à UB, car en particulier il permet au compilateur, pour tout X, de déduire que le programme est équivalent à X. Permettre sûrement au compilateur de déduire que c'est lui permettant de le faire . Je suis d'accord avec l'auteur, aussi, que si UB est prévu, il devrait être explicitement indiqué, et si ce n'est pas prévu, alors le texte de spécification est erroné et devrait être corrigé (peut-être par l'équivalent en langage de spécification de, "le compilateur peut remplacer le boucle avec du code sans effet ", je ne suis pas sûr).
Steve Jessop
@SteveJessop: Que penseriez-vous de simplement dire que l'exécution de n'importe quel morceau de code - y compris des boucles infinies - peut être reportée jusqu'à ce que quelque chose que le morceau de code ait fait affecterait un comportement de programme observable, et que dans ce but règle, le temps nécessaire pour exécuter un morceau de code - même s'il est infini - n'est pas un «effet secondaire observable». Si un compilateur peut démontrer qu'une boucle ne peut pas se terminer sans une variable contenant une certaine valeur, la variable peut être considérée comme détenant cette valeur, même s'il peut également être montré que la boucle ne peut pas se terminer si elle contient cette valeur.
supercat
@supercat: comme vous l'avez déclaré, je ne pense pas que cela améliore les choses. Si la boucle ne se termine jamais, alors pour tout objet Xet motif de bits x, le compilateur peut démontrer que la boucle ne se termine pas sans Xmaintenir le motif de bits x. C'est complètement vrai. On Xpourrait 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 mal Xet xcela 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.
Steve Jessop
8

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:

volatile int dummy_side_effect;

while (1) {
    dummy_side_effect = 0;
}

printf("Never prints.\n");
Daniel Newby
la source
2
@ JohannesSchaub-litb: Si une boucle - sans fin ou non - ne lit ou n'écrit aucune variable volatile pendant l'exécution, et n'appelle aucune fonction qui pourrait le faire, un compilateur est libre de différer n'importe quelle partie de la boucle jusqu'à le premier effort pour accéder à quelque chose qui y est calculé. Étant donné que unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);le premier fprintfpourrait s'exécuter, mais pas le second (le compilateur pourrait déplacer le calcul dummyentre les deux fprintf, mais pas au-delà de celui qui imprime sa valeur).
supercat
1

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:

void testfermat (int n)
{
  int a = 1, b = 1, c = 1;
  tandis que (pow (a, n) + pow (b, n)! = pow (c, n))
  {
    si (b> a) a ++; sinon si (c> b) {a = 1; b ++}; sinon {a = 1; b = 1; c ++};
  }
  printf ("Le résultat est");
  printf ("% d /% d /% d", a, b, c);
}

comme

void testfermat (int n)
{
  si (fork_is_first_thread ())
  {
    int a = 1, b = 1, c = 1;
    tandis que (pow (a, n) + pow (b, n)! = pow (c, n))
    {
      si (b> a) a ++; sinon si (c> b) {a = 1; b ++}; sinon {a = 1; b = 1; c ++};
    }
    signal_other_thread_and_die ();
  }
  else // Deuxième fil
  {
    printf ("Le résultat est");
    wait_for_other_thread ();
  }
  printf ("% d /% d /% d", a, b, c);
}

Généralement pas déraisonnable, bien que je puisse craindre que:

  total int = 0;
  pour (i = 0; num_reps> i; i ++)
  {
    update_progress_bar (i);
    total + = do_something_slow_with_no_side_effects (i);
  }
  show_result (total);

deviendrait

  total int = 0;
  si (fork_is_first_thread ())
  {
    pour (i = 0; num_reps> i; i ++)
      total + = do_something_slow_with_no_side_effects (i);
    signal_other_thread_and_die ();
  }
  autre
  {
    pour (i = 0; num_reps> i; i ++)
      update_progress_bar (i);
    wait_for_other_thread ();
  }
  show_result (total);

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.

supercat
la source
Je pense que votre cas de barre de progression n'a pas pu être séparé, car l'affichage d'une barre de progression est un appel d'E / S de bibliothèque. Les optimisations ne doivent pas modifier le comportement visible de cette manière.
Philip Potter
@Philip Potter: Si la routine lente avait des effets secondaires, ce serait certainement vrai. Dans mon exemple précédent, cela n'aurait aucun sens si ce n'était pas le cas, alors je l'ai changé. Mon interprétation de la spécification est que le système est autorisé à différer l'exécution du code lent jusqu'à ce que ses effets (autres que le temps qu'il faut pour s'exécuter) deviennent visibles, c'est-à-dire l'appel show_result (). Si le code à barres de progression utilisait le total courant, ou du moins prétendait le faire, cela le forcerait à se synchroniser avec le code lent.
supercat du
1
Cela explique toutes ces barres de progression qui vont vite de 0 à 100 puis
restent
0

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.

Albert
la source
1
Exemple où vous pourriez vouloir une boucle infinie: un système embarqué où vous ne voulez pas dormir pour des raisons de performances et tout le code est suspendu à une interruption ou deux?
JCx
@JCx en Standard C, les interruptions doivent définir un drapeau que la boucle principale vérifie, donc la boucle principale aurait un comportement observable dans le cas où les drapeaux sont définis. L'exécution de code substantiel dans les interruptions n'est pas portable.
MM
-1

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.

spraff
la source
S'ils interagissent avec d'autres threads, vous ne devriez pas les rendre volatils, les rendre atomiques ou les protéger avec un verrou.
BCoates
1
Thjs est un conseil terrible. Les fabriquer volatilen'est ni nécessaire ni suffisant, et cela nuit considérablement aux performances.
David Schwartz