web-dev-qa-db-fra.com

Boucle sans fin en C / C ++

Il y a plusieurs possibilités pour faire une boucle sans fin, en voici quelques unes que je choisirais:

  • for(;;) {}
  • while(1) {}/while(true) {}
  • do {} while(1)/do {} while(true)

Y a-t-il une certaine forme que l'on devrait choisir? Et les compilateurs modernes font-ils une différence entre le dernier et le dernier énoncé ou s’aperçoivent-ils qu’il s’agit d’une boucle sans fin et omettent totalement la vérification?

Edit: comme cela a été mentionné, j'ai oublié goto, mais cela a été fait pour la raison que je ne l'aime pas du tout comme commande.

Edit2: J'ai créé des grep sur les dernières versions de kernel.org. Il semble que rien n’a beaucoup changé au fil du temps (du moins dans le noyau) enter image description here

56
magu_

Le problème avec poser cette question est que vous obtiendrez tellement de réponses subjectives qui déclarent simplement "je préfère ceci ...". Au lieu de faire de telles déclarations inutiles, je vais essayer de répondre à cette question par des faits et des références, plutôt que par des opinions personnelles.

Par expérience, nous pouvons probablement commencer par exclure les alternatives à faire (et le goto), car elles ne sont pas couramment utilisées. Je ne me souviens pas de les avoir jamais vues dans un code de production réel, écrit par des professionnels.

while(1), while(true) et for(;;) sont les 3 versions différentes existant couramment dans le code réel. Ils sont bien entendu totalement équivalents et donnent le même code machine.


for(;;)

  • C'est l'exemple canonique original d'une boucle éternelle. Dans l'ancienne Bible en C du langage de programmation C de Kernighan et Ritchie, on peut lire que:

    K & R 2nd ed 3.5:

    for (;;) {
    ...
    }
    

    est une boucle "infinie", probablement cassée par d'autres moyens, comme une rupture ou un retour. Utiliser pendant ou pour est en grande partie une question de préférence personnelle.

    Pendant longtemps (mais pas pour toujours), ce livre a été considéré comme Canon et la définition même du langage C. Puisque K & R a décidé de montrer un exemple de for(;;), cela aurait été considéré comme la forme la plus correcte au moins jusqu’à la normalisation du C en 1990.

    Cependant, K & R eux-mêmes ont déjà déclaré qu'il s'agissait d'une question de préférence.

    Et aujourd'hui, K & R est une source très discutable à utiliser comme référence C canonique. Non seulement il est obsolète plusieurs fois (sans aborder C99 ni C11), il prêche également des pratiques de programmation souvent considérées comme mauvaises ou manifestement dangereuses dans la programmation en C moderne.

    Mais malgré le fait que K & R soit une source douteuse, cet aspect historique semble être l'argument le plus puissant en faveur de for(;;).

  • L'argument contre la boucle for(;;) est qu'il est quelque peu obscur et illisible. Pour comprendre ce que fait le code, vous devez connaître la règle suivante de la norme:

    ISO 9899: 2011 6.8.5.3:

    for ( clause-1 ; expression-2 ; expression-3 ) statement
    

    / - /

    Les clauses-1 et expression-3 peuvent être omises. Une expression omise-2 est remplacée par une constante non nulle.

    Sur la base de ce texte de la norme, je pense que la plupart des gens conviendront que c’est non seulement obscur, mais aussi subtile, puisque la première et la troisième partie de la boucle for sont traitées différemment de la deuxième, quand elles sont omises.


while(1)

  • C'est censé être une forme plus lisible que for(;;). Cependant, elle repose sur une autre règle obscure, bien que bien connue, à savoir que C traite toutes les expressions non nulles comme des booléens logiques vrais. Tous les programmeurs en C sont conscients de cela, ce n’est donc probablement pas un gros problème.

  • Il y a un gros problème pratique avec ce formulaire, à savoir que les compilateurs ont tendance à lui donner un avertissement: "la condition est toujours vraie" ou similaire. C'est un bon avertissement, d'un type que vous ne voulez vraiment pas désactiver, car il est utile pour rechercher divers bogues. Par exemple, un bogue tel que while(i = 1), alors que le programmeur avait l'intention d'écrire while(i == 1).

    En outre, les analyseurs de codes statiques externes sont susceptibles de se plaindre de "la condition est toujours vraie".


while(true)

  • Pour rendre while(1) encore plus lisible, certains utilisent plutôt while(true). Les programmeurs semblent s'entendre pour dire que c'est la forme la plus lisible.

  • Cependant, cette forme a le même problème que while(1), comme décrit ci-dessus: avertissements "la condition est toujours vraie".

  • En ce qui concerne C, cette forme présente un autre inconvénient, à savoir qu’elle utilise la macro true de stdbool.h. Donc, pour faire cette compilation, nous devons inclure un fichier d’en-tête, ce qui peut être gênant ou non. En C++, ce n'est pas un problème, car bool existe en tant que type de données primitif et true est un mot clé de langage.

  • Un autre inconvénient de cette forme est qu’elle utilise le type bool C99, qui n’est disponible que sur les compilateurs modernes et non compatible avec les versions antérieures. Encore une fois, ceci n’est qu’un problème en C et pas en C++.


Alors quelle forme utiliser? Ni semble parfait. Comme K & R l’a déjà dit à l’époque sombre, c’est une question de préférence personnelle.

Personnellement, j'utilise toujours for(;;) uniquement pour éviter les avertissements du compilateur/analyseur fréquemment générés par les autres formulaires. Mais peut-être plus important à cause de cela:

Si même un débutant en C sait que for(;;) signifie une boucle éternelle, alors qui essayez-vous de rendre le code plus lisible pour?

Je suppose que c'est ce que tout se résume vraiment. Si vous essayez de rendre votre code source lisible pour les non-programmeurs, qui ne connaissent même pas les parties fondamentales du langage de programmation, vous ne faites que perdre du temps. Ils ne devraient pas être en train de lire votre code.

Et puisque tout le monde qui devrait lire votre code sait déjà ce que for(;;) signifie, rien ne sert de le rendre plus lisible - c'est déjà aussi lisible que possible.

84
Lundin

C'est très subjectif. J'écris ceci:

while(true) {} //in C++

Parce que son intention est très claire et qu'il est également lisible: vous le regardez et vous savez la boucle infinie est destinée.

On pourrait dire que for(;;) est également clair. Mais je dirais qu'en raison de sa syntaxe alambiquée, cette option nécessite des connaissances supplémentaires pour parvenir à la conclusion qu'il s'agit d'une boucle infinie, d'où elle est relativement moins clair. Je dirais même qu'il y a plus nombre de programmeurs qui ne savent pas ce que fait for(;;) (même s'ils connaissent la boucle for habituelle), mais presque tous les programmeurs qui connaissent la boucle while sauraient immédiatement ce que fait while(true).

Pour moi, écrire for(;;) signifiant boucle infinie, revient à écrire while() signifiant boucle infinie - alors que le premier fonctionne, le dernier ne fonctionne PAS. Dans le premier cas, la condition vide s'avère être true implicitement, mais dans le dernier cas, c'est une erreur! Je personnellement n'a pas aimé.

Maintenant, while(1) est également présent dans la compétition. Je voudrais demander: pourquoi while(1)? Pourquoi pas while(2), while(3) ou while(0.1)? Quoi que vous écriviez, vous voulez dire en réalitéwhile(true) - si oui, pourquoi ne pas l'écrire à la place?

En C (si jamais j'écris), j'écrirais probablement ceci:

while(1) {} //in C

Alors que while(2), while(3) et while(0.1) auraient également un sens. Mais juste pour me conformer aux autres programmeurs C, j’écrirais while(1), car beaucoup de programmeurs C l’écrivent et je ne trouve aucune raison de dévier de la norme.

32
Nawaz

Dans un ultime acte d’ennui, j’ai écrit quelques versions de ces boucles et l’ai compilé avec GCC sur mon mac mini.

while(1){} et for(;;) {} ont produit les mêmes résultats d'assemblage, tandis que do{} while(1); a généré un code d'assemblage similaire mais différent.

heres celui pour la boucle while/for

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    movl    $0, -4(%rbp)
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
    jmp LBB0_1
    .cfi_endproc

et la boucle tout en

        .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    movl    $0, -4(%rbp)
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
    jmp LBB0_2
LBB0_2:                                 ##   in Loop: Header=BB0_1 Depth=1
    movb    $1, %al
    testb   $1, %al
    jne LBB0_1
    jmp LBB0_3
LBB0_3:
    movl    $0, %eax
    popq    %rbp
    ret
    .cfi_endproc
29
Pita

Tout le monde semble aimer while (true):

https://stackoverflow.com/a/224142/1508519

https://stackoverflow.com/a/1401169/1508519

https://stackoverflow.com/a/1401165/1508519

https://stackoverflow.com/a/1401164/1508519

https://stackoverflow.com/a/1401176/1508519

Selon SLaks , ils compilent à l'identique.

Ben Zotto dit aussi que ce n'est pas grave :

Ce n'est pas plus rapide Si cela vous intéresse, compilez avec la sortie de l'assembleur pour votre plate-forme et regardez pour voir. Ça n'a pas d'importance. Cela n'a jamais d'importance. Ecrivez vos boucles infinies comme bon vous semble.

En réponse à user1216838, voici ma tentative de reproduire ses résultats.

Voici ma machine:

cat /etc/*-release
CentOS release 6.4 (Final)

version gcc:

Target: x86_64-unknown-linux-gnu
Thread model: posix
gcc version 4.8.2 (GCC)

Et les fichiers de test:

// testing.cpp
#include <iostream>

int main() {
    do { break; } while(1);
}

// testing2.cpp
#include <iostream>

int main() {
    while(1) { break; }
}

// testing3.cpp
#include <iostream>

int main() {
    while(true) { break; }
}

Les commandes:

gcc -S -o test1.asm testing.cpp
gcc -S -o test2.asm testing2.cpp
gcc -S -o test3.asm testing3.cpp

cmp test1.asm test2.asm

La seule différence est la première ligne, alias le nom de fichier.

test1.asm test2.asm differ: byte 16, line 1

Sortie:

        .file   "testing2.cpp"
        .local  _ZStL8__ioinit
        .comm   _ZStL8__ioinit,1,1
        .text
        .globl  main
        .type   main, @function
main:
.LFB969:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        nop
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE969:
        .size   main, .-main
        .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB970:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        cmpl    $1, -4(%rbp)
        jne     .L3
        cmpl    $65535, -8(%rbp)
        jne     .L3
        movl    $_ZStL8__ioinit, %edi
        call    _ZNSt8ios_base4InitC1Ev
        movl    $__dso_handle, %edx
        movl    $_ZStL8__ioinit, %esi
        movl    $_ZNSt8ios_base4InitD1Ev, %edi
        call    __cxa_atexit
.L3:
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE970:
        .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
        .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB971:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $65535, %esi
        movl    $1, %edi
        call    _Z41__static_initialization_and_destruction_0ii
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE971:
        .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
        .section        .ctors,"aw",@progbits
        .align 8
        .quad   _GLOBAL__sub_I_main
        .hidden __dso_handle
        .ident  "GCC: (GNU) 4.8.2"
        .section        .note.GNU-stack,"",@progbits

Avec -O3, le rendement est bien entendu beaucoup plus petit, mais toujours pas de différence.

8
user1508519

L'idiome conçu dans le langage C (et hérité dans C++) pour une boucle infinie est for(;;): l'omission d'un formulaire de test. Les boucles do/while Et while n'ont pas cette particularité; leurs expressions de test sont obligatoires.

for(;;) n'exprime pas "boucle tant que certaines conditions sont vraies et qu'il est toujours vrai". Il exprime "boucle sans fin". Aucune condition superflue n'est présente.

Par conséquent, la construction for(;;) est la boucle sans fin canonique. C'est un fait.

Tout ce qui reste à l’opinion est d’écrire ou non la boucle sans fin canonique, ou de choisir quelque chose de baroque impliquant des identifiants et des constantes supplémentaires, pour créer une expression superflue.

Même si l'expression test de while était optionnelle, ce qui n'est pas le cas, while(); serait étrange. while quoi? En revanche, la réponse à la question for quoi? est: pourquoi, toujours --- pour toujours! Pour plaisanter, certains programmeurs d’aujourd’hui ont défini des macros vierges, de sorte qu’ils puissent écrire for(ev;e;r);.

while(true) est supérieur à while(1) parce qu'au moins il ne s'agit pas du kludge que 1 représente la vérité. Cependant, while(true) n'est entré dans C que C99. for(;;) existe dans toutes les versions de C, remontant au langage décrit dans le livre K & R1 de 1978, et dans tous les dialectes du C++, et même dans les langages apparentés. Si vous codez dans une base de code écrite en C90, vous devez définir votre propre true pour while (true).

while(true) lit mal. Alors que quoi est vrai? Nous ne voulons pas vraiment voir l'identifiant true dans le code, sauf lorsque nous initialisons ou affectons des variables booléennes. true n'a pas besoin d'apparaître dans les tests conditionnels. Un bon style de codage évite les erreurs comme ceci:

if (condition == true) ...

en faveur de:

if (condition) ...

Pour cette raison, while (0 == 0) est supérieure à while (true): elle utilise une condition réelle qui teste quelque chose, ce qui se traduit par une phrase: "la boucle alors que zéro est égale à zéro". Nous avons besoin d'un prédicat pour aller bien avec "while"; le mot "vrai" n'est pas un prédicat, mais l'opérateur relationnel == l'est.

8
Kaz

J'utilise for(;/*ever*/;).

Il est facile à lire et il faut un peu plus de temps pour taper (en raison des décalages pour les astérisques), ce qui indique que je devrais être très prudent lorsque vous utilisez ce type de boucle. Le texte vert qui apparaît dans le conditionnel est également une vision assez étrange - une autre indication de cette construction est mal vu sauf si c'est absolument nécessaire.

4
Aerom Xundes

Ils compilent probablement à peu près le même code machine, donc c'est une question de goût.

Personnellement, je choisirais celui qui est le plus clair (c’est-à-dire très clair qu’il est supposé être une boucle infinie).

Je me pencherais vers while(true){}.

4
Chris Chambers

Y a-t-il une certaine forme que l'on devrait choisir?

Vous pouvez choisir soit. C'est une question de choix. Tous sont équivalents. while(1) {}/while(true){} est fréquemment utilisé pour la boucle infinie par les programmeurs.

3
haccks

Eh bien, il y a beaucoup de goût dans celui-ci. Je pense que les gens de C sont plus susceptibles de préférer (;;), qui se lit comme "éternellement". Si c'est pour le travail, faites ce que font les locaux, si c'est pour vous-même, faites celui que vous pouvez le plus facilement lire.

Mais dans mon expérience, fais {} while (1); est presque jamais utilisé.

2
RichardPlunkett

Je recommanderais while (1) { } ou while (true) { }. C'est ce que la plupart des programmeurs écriraient, et pour des raisons de lisibilité, vous devriez suivre les idiomes courants.

(Ok, il y a donc une "citation" évidente pour l'affirmation à propos de la plupart des programmeurs. Mais d'après le code que j'ai vu, en C depuis 1984, je crois que c'est vrai.)

Tout compilateur raisonnable les compilerait tous dans le même code, avec un saut inconditionnel, mais je ne serais pas surpris s'il existe des compilateurs déraisonnables, pour systèmes intégrés ou autres systèmes spécialisés.

2