129

私は今日、他の言語に存在するtry/catchブロックについて考えていました。これはしばらくの間グーグルで検索しましたが、結果はありませんでした。私の知る限り、Cではtry / catchのようなものはありません。しかし、それらを「シミュレート」する方法はありますか?
確かに、assertやその他のトリックはありますが、try / catchのようなものはなく、発生した例外もキャッチします。ありがとうございました

4

13 に答える 13

109

C自体は例外をサポートしていませんが、とを使用してある程度まで例外をシミュレートできsetjmpますlongjmp

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened here\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjmp(s_jumpBuffer, 42);
}

このWebサイトには、とを使用して例外をシミュレートする方法に関する優れたチュートリアルがsetjmpあります。longjmp

于 2012-05-14T15:12:03.250 に答える
26

同様のエラー処理状況では、Cでgotoを使用します。
これは、Cで取得できる例外に最も近いものです。

于 2012-05-14T15:08:14.517 に答える
21

わかりました、私はこれに返信するのに抵抗できませんでした。最初に、これをCでシミュレートするのは良い考えではないと思います。これは、実際にはCにとって異質な概念だからです。

プリプロセッサとローカルスタック変数を悪用して、限定バージョンのC ++ try / throw/catchを使用できます

バージョン1(ローカルスコープスロー)

#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

バージョン1はローカルスローのみです(関数のスコープを離れることはできません)。これは、コードで変数を宣言するC99の機能に依存しています(関数で最初に試行する場合は、C89で機能するはずです)。

この関数はローカル変数を作成するだけなので、エラーが発生したかどうかを認識し、gotoを使用してcatchブロックにジャンプします。

例えば:

#include <stdio.h>
#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

int main(void)
{
    try
    {
        printf("One\n");
        throw();
        printf("Two\n");
    }
    catch(...)
    {
        printf("Error\n");
    }
    return 0;
}

これは次のようになります。

int main(void)
{
    bool HadError=false;
    {
        printf("One\n");
        {
            HadError=true;
            goto ExitJmp;
        }
        printf("Two\n");
    }
ExitJmp:
    if(HadError)
    {
        printf("Error\n");
    }
    return 0;
}

バージョン2(スコープジャンプ)

#include <stdbool.h>
#include <setjmp.h>

jmp_buf *g__ActiveBuf;

#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);

バージョン2ははるかに複雑ですが、基本的に同じように機能します。現在の関数からtryブロックへのロングジャンプを使用します。次に、tryブロックはif / elseを使用してコードブロックをcatchブロックにスキップし、ローカル変数をチェックしてキャッチする必要があるかどうかを確認します。

例は再び拡張されました:

jmp_buf *g_ActiveBuf;

int main(void)
{
    jmp_buf LocalJmpBuff;
    jmp_buf *OldActiveBuf=g_ActiveBuf;
    bool WasThrown=false;
    g_ActiveBuf=&LocalJmpBuff;

    if(setjmp(LocalJmpBuff))
    {
        WasThrown=true;
    }
    else
    {
        printf("One\n");
        longjmp(*g_ActiveBuf,1);
        printf("Two\n");
    }
    g_ActiveBuf=OldActiveBuf;
    if(WasThrown)
    {
        printf("Error\n");
    }
    return 0;
}

これはグローバルポインタを使用するため、longjmp()は最後に実行された試行を認識します。スタックの悪用を使用しているため、子関数もtry/catchブロックを持つことができます。

このコードを使用すると、いくつかの欠点があります(ただし、楽しいメンタルエクササイズです)。

  • 呼び出されているデコンストラクタがないため、割り当てられたメモリは解放されません。
  • スコープ内に複数のtry/catchを含めることはできません(ネストなし)
  • C ++のように、実際に例外やその他のデータをスローすることはできません。
  • スレッドセーフではありません
  • 他のプログラマーがハッキングに気付かず、C++のtry/ catchブロックのように使用しようとする可能性があるため、他のプログラマーを失敗に備えて設定しています。
于 2016-09-06T16:26:29.943 に答える
10

C99では、非ローカル制御フローにsetjmp/を使用できます。longjmp

単一のスコープ内で、複数のリソース割り当てと複数の出口が存在する場合のCの一般的な構造化コーディングパターンは、この例gotoのようにを使用します。これは、C ++が内部で自動オブジェクトのデストラクタ呼び出しを実装する方法に似ており、これに熱心に固執すれば、複雑な関数でもある程度のクリーンさを実現できるはずです。

于 2012-05-14T15:10:01.730 に答える
6

他の回答のいくつかはとを使用した単純なケースをカバーしていsetjmpますlongjmpが、実際のアプリケーションでは、本当に重要な2つの懸念事項があります。

  1. try/catchブロックのネスト。に単一のグローバル変数を使用するjmp_bufと、これらは機能しなくなります。
  2. 糸脱毛。単一のグローバル変数はjmp_buf、この状況であらゆる種類の苦痛を引き起こします。

jmp_bufこれらの解決策は、進行中に更新されるスレッドローカルスタックを維持することです。(これはluaが内部で使用するものだと思います)。

だからこれの代わりに(JaredParの素晴らしい答えから)

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjump(s_jumpBuffer, 42);
}

次のようなものを使用します:

#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
  jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
  int current_depth;
};

int try_point(struct exception_state * state) {
  if(current_depth==MAX_EXCEPTION_DEPTH) {
     abort();
  }
  int ok = setjmp(state->jumpBuffer[state->current_depth]);
  if(ok) {
    state->current_depth++;
  } else {
    //We've had an exception update the stack.
    state->current_depth--;
  }
  return ok;
}

void throw_exception(struct exception_state * state) {
  longjump(state->current_depth-1,1);
}

void catch_point(struct exception_state * state) {
    state->current_depth--;
}

void end_try_point(struct exception_state * state) {
    state->current_depth--;
}

__thread struct exception_state g_exception_state; 

void Example() { 
  if (try_point(&g_exception_state)) {
    catch_point(&g_exception_state);
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
    end_try_point(&g_exception_state);
  }
}

void Test() {
  // Rough equivalent of `throw`
  throw_exception(g_exception_state);
}

繰り返しますが、これのより現実的なバージョンには、エラー情報をに格納する方法exception_state、より適切な処理MAX_EXCEPTION_DEPTH(おそらく、reallocを使用してバッファーを拡張するなど)が含まれます。

免責事項:上記のコードは、何のテストも行わずに作成されました。それは純粋に、物事をどのように構成するかについてのアイデアを得ることができるようにするためです。システムやコンパイラが異なれば、スレッドローカルストレージを異なる方法で実装する必要があります。コードにはおそらくコンパイルエラーとロジックエラーの両方が含まれているため、自由に使用できますが、使用する前にテストしてください;)

于 2014-11-20T00:31:43.357 に答える
5

これは、Cでエラー処理を行う別の方法であり、setjmp/longjmpを使用するよりもパフォーマンスが高くなります。残念ながら、MSVCでは機能しませんが、GCC / Clangのみを使用するオプションがある場合は、それを検討することをお勧めします。具体的には、「label as value」拡張機能を使用します。これにより、ラベルのアドレスを取得して値に格納し、無条件にジャンプできます。例を使用して説明します。

GameEngine *CreateGameEngine(GameEngineParams const *params)
{
    /* Declare an error handler variable. This will hold the address
       to jump to if an error occurs to cleanup pending resources.
       Initialize it to the err label which simply returns an
       error value (NULL in this example). The && operator resolves to
       the address of the label err */
    void *eh = &&err;

    /* Try the allocation */
    GameEngine *engine = malloc(sizeof *engine);
    if (!engine)
        goto *eh; /* this is essentially your "throw" */

    /* Now make sure that if we throw from this point on, the memory
       gets deallocated. As a convention you could name the label "undo_"
       followed by the operation to rollback. */
    eh = &&undo_malloc;

    /* Now carry on with the initialization. */
    engine->window = OpenWindow(...);
    if (!engine->window)
        goto *eh;   /* The neat trick about using approach is that you don't
                       need to remember what "undo" label to go to in code.
                       Simply go to *eh. */

    eh = &&undo_window_open;

    /* etc */

    /* Everything went well, just return the device. */
    return device;

    /* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}

必要に応じて、定義内の共通コードをリファクタリングして、独自のエラー処理システムを効果的に実装できます。

/* Put at the beginning of a function that may fail. */
#define declthrows void *_eh = &&err

/* Cleans up resources and returns error result. */
#define throw goto *_eh

/* Sets a new undo checkpoint. */
#define undo(label) _eh = &&undo_##label

/* Throws if [condition] evaluates to false. */
#define check(condition) if (!(condition)) throw

/* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
#define checkpoint(label, condition) { check(condition); undo(label); }

次に、例は次のようになります

GameEngine *CreateGameEngine(GameEngineParams const *params)
{
    declthrows;

    /* Try the allocation */
    GameEngine *engine = malloc(sizeof *engine);
    checkpoint(malloc, engine);

    /* Now carry on with the initialization. */
    engine->window = OpenWindow(...);
    checkpoint(window_open, engine->window);

    /* etc */

    /* Everything went well, just return the device. */
    return device;

    /* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
于 2017-07-04T18:04:27.940 に答える
4

クイックグーグル検索は、他の人が言及したように、setjmp/longjmpを使用するこのような厄介な解決策をもたらします。C ++/Javaのtry/catchほど単純でエレガントなものはありません。私は自分自身を処理するエイダの例外にかなり部分的です。

ifステートメントですべてをチェックしてください:)

于 2012-05-14T15:10:49.053 に答える
4

これはsetjmp/longjmpCで行うことができます。P99にはこのための非常に快適なツールセットがあり、C11の新しいスレッドモデルとも一致しています。

于 2012-05-14T15:11:26.577 に答える
3

Cでは、明示的なエラー処理のためにif + gotoを手動で使用することにより、自動の「オブジェクト再利用」とともに例外を「エミュレート」できます。

私はよく次のようなCコードを記述します(エラー処理を強調するために要約します)。

#include <assert.h>

typedef int errcode;

errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
    errcode ret = 0;

    if ( ( ret = foo_init( f ) ) )
        goto FAIL;

    if ( ( ret = goo_init( g ) ) )
        goto FAIL_F;

    if ( ( ret = poo_init( p ) ) )
        goto FAIL_G;

    if ( ( ret = loo_init( l ) ) )
        goto FAIL_P;

    assert( 0 == ret );
    goto END;

    /* error handling and return */

    /* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */

FAIL_P:
    poo_fini( p );

FAIL_G:
    goo_fini( g );

FAIL_F:
    foo_fini( f );

FAIL:
    assert( 0 != ret );

END:
    return ret;        
}

これは完全に標準のANSICであり、エラー処理をメインラインコードから分離し、C ++と同じように初期化されたオブジェクトの(手動)スタックアンワインドを可能にし、ここで何が起こっているかを完全に明らかにします。各ポイントで障害を明示的にテストしているため、エラーが発生する可能性のある各場所に特定のログまたはエラー処理を簡単に挿入できます。

少しマクロの魔法を気にしないのであれば、スタックトレースでエラーをログに記録するなどの他のことをしながら、これをより簡潔にすることができます。例えば:

#include <assert.h>
#include <stdio.h>
#include <string.h>

#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '%s' failed! %d, %s\n", __FILE__, __LINE__, #X, ret, strerror( ret ) ); goto LABEL; } while ( 0 )

typedef int errcode;

errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
    errcode ret = 0;

    TRY( ret = foo_init( f ), FAIL );
    TRY( ret = goo_init( g ), FAIL_F );
    TRY( ret = poo_init( p ), FAIL_G );
    TRY( ret = loo_init( l ), FAIL_P );

    assert( 0 == ret );
    goto END;

    /* error handling and return */

FAIL_P:
    poo_fini( p );

FAIL_G:
    goo_fini( g );

FAIL_F:
    foo_fini( f );

FAIL:
    assert( 0 != ret );

END:
    return ret;        
}

もちろん、これはC++例外+デストラクタほどエレガントではありません。たとえば、この方法で1つの関数内に複数のエラー処理スタックをネストすることは、あまりクリーンではありません。代わりに、これらを同様にエラーを処理する自己完結型のサブ関数に分割し、このように明示的に初期化+ファイナライズすることをお勧めします。

これも単一の関数内でのみ機能し、上位レベルの呼び出し元が同様の明示的なエラー処理ロジックを実装しない限り、スタックをジャンプし続けることはありませんが、C ++例外は、適切なハンドラーが見つかるまでスタックをジャンプし続けます。また、任意のタイプをスローすることはできませんが、代わりにエラーコードのみをスローすることができます。

このように体系的にコーディングすると(つまり、単一のエントリと単一の出口ポイントを使用して)、何があっても実行される前後(「最終」)ロジックを非常に簡単に挿入できます。ENDラベルの後に「最終的に」ロジックを配置するだけです。

于 2018-07-17T19:15:53.973 に答える
2

警告:以下はあまり良くありませんが、それは仕事をします。

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    unsigned int  id;
    char         *name;
    char         *msg;
} error;

#define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " ‘%s_error’ " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__)
#define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__)
#define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg)

#define _errordef(n, _id) \
error* new_##n##_error_msg(char* msg) { \
    error* self = malloc(sizeof(error)); \
    self->id = _id; \
    self->name = #n; \
    self->msg = msg; \
    return self; \
} \
error* new_##n##_error() { return new_##n##_error_msg(""); }

#define errordef(n) _errordef(n, __COUNTER__ +1)

#define try(try_block, err, err_name, catch_block) { \
    error * err_name = NULL; \
    error ** __err = & err_name; \
    void __try_fn() try_block \
    __try_fn(); \
    void __catch_fn() { \
        if (err_name == NULL) return; \
        unsigned int __##err_name##_id = new_##err##_error()->id; \
        if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \
            printuncaughterr(); \
        else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \
            catch_block \
    } \
    __catch_fn(); \
}

#define throw(e) { *__err = e; return; }

_errordef(any, 0)

使用法:

errordef(my_err1)
errordef(my_err2)

try ({
    printf("Helloo\n");
    throw(new_my_err1_error_msg("hiiiii!"));
    printf("This will not be printed!\n");
}, /*catch*/ any, e, {
    printf("My lovely error: %s %s\n", e->name, e->msg);
})

printf("\n");

try ({
    printf("Helloo\n");
    throw(new_my_err2_error_msg("my msg!"));
    printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
    printerr("%s", e->msg);
})

printf("\n");

try ({
    printf("Helloo\n");
    throw(new_my_err1_error());
    printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
    printf("Catch %s if you can!\n", e->name);
})

出力:

Helloo
My lovely error: my_err1 hiiiii!

Helloo
/home/naheel/Desktop/aa.c:28: error: ‘my_err2_error’ my msg!

Helloo
/home/naheel/Desktop/aa.c:38: uncaught error: ‘my_err1_error’ 

これは入れ子関数とを使用していることに注意してください__COUNTER__。gccを使用している場合は、安全を確保できます。

于 2017-05-21T19:39:54.547 に答える
1

Redisはgotoを使用してtry/catchをシミュレートしますが、私見では非常にクリーンでエレガントです。

/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
int rdbSave(char *filename) {
    char tmpfile[256];
    FILE *fp;
    rio rdb;
    int error = 0;

    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }

    rioInitWithFile(&rdb,fp);
    if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
        errno = error;
        goto werr;
    }

    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"DB saved on disk");
    server.dirty = 0;
    server.lastsave = time(NULL);
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;

werr:
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
    return REDIS_ERR;
}
于 2014-12-10T09:45:13.087 に答える
0

Win32でCを使用している場合は、その構造化例外処理(SEH)を利用してtry/catchをシミュレートできます。

setjmp()およびをサポートしていないプラットフォームでCを使用している場合は、pjsipライブラリのlongjmp()この例外処理を参照してください。Cは独自の実装を提供します。

于 2013-07-22T11:19:45.880 に答える
-1

おそらく主要な言語ではありませんが(残念ながら)、APLには⎕EA操作(ExecuteAlternateの略)があります。

使用法:'Y'⎕EA'X'ここで、XとYは、文字列または関数名として提供されるコードスニペットです。

Xでエラーが発生した場合は、代わりにY(通常はエラー処理)が実行されます。

于 2013-04-09T15:28:22.920 に答える