C での Try-Catch
Try-Catch
メカニズムは、Python、C++、JavaScript などの多くのプログラミング言語で一般的です。一般的な構造は以下のとおりです。
try {
/*
Insert some lines of code that will probably give you errors
*/
} catch {
/*
Write some code to handle the errors you're getting.
*/
}
各ステートメントをテストしなくても、コードを記述できます。try
ブロックで実行されているプログラムが例外に達すると、その例外は catch
ブロックに渡されます。
例外が特定の例外タイプと一致する場合、catch
ブロック内のコードが実行されます。それ以外の場合、例外は try
ブロックに戻されます。
C の Try-Catch
C は例外処理をサポートしていません。少なくとも、組み込みのメカニズムはありません。
このガイドでは、C で try-catch
機能を提供するための可能な解決策を示します。解決策は必ずしも完全ではないことに注意してください。
例外処理システムは、スタックがトラバースされたときにメモリを解放するメカニズムがなければ完全で安全ではなく、C にはガベージコレクターがありません。また、メモリを解放するためにコンテキストマネージャを含める必要もあります。
このソリューションは、完全で広範な try-catch
メカニズムを提供することを意図したものではありません。この概念は、技術的にはいくつかの例外を処理するために使用できます。
ソリューションを段階的に構築し、コードを更新します。C が提供する 2つの関数、longjmp
と setjmp
を使用します。これらは setjmp.h
ヘッダーファイルから取得できます。
両方の関数の定義を詳しく見ていきます。
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp
は、タイプ jmp_buf
の変数を取ります。この関数を直接呼び出すと、0
が返されます。
longjmp
は 2つの変数を取り、longjmp
が同じ jmp_buf
変数で呼び出されると、setjmp
関数は longjmp
の 2 番目の引数(val
)と同じ値を返します。
ここでの env
変数は本質的に呼び出し環境
であり、レジスターの状態と、関数呼び出しが行われているときのコード内の位置を表します。longjmp
が呼び出されると、呼び出し元の環境
の状態がプロセッサにコピーされ、longjmp
の val
引数に格納されている値が返されます。
単純な Try-Catch
ブロックの場合、Try
ステートメントを if
ステートメントにマップすると、Catch
ステートメントが条件付きの else
になります。ここで、setjmp
がさまざまな値を返すことができるという事実をインテリジェントに利用できます。
関数が 0
を返す場合、実行されたコードは TRY
ブロックのコードのみであることがわかります。関数が他のものを返す場合は、開始時と同じ状態で CATCH
ブロックに入る必要があります。
例外を THROW
すると、longjmp
関数を呼び出すことができます。
以下のコードでわかるように、TRY
ブロックも閉じる必要があります。do-while
ブロックの終了部分を提供する ENDTRY
関数を作成します。
これは、同じブロック内に複数の TRY
ステートメントを作成するのにも役立ちます。buf_state
変数を再利用するため、ネストできないことに注意してください。
この実装の例を以下に示します。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
if (!setjmp(buf_state)) {
#define CATCH \
} \
else {
#define ENDTRY \
} \
} \
while (0)
#define THROW longjmp(buf_state, 1)
int main() {
TRY {
printf("Testing Try statement \n");
THROW;
printf(
"Statement should not appear, as the THROW block has already thrown "
"the exception \n");
}
CATCH { printf("Got Exception \n"); }
ENDTRY;
return 0;
}
出力:
Testing Try statement
Got Exception
実際のシステムでは、これだけでは不十分です。さまざまな種類の例外が必要です。
上記の例では、1つのタイプの例外のみがサポートされています。ここでも、setjmp
のさまざまな戻り値を使用できます。
if-else
を使用する代わりに、switch-case
でこれを切り替えます。
設計は次のとおりです。TRY
ステートメントは switch
ステートメントを使用し、CATCH
は例外タイプを表すパラメーターを持つマクロになります。各 CATCH
ステートメントの条件は、break
を使用して前の case
を閉じる必要があることです。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
switch (setjmp(buf_state)) { \
case 0:
#define CATCH(x) \
break; \
case x:
#define ENDTRY \
} \
} \
while (0)
#define THROW(x) longjmp(buf_state, x)
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
TRY {
printf("Inside Try statement \n");
THROW(EXCEPTION2);
printf("This does not appear as exception has already been called \n");
}
CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
ENDTRY;
return 0;
}
出力:
Inside Try statement
Exception 2 called
C の Try-Catch
に Finally
を追加
完全に機能する Try-Catch
実装には、FINALLY
ブロックを追加する必要があります。finally
ブロックは通常、try
および catch
ブロックが実行された後に実行されます。
例外がスローされたかどうかに関係なく実行されます。
これをどのように実装しますか?重要なアイデアは、switch
ケースの default
ケースを使用して、FINALLY
ブロックを実装することです。
ただし、通常の場合に例外が呼び出された場合、switch-case
は default
ケースを実行しません。
Duff’sDeviceと同様のメカニズムを使用します。基本的に、switch-case
ステートメントと do-while
ステートメントが絡み合っています。
その論理はこんな感じです。
switch (an expression) {
case 0:
while (1) {
// code for case 0
break;
case 1:
// code for case 1
break;
}
default:
// code for default case
}
C の最も物議を醸す機能の 1つを使用します。各ケースラベルの前にスイッチが自動的に壊れないことです。ここで、while
ステートメントは、break
が呼び出されたときに switch-case
内にネストされます。while
ループを終了し、ケースをトラバースし続けます。
コードのコンテキストでは(以下に示すように)、switch
ケースはすべて例外であり、default
ケースは FINALLY
ブロックにあります。当然、例外コードはすでに実行されているため、デフォルト
の場合に該当します。
これは、以下のコードに示されています。
#include <setjmp.h>
#include <stdio.h>
#define TRY \
do { \
jmp_buf buf_state; \
switch (setjmp(buf_state)) { \
case 0: \
while (1) {
#define CATCH(x) \
break; \
case x:
#define ENDTRY \
} \
} \
while (0)
#define THROW(x) longjmp(buf_state, x)
#define FINALLY \
break; \
} \
default:
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
TRY {
printf("Inside Try statement \n");
THROW(EXCEPTION2);
printf("This does not appear as exception has already been called \n");
}
CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
FINALLY { printf("This will always be called! \n"); }
ENDTRY;
return 0;
}
出力:
Inside Try statement
Exception 2 called
This will always be called!
これで、C で try-catch
システムを作成するためのガイドは終わりです。もちろん、ここにはメモリの問題があり、いくつかの制限(ネストされた try-catch
システムのサポートの欠如など)がありますが、これは C での機能的な try-catch
の実装。
Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.
LinkedIn