web-dev-qa-db-ja.com

C / C ++の無限ループ

無限ループを行うにはいくつかの可能性がありますが、ここでいくつか選択します:

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

選択すべき特定の形式はありますか?そして、最新のコンパイラーは中間と最後のステートメントの違いを生みますか、それは無限ループであり、チェック部分を完全にスキップすることを認識していますか?

編集:言及されているように、gotoを忘れましたが、これはコマンドとしてそれがまったく好きではないという理由で行われました。

Edit2:kernel.orgから取得した最新バージョンでgrepを作成しました。私は時間の経過とともに(少なくともカーネル内で)あまり変化していないようです enter image description here

56
magu_

この質問をすることの問題は、単に「私はこれを好む...」と述べるだけの非常に多くの主観的な答えを得るということです。このような無意味な発言をする代わりに、個人的な意見ではなく、事実と参考文献でこの質問に答えようとします。

経験を通じて、do-whileの代替(およびgoto)は一般的に使用されないため、除外することから始めることができます。専門家によって書かれたライブのプロダクションコードでそれらを見たことはありません。

while(1)while(true)、およびfor(;;)は、実際のコードに一般的に存在する3つの異なるバージョンです。もちろん、これらは完全に同等であり、同じマシンコードになります。


for(;;)

  • これは、永遠のループの元の標準的な例です。 KernighanとRitchieによる古代のC聖書The C Programming Languageでは、次のように読むことができます。

    K&R 2nd ed 3.5:

    for (;;) {
    ...
    }
    

    おそらく、ブレークやリターンなどの他の手段によってブレークされる「無限」ループです。 whileとforのどちらを使用するかは、主に個人の好みの問題です。

    長い間(ただし、永遠ではありませんが)、この本はCanonとC言語のまさに定義と見なされていました。 K&Rはfor(;;)の例を示すことを決定したため、少なくとも1990年のC標準化まではこれが最も正しい形式と見なされていました。

    しかし、K&R自身はすでにそれが好みの問題だと述べています。

    そして今日、K&Rは標準Cリファレンスとして使用する非常に疑わしいソースです。 C99やC11に対応していないため、何度も時代遅れになっているだけでなく、現代のCプログラミングでしばしば悪いまたは露骨に危険と見なされているプログラミング手法も説いています。

    しかし、K&Rは疑わしいソースであるにもかかわらず、この歴史的側面はfor(;;)を支持する最も強力な議論のようです。

  • for(;;)ループに対する議論は、それがいくぶんあいまいで読みにくいということです。コードの機能を理解するには、標準から次のルールを知っている必要があります。

    ISO 9899:2011 6.8.5.3:

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

    /-/

    第1節と第3節の両方を省略できます。省略されたexpression-2は、ゼロ以外の定数に置き換えられます。

    標準のこのテキストに基づいて、forループの1番目と3番目の部分が省略された場合、2番目とは異なる方法で処理されるため、ほとんどの人がそれが不明瞭であるだけでなく微妙であることに同意すると思います。


while(1)

  • これは、おそらくfor(;;)よりも読みやすい形式です。ただし、よく知られているルールですが、Cはすべての非ゼロ式をブール論理真として処理するという、別のあいまいさに依存しています。すべてのCプログラマーはそれを認識しているので、大きな問題ではないでしょう。

  • この形式には、実用的で大きな問題が1つあります。つまり、コンパイラーは「条件が常に真である」などの警告を出す傾向があります。これは、さまざまなバグを見つけるのに役立つため、本当に無効にしたくない種類の警告です。たとえば、プログラマがwhile(i = 1)を作成しようとしたときのwhile(i == 1)などのバグ。

    また、外部の静的コードアナライザーは、「条件が常に真である」ことを訴えがちです。


while(true)

  • while(1)をさらに読みやすくするために、代わりにwhile(true)を使用する人もいます。プログラマーの間のコンセンサスは、これが最も読みやすい形式であるようです。

  • ただし、上記のように、このフォームにはwhile(1)と同じ問題があります。「条件は常にtrue」という警告です。

  • Cに関しては、この形式には別の欠点があります。つまり、stdbool.hのマクロtrueを使用するということです。したがって、これをコンパイルするには、不便な場合とそうでない場合があるヘッダーファイルを含める必要があります。 C++では、boolはプリミティブデータ型として存在し、trueは言語キーワードであるため、これは問題ではありません。

  • この形式のさらに別の欠点は、C99 bool型を使用することです。これは、最新のコンパイラでのみ使用可能であり、後方互換性はありません。繰り返しますが、これはCの問題であり、C++の問題ではありません。


使用するフォームは?どちらも完璧ではないようです。 K&Rが暗黒時代にすでに言ったように、それは個人的な好みの問題です。

個人的には、他のフォームによって頻繁に生成されるコンパイラ/アナライザの警告を避けるために、常にfor(;;)を使用しています。しかし、おそらくもっと重要なのは、このためです:

C初心者でもfor(;;)が永遠のループを意味することを知っている場合、コードをより読みやすくしようとしているのは誰ですか? =

私はそれがすべてが本当に要約するものだと思います。プログラミング言語の基本的な部分すら知らない非プログラマー向けにソースコードを読みやすくしようとしていることに気付いた場合は、時間の無駄です。彼らはあなたのコードを読んではいけません。

そして、shouldを読むべきだれもがfor(;;)が何を意味するかを既に知っているので、それをさらに読みやすくする意味はありません-それはすでに読みやすいです取得。

84
Lundin

それは非常に主観的です。私はこれを書きます:

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

その意図は非常に明確であるであり、また読み取り可能でもあるため、あなたはそれを見てknow無限ループが意図されているからです。

for(;;)も明確であると言うかもしれません。しかし、複雑な構文のため、このオプションrequiresは無限ループであるという結論に達するための追加の知識であるため、比較的少ないクリア。 morefor(;;)が何をするのかわからないプログラマーがいるとさえ言うでしょう(通常のforループを知っていても)、しかしほぼwhileループを知っているすべてのプログラマは、while(true)が何をするかをすぐに理解します。

私にとって、for(;;)と無限ループを意味することは、while()と無限ループを意味することに似ています。前者は機能しますが、後者は機能しません。前者の場合、empty conditionは暗黙的にtrueになりますが、後者の場合はエラーです!私個人的には気に入らなかった。

現在、while(1)も競争に参加しています。私は尋ねるだろう:なぜwhile(1)while(2)while(3)、またはwhile(0.1)を選択しないのはなぜですか?さて、あなたが書くものは何でも実際に意味するwhile(true) —もしそうなら、代わりにそれを書いてみませんか?

Cで(もし私が書いたなら)、私はおそらくこれを書くでしょう:

while(1) {} //in C

while(2)while(3)、およびwhile(0.1)も同様に意味があります。しかし、他のCプログラマーに準拠するために、私はwhile(1)と書きます。これは、多くのCプログラマーがこれを書いており、標準から逸脱する理由がないからです。

32
Nawaz

究極の退屈行為で、これらのループのいくつかのバージョンを実際に作成し、Mac miniのGCCでコンパイルしました。

while(1){}for(;;) {}は同じアセンブリ結果を生成し、do{} while(1);は同様のアセンブリコードを生成しました

while/forループ用のheres

    .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

そして、do whileループ

        .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

誰もが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

SLaks によると、同じようにコンパイルされます。

Ben Zottoはそれは問題ではないと言っています

速くはありません。本当に気になる場合は、ご使​​用のプラットフォーム用のアセンブラー出力でコンパイルして確認してください。関係ありません。これは重要ではありません。無限ループを好きなように書いてください。

User1216838への応答として、彼の結果を再現しようとしています。

これが私のマシンです。

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

gccバージョン:

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

そしてテストファイル:

// 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; }
}

コマンド:

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

唯一の違いは、最初の行、つまりファイル名です。

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

出力:

        .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

-O3の場合、出力はもちろんかなり小さくなりますが、それでも違いはありません。

8
user1508519

無限ループのためにC言語に設計された(そしてC++に継承された)イディオムはfor(;;):テストフォームの省略です。 do/whileおよびwhileループには、この特別な機能はありません。テスト式は必須です。

for(;;)は、「たまたま常に真になる条件が真である間のループ」を表現しません。 「無限ループ」を表現しています。余分な状態はありません。

したがって、for(;;)コンストラクトは標準無限ループです。これは事実です。

必要なのは、正規の無限ループを記述するか、余分な識別子と定数を含むバロック式を選択して、余分な表現を作成するかどうかだけです。

whileのテスト式がオプションであったとしても、それはそうではありませんが、while();は奇妙です。 while何?対照的に、質問forの答えは何ですか? is:なぜ、永遠に---永遠に!冗談として、昔のプログラマーは空のマクロを定義していたので、for(ev;e;r);を書くことができました。

while(true)while(1)よりも優れています。少なくとも1が真理を表すクラッジを含まないためです。ただし、while(true)はC99までCに入りませんでした。 for(;;)は、1978年の本K&R1で説明されている言語にまで遡るCのすべてのバージョン、C++のすべての方言、さらには関連言語に存在します。 C90で記述されたコードベースでコーディングしている場合、独自のtruewhile (true)に定義する必要があります。

while(true)の読み取りが不適切です。 whatは真ですか?ブール変数を初期化または割り当てている場合を除いて、コードで識別子trueを実際に見たくありません。 trueは条件テストに現れる必要はありません。優れたコーディングスタイルは、次のような問題を回避します。

if (condition == true) ...

賛成で:

if (condition) ...

このため、while (0 == 0)while (true)よりも優れています。実際の条件を使用して何かをテストし、「ゼロがゼロに等しい間にループする」という文になります。 「while」をうまく使用するには、述語が必要です。単語「true」は述語ではありませんが、関係演算子==は述語です。

8
Kaz

for(;/*ever*/;)を使用します。

読みやすく、入力に少し時間がかかります(アスタリスクのシフトのため)。このタイプのループを使用するときは注意が必要です。条件に表示される緑色のテキストもかなり奇妙な光景です。絶対に必要な場合を除き、このコンストラクトは眉をひそめています。

4
Aerom Xundes

それらはおそらくほぼ同じマシンコードにコンパイルされるので、それは好みの問題です。

個人的には、最も明確なものを選択します(つまり、無限ループであることが非常に明確です)。

私はwhile(true){}に傾くでしょう。

4
Chris Chambers

選択すべき特定の形式はありますか?

どちらかを選択できます。選択の問題。すべて同等です。 while(1) {}/while(true){}は、プログラマが無限ループに頻繁に使用します。

3
haccks

まあ、これにはたくさんの味があります。 Cのバックグラウンドを持つ人は、for(;;)を好む可能性が高いと思います。それが仕事の場合は、地元の人々が行うことを行い、自分の場合は、最も簡単に読むことができるものを行います。

しかし、私の経験では、{} while(1);使用されることはほとんどありません。

2
RichardPlunkett

while (1) { }またはwhile (true) { }をお勧めします。それはほとんどのプログラマーが書くものであり、読みやすさの理由から、一般的なイディオムに従うべきです。

(わかりました。だから、ほとんどのプログラマーについての主張には明らかな「引用が必要」です。しかし、1984年以来Cで見たコードから、それは真実だと思います。)

合理的なコンパイラはすべて、それらをすべて無条件ジャンプで同じコードにコンパイルしますが、組み込みシステムやその他の特殊なシステム用にnreasonableコンパイラがいくつかあっても驚かないでしょう。