HOWTO · C

C での Try-Catch

この記事では、C で try-catch 動作を実装する例を示します。これは、デフォルトでは、try-catch/exception 処理メカニズムをサポートしていません。

このページの内容

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つの関数、longjmpsetjmp を使用します。これらは 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 が呼び出されると、呼び出し元の環境の状態がプロセッサにコピーされ、longjmpval 引数に格納されている値が返されます。

単純な 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-CatchFinally を追加

完全に機能する Try-Catch 実装には、FINALLY ブロックを追加する必要があります。finally ブロックは通常、try および catch ブロックが実行された後に実行されます。

例外がスローされたかどうかに関係なく実行されます。

これをどのように実装しますか?重要なアイデアは、switch ケースの default ケースを使用して、FINALLY ブロックを実装することです。

ただし、通常の場合に例外が呼び出された場合、switch-casedefault ケースを実行しません。

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 の実装。