goto 演算子は、C言語のプログラミングにおいて最も議論されているテーマの一つです。
歴史的背景
goto 演算子は、他のメカニズムがまだ存在しなかった初期のプログラミング言語で、分岐やループを書くために簡素化する目的で導入されました。C言語では、互換性のためと、通常の構文が適さない比較的まれなケースに備えて保持されています。
問題点
goto は、一部の低レベルのアルゴリズム(例えば、複雑なエラーハンドリング)の実装を簡素化しますが、簡単に「スパゲティコード」に変わることがあり、実行フローの流れが混乱します。不適切な使用は、テストや理解、コードの保守を困難にします。
解決策
goto の使用は、多重入れ子ループからの制御を管理したり、エラー時にリソースを一括して解放する際に許可されます。たとえば、異なるステージで割り当てられた複数のリソースを順に解放する必要がある場合です。
コード例:
#include <stdio.h> #include <stdlib.h> int process() { int *a = malloc(10 * sizeof(int)); if (!a) return -1; int *b = malloc(20 * sizeof(int)); if (!b) goto cleanup_a; // ... free(b); cleanup_a: free(a); return 0; }
主な特徴:
gotoは他の関数に飛ぶことができますか、または関数から出ることができますか?
いいえ、goto 演算子は同じ関数内のラベルのみにジャンプできます。関数間でジャンプしようとすると、コンパイルエラーが発生します。
変数宣言ブロックに入るためにgotoを使用できますか?
厳禁です!自動初期化された変数が宣言されるブロックに goto で入ることは未定義の動作を引き起こします。
コード例:
void bad() { goto label; int x = 5; label: printf("%d ", x); // 未定義の動作 }
continueとbreakの演算子はgotoですか?
いいえ。break と continue の演算子はループを制御するために特化されており、アイデアとしては goto に似ていますが、言語レベルでは最も近い外部のループのみで機能し、goto は関数内で宣言されたラベルに作用します。
利点: エラーとリソース解放をコンパクトに処理でき、多重構造からの脱出を容易にすることがある。
欠点: "スパゲティコード" を容易に作成し、保守を複雑にし、構造的プログラミングを妨げる。
ネガティブケース:
プロジェクトでは、約50回の goto があります。中にはテキストの上部へ逆戻りするものもあり、その結果、論理を理解するのが非常に難しく、エラーが増え、複雑さが増し、保守コストが高くなります。利点:すばやく書かれた、欠点:理解および修正がほぼ不可能。
ポジティブケース:
大きなオブジェクトの初期化関数では、エラー時にリソースを中央で解放するためにのみ goto を使用します。コードは簡潔で、保守が容易で新しいリソースを追加するのも簡単です。利点:読みやすさ、メモリリークの防止;欠点:一部の人は goto をアンチパターンと見なしており、使用には注意が必要です。