55

私がCを学んだとき、先生は一日中私に言った:「後藤を使わないでください、それは悪い習慣です、それは醜いです、それは危険です!」等々。

それでは、なぜ一部のカーネルプログラマーはgoto、たとえばこの関数で、単純なものに置き換えることができるのでしょうか。

while(condition) {} 

また

do {} while(condition);

理解できません。while/ do-の代わりにgotoを使用する方が良い場合がありますwhileか?もしそうなら、なぜですか?

4

3 に答える 3

61

歴史的背景:ダイクストラは、構造化プログラミング(、、、など)の代わりに多くのプログラマーが使用した1968年にGotoThoughdHarmfulを書いたことを覚えておく必要があります。 gotoifwhilefor

それは44年後のことであり、このような使用法が実際に見られることはめったにありませんgoto。構造化プログラミングは、ずっと前にすでに勝っています。

ケース分析:

サンプルコードは次のようになります。

    SETUP...
again:
    COMPUTE SOME VALUES...
    if (cmpxchg64(ptr, old_val, val) != old_val)
        goto again;

構造化バージョンは次のようになります。

SETUP...
do {
    COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);

構造化されたバージョンを見ると、すぐに「ループだ」と思います。バージョンを見るとgoto、最後に「再試行」のケースが付いた直線だと思います。

このgotoバージョンには両方がSETUPありCOMPUTE SOME VALUES、同じ列にあります。これは、ほとんどの場合、制御フローが両方を通過することを強調しています。構造化バージョンはSETUPCOMPUTE SOME VALUES異なる列に配置します。これは、制御が異なる列を通過する可能性があることを強調しています。

ここでの問題は、コードにどのような重点を置きたいかということです。gotoこれをエラー処理と比較できます。

構造化バージョン:

if (do_something() != ERR) {
    if (do_something2() != ERR) {
        if (do_something3() != ERR) {
            if (do_something4() != ERR) {
                ...

後藤バージョン:

if (do_something() == ERR)  // Straight line
    goto error;             // |
if (do_something2() == ERR) // |
    goto error;             // |
if (do_something3() == ERR) // |
    goto error;             // V
if (do_something4() == ERR) // emphasizes normal control flow
    goto error;

生成されるコードは基本的に同じであるため、インデントのような活版印刷の問題と考えることができます。

于 2012-10-21T19:26:47.383 に答える
31

この例の場合、元々SMPセーフではない方法で記述されたコードにSMPサポートを後付けすることであったと思われます。パスの追加goto again;は、関数の再構築よりもはるかに簡単で侵襲性が低くなります。

私はこのスタイルがあまり好きだとは言えませんがgoto、イデオロギー的な理由から避けるのは間違っていると思います。使用法の1つの特殊なケースgoto(この例とは異なります)はgoto、関数内で前進するためにのみ使用され、後退することはない場合です。このクラスの使用法では、から生じるループ構造が発生することはありませんgoto。ほとんどの場合、必要な動作(通常はクリーンアップしてエラーが返される)を実装するための最も簡単で明確な方法です。

于 2012-10-21T19:02:50.773 に答える
1

非常に良い質問です。決定的な答えを提供できるのは著者だけだと思います。@Izkataによって説明されているように、エラー処理に使用することから始めた可能性があると言って、少し推測を追加します。その後、ゲートは基本的なループにも使用できるようになりました。

私の意見では、エラー処理の使用法はシステムプログラミングでは正当なものです。関数は、進行するにつれてメモリを徐々に割り当てます。エラーが発生した場合はgoto、適切なラベルを付けて、その時点から逆の順序でリソースを解放します。

したがって、最初の割り当て後にエラーが発生した場合は、最後のエラーラベルにジャンプして、1つのリソースのみを解放します。同様に、最後の割り当て後にエラーが発生した場合は、最初のエラーラベルにジャンプしてそこから実行され、関数が終了するまですべてのリソースが解放されます。エラー処理のこのようなパターンは、特にコードを変更する場合は注意深く使用する必要があり、valgrindおよび単体テストを強くお勧めします。しかし、他のアプローチよりも間違いなく読みやすく、保守しやすいです。

使用の黄金のルールの1つgotoは、いわゆるスパゲッティコードを回避することです。goto各ステートメントとそれぞれのラベルの間に線を引いてみてください。線が交差している場合は、線を交差しています:)。このような使用法はgoto、フロー制御に依存しているBASICのような言語で見られるため、読みにくく、追跡が難しいバグの一般的な原因です。

単純なループを1つだけ実行すると、線が交差することはないため、読み取りと保守が可能であり、主にスタイルの問題になります。とは言うものの、質問で示したように、言語で提供されるループキーワードを使用して簡単に実行できるため、gotoforループの使用を避けることをお勧めします。これはfor、、do/whileまたはwhile構造が設計上よりエレガントであるためです。

于 2019-06-26T11:05:54.837 に答える