他の人が言ったように、問題はgoto
それ自体ではありません。問題は、人々がどのように使用するかgoto
、そしてそれがどのようにコードを理解し維持するのを難しくするかということです。
次のコードスニペットを想定します。
i = 4;
label: printf( "%d\n", i );
どのような値が印刷されi
ますか? いつ印刷されますか?関数内のすべてのインスタンスを説明するまでgoto label
、あなたは知ることができません。そのラベルの単純な存在は、単純な検査によってコードをデバッグする能力を破壊します。1つまたは2つのブランチを持つ小さな関数の場合、それほど問題はありません。小さくない関数の場合...
90年代初期の頃、3Dグラフィック表示を駆動し、より高速に実行するように指示されたCコードの山が与えられました。コードは約5000行だけでしたが、すべてはにmain
あり、著者goto
は両方向に約15程度の分岐を使用しました。そもそもこれは悪いコードでしたが、それらgoto
の存在はそれをさらに悪化させました。同僚が制御の流れを解明するのに約2週間かかりました。さらに良いことに、これらgoto
はコードをそれ自体と密接に結び付けているため、何かを壊さずに変更を加えることはできません。
レベル1の最適化でコンパイルしてみました。コンパイラーは使用可能なすべてのRAM、次に使用可能なすべてのスワップを使い果たし、システムをパニック状態にしました(おそらくgoto
s自体とは何の関係もありませんでしたが、その逸話を捨てるのが好きです)。
最後に、顧客に2つのオプションを与えました-ゼロからすべてを書き換えるか、より高速なハードウェアを購入しましょう。
彼らはより速いハードウェアを買いました。
使用するためのボードのルールgoto
:
- 前方分岐のみ。
- 制御構造をバイパスしないでください(つまり、
if
or for
またはwhile
ステートメントの本体に分岐しないでください)。
goto
制御構造の代わりに使用しないでください
a goto
が正しい答えである場合もありますが、まれです(深く入れ子になったループから抜け出すことは、私がそれを使用する唯一の場所です)。
編集
その最後のステートメントを拡張して、の数少ない有効なユースケースの1つを以下に示しますgoto
。次の機能があると仮定します。
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
for ( i = 0; i < N; i ++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
for ( j = 0; j < M; j++ )
{
arr[i][j] = malloc( sizeof *arr[i][j] * P );
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
return arr;
}
さて、問題があります- malloc
呼び出しの1つが途中で失敗した場合はどうなりますか?イベントとは異なり、部分的に割り当てられた配列を返したくはありません。また、エラーを出して関数から脱出したいだけでもありません。自分の後にクリーンアップし、部分的に割り当てられたメモリの割り当てを解除する必要があります。悪いallocで例外をスローする言語では、それはかなり簡単です-既に割り当てられているものを解放するために例外ハンドラを書くだけです。
Cでは、構造化された例外処理はありません。各malloc
呼び出しの戻り値を確認し、適切なアクションを実行する必要があります。
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( i = 0; i < N; i ++ )
{
if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
goto cleanup_1;
for ( j = 0; j < M; j++ )
{
if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
goto cleanup_2;
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
}
goto done;
cleanup_2:
// We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
while ( j-- )
free( arr[i][j] );
free( arr[i] );
// fall through
cleanup_1:
// We failed while allocating arr[i]; free up all previously allocated arr[i][j]
while ( i-- )
{
for ( j = 0; j < M; j++ )
free( arr[i][j] );
free( arr[i] );
}
free( arr );
arr = NULL;
done:
return arr;
}
使用せずにこれを行うことはできますgoto
か?もちろんできます-少し余分な簿記が必要です(そして、実際には、それが私がとる道です)。しかし、aの使用goto
がすぐに悪い習慣やデザインの兆候ではない場所を探している場合、これは数少ないものの1つです。