私は長い間goto
、可能であれば決して使用されるべきではないという印象を受けてきました。
しかし、先日、libavcodec(Cで書かれている)を熟読していると、何度も使用されていることに驚きました。
goto
ループと関数をサポートする言語で使用することは、これまでに有利ですか?もしそうなら、なぜですか?の使用を明確に正当化する具体的な例を提供してくださいgoto
。
私は長い間goto
、可能であれば決して使用されるべきではないという印象を受けてきました。
しかし、先日、libavcodec(Cで書かれている)を熟読していると、何度も使用されていることに驚きました。
goto
ループと関数をサポートする言語で使用することは、これまでに有利ですか?もしそうなら、なぜですか?の使用を明確に正当化する具体的な例を提供してくださいgoto
。
goto
Edsger Dijkstra のGoTo が有害と見なす記事を直接的または間接的に反論する人は誰でも、彼らの立場を立証するために引用します。残念なことに、Dijkstra の記事は、最近のステートメントの使用方法とはほとんど関係がないため、この記事の内容は、現代のプログラミング シーンにはほとんどまたはまったく適用できません。より少ないミームは、高位から口述されたその経典、その大祭司、および認識された異端者の回避(またはさらに悪い)に至るまで、今や宗教の危機に瀕していますgoto
。goto
Dijkstra の論文を文脈に当てはめて、この主題に少し光を当てましょう。
Dijkstra が論文を書いた当時、人気のある言語は、BASIC、FORTRAN (初期の方言)、およびさまざまなアセンブリ言語などの構造化されていない手続き型言語でした。高水準言語を使用している人々が、「スパゲッティ コード」という用語を生み出したねじれたねじれた実行スレッドでコード ベース全体をジャンプすることは非常に一般的でした。これは、Mike Mayfield によって書かれた古典的な Trek ゲームに飛び乗って、物事がどのように機能するかを理解しようとすることで確認できます。少し時間をかけて、それを見てください。
これは、ダイクストラが 1968 年の論文で非難していた「go to ステートメントの無制限の使用」 です。コード内の好きな場所にいつでもジャンプできる機能は、彼が批判し、停止するよう求めていたものでした。goto
それをC やその他のより現代的な言語の貧弱な力と比較することは、単純に危険です。
異端者に立ち向かうカルト信者たちの高らかな詠唱がすでに聞こえてきます。goto
「しかし、 C ではコードを非常に読みにくくすることができます」と唱えるでしょう。そうそう?コードを非常に読みにくくすることもできgoto
ます。このように:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
見えないgoto
ので、読みやすいはずですよね?または、これはどうですか:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
そこにもありませんgoto
。したがって、読み取り可能でなければなりません。
これらの例のポイントは何ですか? 読めない、保守できないコードを作るのは言語機能ではありません。それを行うのは構文ではありません。これを引き起こすのは悪いプログラマーです。そして、上記の項目でわかるように、下手なプログラマーは、言語機能を読み取れず、使用できなくする可能性があります。そこのfor
ループのように。(見えますよね?)
公平を期すために、一部の言語構造は他の構造よりも悪用されやすいです。しかし、あなたが C プログラマーなら、 !#define
に対する十字軍に行くずっと前に、goto
!
というわけで、わざわざここまで読んでくれた人のために、注意すべき重要な点がいくつかあります。
goto
ステートメントに関するDijkstra の論文
は、アセンブラーではないほとんどの最新の言語よりも潜在的に有害なプログラミング環境向けに書かgoto
れています。goto
ことは、「一度楽しもうとしたが、気に入らなかったので、今は反対している」と言うのと同じくらい合理的です。goto
他の構造体で適切に置き換えることができない、コード内の最新の (貧弱な) ステートメントの正当な使用法があります。godo
常に false のdo
ループが発生するという嫌悪感のような、最新の制御ステートメントの不正な使用も存在します。これらは、 の賢明な使用よりも悪いことがよくあります。break
goto
goto
私が知っている「goto」ステートメントを使用する理由はいくつかあります(すでにこれについて話している人もいます)。
関数をきれいに終了する
多くの場合、関数では、リソースを割り当てて、複数の場所で終了する必要がある場合があります。プログラマーは、関数の最後にリソースクリーンアップコードを配置することでコードを簡略化でき、関数のすべての「出口点」はクリーンアップラベルに移動します。このように、関数のすべての「出口点」でクリーンアップコードを記述する必要はありません。
ネストされたループを終了する
ネストされたループにいて、すべてのループから抜け出す必要がある場合、gotoを使用すると、breakステートメントやif-checkよりもはるかにクリーンでシンプルになります。
低レベルのパフォーマンスの向上
これはパフォーマンスクリティカルなコードでのみ有効ですが、gotoステートメントは非常に高速に実行され、関数内を移動するときにブーストを与えることができます。ただし、コンパイラは通常、gotoを含むコードを最適化できないため、これは両刃の剣です。
これらすべての例で、gotoは単一の関数のスコープに制限されていることに注意してください。
盲目的にベスト プラクティスに従うことは、ベスト プラクティスではありません。フロー制御の主要な形式としてステートメントを回避するという考えは、goto
判読不能なスパゲッティ コードの生成を回避することです。適切な場所で控えめに使用すれば、アイデアを表現する最も単純で明確な方法になる場合があります。Zortech C++ コンパイラと D プログラミング言語の作成者である Walter Bright は、それらを頻繁に、しかし慎重に使用しています。ステートメントがあってもgoto
、彼のコードは完全に読めます。
結論: 避けるgoto
ための回避goto
は無意味です。本当に避けたいのは、読めないコードを生成することです。goto
ロードされたコードが読み取り可能であれば、何も問題はありません。
ええと、常により悪いことが 1 つありgoto's
ます。goto を回避するための他のプログラムフロー演算子の奇妙な使用:
例:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
goto
プログラムフローについての推論を難しくするので1 (別名「スパゲッティコード」)、goto
一般的に不足している機能を補うためにのみ使用されますgoto
。同じ目標。疑いの例を見てください:
私たちが使用するgotoのルールは、関数内の単一の出口クリーンアップポイントにジャンプするためにgotoは問題ないということです。
これは真実ですが、言語がクリーンアップコード(RAIIやなど)を使用した構造化例外処理を許可していない場合にのみfinally
、同じ仕事をより適切に実行します(それを行うために特別に構築されているため)、または正当な理由がない場合構造化された例外処理を採用する(ただし、非常に低いレベルを除いて、このケースは発生しません)。
他のほとんどの言語では、の唯一の許容可能な使用法は、goto
ネストされたループを終了することです。そして、そこでさえ、ほとんどの場合、外側のループを独自の方法に持ち上げて、return
代わりに使用する方が良いでしょう。
それ以外はgoto
、特定のコードに十分な考慮が払われていないことを示しています。
1サポートする現代言語goto
はいくつかの制限を実装します(たとえばgoto
、関数に飛び込んだり、関数から飛び出したりしない場合があります)が、問題は基本的に同じままです。
ちなみに、同じことが他の言語機能、特に例外にも当てはまります。また、通常、例外を使用して非例外的なプログラムフローを制御しないという規則など、指定された場合にのみこれらの機能を使用するための厳格な規則があります。
C# では、 switchステートメントは fall-through を許可しません。そのため、特定の switch-case ラベルまたはデフォルトラベルに制御を移すためにgotoが使用されます。
例えば:
switch(value)
{
case 0:
Console.WriteLine("In case 0");
goto case 1;
case 1:
Console.WriteLine("In case 1");
goto case 2;
case 2:
Console.WriteLine("In case 2");
goto default;
default:
Console.WriteLine("In default");
break;
}
編集:「フォールスルーなし」ルールには例外が1つあります。case ステートメントにコードがない場合、フォールスルーが許可されます。
#ifdef TONGUE_IN_CHEEK
Perl には、goto
貧弱な末尾呼び出しを実装できる があります。:-P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
さて、それは C とは何の関係もありませんgoto
。もっと真剣に、goto
クリーンアップやダフのデバイスの実装などに使用することについての他のコメントに同意します。悪用するのではなく、使用することがすべてです。
(同じコメントがlongjmp
、例外などにも当てはまりますcall/cc
。これらには正当な用途がありますが、簡単に悪用される可能性があります。たとえば、完全に例外的ではない状況下で、深くネストされた制御構造をエスケープするためだけに例外をスローする場合などです。 .)
私は何年にもわたって数行以上のアセンブリ言語を書いてきました。最終的に、すべての高級言語は goto にコンパイルされます。さて、それらを「ブランチ」または「ジャンプ」などと呼んでください。誰でもgoto-lessアセンブラを書くことができますか?
確かに、Fortran、C、または BASIC プログラマーに、goto で riot を実行することはスパゲッティ ボロネーゼのレシピであることを指摘できます。ただし、答えはそれらを避けることではなく、慎重に使用することです。
ナイフは、食べ物を準備したり、誰かを解放したり、誰かを殺したりするために使用できます。後者を恐れて、ナイフなしでやりますか?同様に goto: うっかり使うと邪魔になる、慎重に使うと助かる。
一部の人々が goto が許容されるケースのリストを提供し、他のすべての使用法は許容できないと言うのはおかしいと思います。goto がアルゴリズムを表現するための最良の選択であるすべてのケースを知っていると本当に思いますか?
説明のために、ここで誰もまだ示していない例を挙げます。
今日、私はハッシュ テーブルに要素を挿入するためのコードを書いていました。ハッシュ テーブルは以前の計算のキャッシュであり、自由に上書きできます (パフォーマンスには影響しますが、正確性には影響しません)。
ハッシュ テーブルの各バケットには 4 つのスロットがあり、バケットがいっぱいになったときにどの要素を上書きするかを決定する基準がたくさんあります。現在、これは次のように、バケットを介して最大 3 つのパスを作成することを意味します。
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
goto add;
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
goto add;
// Additional passes go here...
add:
// element is written to the hash table here
goto を使用しなかった場合、このコードはどのようになりますか?
このようなもの:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
break;
if (add_index >= ELEMENTS_PER_BUCKET) {
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
break;
if (add_index >= ELEMENTS_PER_BUCKET)
// Additional passes go here (nested further)...
}
// element is written to the hash table here
パスが追加されると見た目がますます悪くなりますが、goto を使用したバージョンは常に同じインデント レベルを維持し、前のループの実行によって結果が暗示される誤った if ステートメントの使用を回避します。
goto がコードをよりクリーンにし、書きやすく、理解しやすくする別のケースがあります...もっとたくさんあると思いますので、goto が役立つすべてのケースを知っているふりをしないでください。考えないでください。
私たちが使用するgotoのルールは、関数内の単一の出口クリーンアップポイントにジャンプするためにgotoは問題ないということです。非常に複雑な関数では、そのルールを緩和して、他のジャンプを許可します。どちらの場合も、エラーコードチェックで頻繁に発生する、深くネストされたifステートメントを避けています。これは、読みやすさと保守に役立ちます。
goto が悪い理由の 1 つは、コーディング スタイルに加えて、重複するがネストされていないループを作成するために使用できることです。
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
これにより、(a、b、c、b、a、b、a、b、...) のようなシーケンスが可能な、奇妙ではあるがおそらく合法的な制御フロー構造が作成され、コンパイラ ハッカーが不満を抱くようになります。どうやら、このタイプの構造が発生しないことに依存する巧妙な最適化のトリックがいくつかあるようです。(ドラゴンブックのコピーをチェックする必要があります...)これの結果、(一部のコンパイラを使用して)sを含むコードに対して他の最適化が行われない可能性がありますgoto
。
「ああ、ちなみに」というだけで、たまたまコンパイラがより高速なコードを出力するように説得することがわかっている場合、これは役立つかもしれません。goto
個人的には、goto のようなトリックを使用する前に、可能性のあるものとそうでないものについてコンパイラーに説明したいと思いますが、おそらく、アセンブラーをハッキングする前に試すこともできます。
Perlモジュールでは、サブルーチンやクロージャをその場で作成したい場合があります。重要なのは、サブルーチンを作成したら、どのようにしてそれに到達するかということです。それを呼び出すこともできますが、サブルーチンがcaller()
それを使用する場合、それはそれができるほど役に立ちません。そこで、goto &subroutine
バリエーションが役立ちます。
sub AUTOLOAD{
my($self) = @_;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
*{$name} = my($sub) = sub{
# the body of the closure
}
goto $sub;
# nothing after the goto will ever be executed.
}
この形式を使用してgoto
、基本的な形式の末尾呼び出しの最適化を提供することもできます。
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(Perl 5バージョン16では、次のように記述した方がよいでしょうgoto __SUB__;
)
tail
修飾子をインポートするモジュールと、recur
この形式のを使用したくない場合にインポートするモジュールがありますgoto
。
use Sub::Call::Tail;
sub AUTOLOAD {
...
tail &$sub( @_ );
}
use Sub::Call::Recur;
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
recur( $n-1, $tally * $n );
}
goto
のキーワードを使用する方が適切です。redo
ちょっとしたコードのように:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
last
または、複数の場所からのコードのビットに行きます:
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
1) 私が知っている goto の最も一般的な使用法は、それを提供していない言語、つまり C で例外処理をエミュレートすることです (上記の Nuclear によって提供されたコードはまさにそれです)。Linux のソース コードを見てください。無数の goto がそのように使用されるのを見るでしょう。2013 年に実施された簡単な調査によると、Linux コードには約 100,000 の goto がありました: http://blog.regehr.org/archives/894。Goto の使用法は、Linux コーディング スタイル ガイド ( https://www.kernel.org/doc/Documentation/CodingStyle ) でも言及されています。オブジェクト指向プログラミングが、関数ポインターが設定された構造体を使用してエミュレートされるように、goto は C プログラミングでその場所を占めています。では、ダイクストラとライナス (およびすべての Linux カーネル コーダー) のどちらが正しいのでしょうか? 基本的には理論と実践です。
ただし、コンパイラ レベルのサポートと一般的な構造/パターンのチェックがないという通常の落とし穴があります。それらを間違って使用したり、コンパイル時のチェックなしでバグを導入したりする方が簡単です。Windows と Visual C++ は、C モードでは SEH/VEH を介して例外処理を提供します。これは、まさにこの理由からです。例外は、OOP 言語の外、つまり手続き型言語でも役立ちます。ただし、コンパイラは、言語の例外に対する構文サポートを提供していても、常にベーコンを保存できるとは限りません。後者のケースの例として、有名な Apple SSL の「goto 失敗」バグを考えてみましょう。これは、悲惨な結果をもたらす 1 つの goto を複製しただけです ( https://www.imperialviolet.org/2014/02/22/applebug.html )。
if (something())
goto fail;
goto fail; // copypasta bug
printf("Never reached\n");
fail:
// control jumps here
コンパイラがサポートする例外を使用すると、まったく同じバグが発生する可能性があります。たとえば、C++ では次のようになります。
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
ただし、コンパイラが到達不能なコードを分析して警告する場合、バグの両方のバリアントを回避できます。たとえば、/W4 警告レベルで Visual C++ をコンパイルすると、どちらの場合でもバグが検出されます。たとえば、Java は到達不能なコード (それを見つけることができる場所) を禁止しますが、それにはかなりの理由があります。それは、平均的な Joe のコードのバグである可能性が高いからです。goto コンストラクトが、計算されたアドレス (**) への goto のように、コンパイラが簡単に把握できないターゲットを許可しない限り、Dijkstra を使用するよりも、コンパイラが goto を使用して関数内の到達不能なコードを見つけるのは難しくありません。 -承認されたコード。
(**) 脚注: Basic の一部のバージョンでは、計算された行番号への Goto が可能です。たとえば、x は変数である GOTO 10*x です。やや紛らわしいことに、Fortran では、「計算された goto」は、C の switch ステートメントと同等の構造を指します。標準 C では、言語で計算された goto は許可されませんが、静的/構文的に宣言されたラベルへの goto のみが許可されます。ただし、GNU C には、ラベルのアドレスを取得するための拡張 (単項、プレフィックス && 演算子) があり、型 void* の変数への goto も許可されています。このあいまいなサブトピックの詳細については、 https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlを参照してください。この投稿の残りの部分は、そのあいまいな GNU C 機能には関係ありません。
標準 C (つまり、計算されない) goto は、通常、コンパイル時に到達不能なコードが見つからない理由ではありません。通常の理由は、次のようなロジック コードです。与えられた
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
コンパイラが次の 3 つの構造のいずれかで到達不能なコードを見つけるのも同様に困難です。
void tough1() {
if (computation1() != computation2())
printf("Unreachable\n");
}
void tough2() {
if (computation1() == computation2())
goto out;
printf("Unreachable\n");
out:;
}
struct Out{};
void tough3() {
try {
if (computation1() == computation2())
throw Out();
printf("Unreachable\n");
}
catch (Out&) {
}
}
(ブレース関連のコーディング スタイルで申し訳ありませんが、例をできるだけコンパクトに保つように努めました。)
Visual C++ /W4 (/Ox を使用した場合でも) は、これらのいずれかで到達不能コードを検出できません。おそらくご存知のように、到達不能コードを検出する問題は、一般的に判断できません。(あなたがそれについて私を信じていない場合: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
関連する問題として、C goto を使用して、関数の本体内でのみ例外をエミュレートできます。標準 C ライブラリは、非ローカルの終了/例外をエミュレートするための setjmp() と longjmp() の関数のペアを提供しますが、これらには他の言語が提供するものと比較していくつかの重大な欠点があります。ウィキペディアの記事http://en.wikipedia.org/wiki/Setjmp.hは、この後者の問題をかなりよく説明しています。この関数ペアは Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ) でも動作しますが、SEH/VEH が優れているため、Windows で使用する人はほとんどいません。Unix でも、setjmp と longjmp はほとんど使用されていないと思います。
2) C での goto の 2 番目に一般的な使用法は、マルチレベル ブレークまたはマルチレベル コンティニューの実装であると思います。Java では goto ラベルは許可されていませんが、ブレーク ラベルまたはコンティニュー ラベルは許可されていることを思い出してください。http://www.oracle.com/technetwork/java/simple-142616.htmlによると、これは実際には C での goto の最も一般的な使用例 (90% と彼らは言う) ですが、私の主観的な経験では、システム コードは傾向があります。エラー処理に goto をより頻繁に使用する。おそらく、科学的なコードや、OS が例外処理を提供する場所 (Windows) では、マルチレベルの終了が支配的なユース ケースです。彼らは、調査の背景について詳細を明らかにしていません。
編集して追加: これら 2 つの使用パターンは、Kernighan と Ritchie の C ブックの 60 ページ付近 (版によって異なります) にあることがわかりました。もう 1 つの注意点は、どちらのユース ケースも前方 goto のみを含むことです。そして、MISRA C 2012 版 (2004 版とは異なり) では、goto が forward のみである限り許可されるようになりました。
C++ では goto の理由がないと言う人もいます。99% の場合、より良い代替手段があると言う人もいます。これは理屈ではなく、ただの不合理な印象です。これは、goto が強化された do-while ループのような素敵なコードにつながる確かな例です。
int i;
PROMPT_INSERT_NUMBER:
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
goto PROMPT_INSERT_NUMBER;
}
std::cout << "your number is " << i;
それをgoto-freeコードと比較してください:
int i;
bool loop;
do {
loop = false;
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
loop = true;
}
} while(loop);
std::cout << "your number is " << i;
次の違いがあります。
{}
ブロックが必要です (do {...} while
より見慣れているように見えますが)loop
変数が必要で、4 か所で使用されますloop
loop
データを保持せず、実行の流れを制御するだけであり、単純なラベルよりも理解しにくい別の例があります
void sort(int* array, int length) {
SORT:
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
goto SORT; // it is very easy to understand this code, right?
}
}
それでは、「悪」の後藤を取り除きましょう。
void sort(int* array, int length) {
bool seemslegit;
do {
seemslegit = true;
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
seemslegit = false;
}
} while(!seemslegit);
}
goto を使用するのと同じタイプであることがわかります。これは適切に構造化されたパターンであり、推奨される唯一の方法と同じように forward goto ではありません。確かに、次のような「スマート」なコードは避けたいでしょう:
void sort(int* array, int length) {
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
i = -1; // it works, but WTF on the first glance
}
}
要点は、goto は簡単に誤用される可能性があるということですが、goto 自体に責任はありません。label は C++ で関数スコープを持っているため、7 セグメント ディスプレイが P1 に接続されている 8051 の次のコードのように、重複するループがその場所を持ち、非常に一般的な純粋なアセンブリのようにグローバル スコープを汚染しないことに注意してください。プログラムは稲妻セグメントをループします。
; P1 states loops
; 11111110 <-
; 11111101 |
; 11111011 |
; 11110111 |
; 11101111 |
; 11011111 |
; |_________|
init_roll_state:
MOV P1,#11111110b
ACALL delay
next_roll_state:
MOV A,P1
RL A
MOV P1,A
ACALL delay
JNB P1.5, init_roll_state
SJMP next_roll_state
別の利点があります: goto は、名前付きループ、条件、およびその他のフローとして機能できます。
if(valid) {
do { // while(loop)
// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket
} while(loop);
} // if(valid)
または、インデント付きの同等の goto を使用できるため、ラベル名を賢く選択すればコメントは必要ありません。
if(!valid) goto NOTVALID;
LOOPBACK:
// more than one page of code here
if(loop) goto LOOPBACK;
NOTVALID:;
do {} while(false)の使用法は完全に反抗的です。奇妙な場合にはそれが必要であると私に納得させるかもしれないと考えられますが、それがクリーンで賢明なコードであることは決してありません。
このようなループを実行する必要がある場合は、フラグ変数への依存を明示的にしてみませんか?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
同様に、「COMEFROM」ステートメントを実装した人は誰もいません。
もしそうなら、なぜですか?
C にはマルチレベル/ラベル付きブレークがなく、すべての制御フローを C の反復および決定プリミティブで簡単にモデル化できるわけではありません。goto は、これらの欠陥の修正に大いに役立ちます。
ある種の疑似マルチレベル ブレークを実行するために何らかのフラグ変数を使用する方が明確な場合もありますが、常に goto よりも優れているとは限りません (少なくとも goto を使用すると、フラグ変数とは異なり、制御の行き先を簡単に判断できます)。 )、また、goto を回避するために、フラグやその他のゆがみのパフォーマンスの代償を払いたくない場合もあります。
libavcodec はパフォーマンスが重要なコードです。制御フローの直接的な表現は、より適切に実行される傾向があるため、おそらく優先事項です。
もちろん、GOTO を使用することもできますが、コード スタイルよりも重要なことが 1 つあります。つまり、コードを使用するときに考慮しなければならないコードの可読性の有無です。と思います。
たとえば、次の 2 つのコード スニペットを見てください。
If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)
GOTO と同等のコード
If A == 0 Then GOTO FINAL EndIf
A = 0
FINAL:
Write("Value of A:" + A)
最初に考えるのは、コードの両方のビットの結果が「A の値: 0」になるということです (もちろん、並列処理なしの実行を想定しています)。
これは正しくありません。最初のサンプルでは A は常に 0 ですが、2 番目のサンプル (GOTO ステートメントを使用) では A は 0 ではない可能性があります。
その理由は、プログラムの別のポイントからGOTO FINAL
、A の値を制御せずにa を挿入できるためです。
この例は非常に明白ですが、プログラムが複雑になるにつれて、そのようなものを見るのが難しくなります。
関連資料は、ダイクストラ氏の有名な記事「A case against the GO TO statement」にあります。
Perlでは、ループから「goto」するためのラベルを使用します。これは、breakに似た「last」ステートメントを使用します。
これにより、ネストされたループをより適切に制御できます。
従来のgotoラベルもサポートされていますが、これが目的を達成する唯一の方法である場合が多すぎるかどうかはわかりません。ほとんどの場合、サブルーチンとループで十分です。
「goto」の問題と「goto-lessprogramming」の動きの最も重要な議論は、コードを頻繁に使用すると、正しく動作する可能性はあるものの、読み取り不能、保守不能、レビュー不能などになることです。ケース「goto」はスパゲッティコードにつながります。個人的には、「goto」を使用する理由については考えられません。
この分野で大きな貢献をしたコンピュータ科学者の Edsger Dijkstra も、GoTo の使用を批判したことで有名です。ウィキペディアに彼の議論に関する短い記事があります。