Coba pernyataan tangkap di C

101

Saya berpikir hari ini tentang blok coba / tangkap yang ada dalam bahasa lain. Googled untuk sementara ini tetapi tanpa hasil. Dari apa yang saya tahu, tidak ada yang namanya coba / tangkap di C. Namun, apakah ada cara untuk "mensimulasikan" mereka?
Tentu, ada trik assert dan trik lain tetapi tidak seperti coba / tangkap, yang juga menangkap pengecualian yang dimunculkan. Terima kasih

Andrew
sumber
3
Mekanisme seperti pengecualian tidak akan berguna secara umum tanpa mekanisme untuk membebaskan sumber daya secara otomatis saat tumpukan dibatalkan. C ++ menggunakan RAII; Java, C #, Python, dll. Menggunakan pengumpul sampah. (Dan perhatikan bahwa pengumpul sampah hanya membebaskan memori. Untuk secara otomatis membebaskan jenis sumber daya lain, mereka juga menambahkan hal-hal seperti finalizer atau manajer konteks ...)
jamesdlin
@jamesdlin, Mengapa kita tidak bisa melakukan RAII dengan C?
Pacerier
1
@Pacerier RAII memerlukan pemanggilan fungsi secara otomatis saat objek dimusnahkan (mis., Destruktor). Bagaimana Anda mengusulkan melakukan itu di C?
jamesdlin

Jawaban:

90

C itu sendiri tidak mendukung pengecualian tetapi Anda dapat mensimulasikannya sampai taraf tertentu dengan panggilan setjmpdan 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);
}

Situs web ini memiliki tutorial bagus tentang cara mensimulasikan pengecualian dengan setjmpdanlongjmp

JaredPar
sumber
1
solusi yang luar biasa! apakah solusi ini silang? Ini bekerja untuk saya di MSVC2012 tetapi tidak di kompiler MacOSX Clang.
mannysz
1
petunjuk: Saya pikir klausa coba tangkap memungkinkan Anda menangkap pengecualian (seperti membagi dengan nol). Fungsi ini sepertinya hanya memungkinkan Anda untuk menangkap pengecualian yang Anda lemparkan sendiri. Pengecualian nyata tidak diberikan dengan memanggil longjmp bukan? Jika saya menggunakan kode ini untuk melakukan sesuatu seperti try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; itu tidak akan berfungsi, bukan?
Sam
Membagi dengan nol bahkan bukan pengecualian dalam C ++, untuk menanganinya Anda perlu memeriksa pembagi bukan nol dan menanganinya atau menangani SIGFPE yang dilemparkan saat Anda menjalankan rumus devide dengan nol.
James
25

Anda menggunakan goto di C untuk situasi penanganan kesalahan yang serupa.
Itu adalah ekuivalen terdekat dari pengecualian yang bisa Anda dapatkan di C.

Alok Save
sumber
3
@JensGustedt Inilah yang saat ini sangat sering digunakan oleh goto dan contoh yang masuk akal (setjmp / ljmp adalah alternatif yang lebih baik, tetapi label + goto biasanya lebih sering digunakan).
Tomas Pruzina
1
@AoeAoe, mungkin gotolebih digunakan untuk penanganan error, tapi terus kenapa ? Pertanyaannya bukan tentang penanganan kesalahan seperti itu tetapi secara eksplisit tentang persamaan coba / tangkap. gotobukan ekuivalen dari coba / tangkap karena dibatasi untuk fungsi yang sama.
Justedt
@JensGustedt Saya agak bereaksi terhadap kebencian / ketakutan akan goto dan orang-orang yang menggunakannya (guru saya juga menceritakan kisah-kisah menakutkan tentang penggunaan goto di universitas). [OT] Satu-satunya hal yang benar-benar berisiko dan 'berawan' tentang goto adalah 'pergi ke belakang', tetapi saya telah melihat itu di Linux VFS (git menyalahkan orang bersumpah bahwa itu adalah kinerja yang sangat menguntungkan).
Tomas Pruzina
Lihat sumber systemctl untuk penggunaan yang sah gotosebagai mekanisme coba / tangkap yang digunakan dalam sumber modern, diterima secara luas, dan ditinjau sejawat. Telusuri gotopadanan "lemparan", dan padanan finish"tangkapan".
Stewart
13

Oke, saya tidak bisa menahan diri untuk tidak membalas ini. Izinkan saya mengatakan saya rasa itu bukan ide yang baik untuk mensimulasikan ini di C karena ini benar-benar konsep asing untuk C.

Kita dapat menggunakan menyalahgunakan preprocessor dan lokal variabel stack untuk memberikan penggunaan versi terbatas dari C ++ mencoba / melempar / menangkap.

Versi 1 (lemparan cakupan lokal)

#include <stdbool.h>

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

Versi 1 adalah lemparan lokal saja (tidak dapat meninggalkan ruang lingkup fungsi). Itu bergantung pada kemampuan C99 untuk mendeklarasikan variabel dalam kode (itu harus bekerja di C89 jika percobaan adalah hal pertama dalam fungsi).

Fungsi ini hanya membuat var lokal sehingga ia tahu jika ada kesalahan dan menggunakan goto untuk melompat ke blok catch.

Sebagai contoh:

#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;
}

Ini berhasil untuk sesuatu seperti:

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

Versi 2 (scope jumping)

#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);

Versi 2 jauh lebih kompleks tetapi pada dasarnya bekerja dengan cara yang sama. Ini menggunakan lompat jauh dari fungsi saat ini ke blok percobaan. Blok try kemudian menggunakan if / else untuk melewati blok kode ke blok catch yang memeriksa variabel lokal untuk melihat apakah ia harus menangkap.

Contoh itu diperluas lagi:

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;
}

Ini menggunakan penunjuk global sehingga longjmp () tahu percobaan apa yang terakhir dijalankan. Kami menggunakan penyalahgunaan tumpukan sehingga fungsi anak juga dapat memiliki blok coba / tangkap.

Menggunakan kode ini memiliki sejumlah sisi buruk (tetapi merupakan latihan mental yang menyenangkan):

  • Ini tidak akan membebaskan memori yang dialokasikan karena tidak ada dekonstruksi yang dipanggil.
  • Anda tidak boleh memiliki lebih dari 1 coba / tangkap dalam satu lingkup (tidak ada bersarang)
  • Anda tidak dapat benar-benar membuang pengecualian atau data lain seperti di C ++
  • Tidak aman untuk benang sama sekali
  • Anda sedang menyiapkan pemrogram lain untuk kegagalan karena mereka kemungkinan besar tidak akan memperhatikan peretasan dan mencoba menggunakannya seperti blok coba / tangkap C ++.
Paul Hutchinson
sumber
solusi alternatif yang bagus.
HaseeB Mir
versi 1 adalah ide bagus, tetapi variabel __HadError itu perlu disetel ulang atau dicakup. Jika tidak, Anda tidak akan dapat menggunakan lebih dari satu coba-tangkap di blok yang sama. Mungkin menggunakan fungsi global seperti bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. Tetapi variabel lokal juga akan didefinisikan ulang, jadi hal-hal menjadi sedikit di luar kendali.
flamewave000
Ya, ini terbatas pada satu uji coba dalam fungsi yang sama. Masalah yang lebih besar maka variabel bagaimanapun adalah label karena Anda tidak dapat memiliki label duplikat dalam fungsi yang sama.
Paul Hutchinson
10

Di C99, Anda dapat menggunakan setjmp/ longjmpuntuk aliran kontrol non-lokal.

Dalam satu cakupan, pola pengkodean terstruktur dan generik untuk C dengan adanya beberapa alokasi sumber daya dan beberapa penggunaan keluar goto, seperti dalam contoh ini . Ini mirip dengan bagaimana C ++ mengimplementasikan panggilan destruktor dari objek otomatis di bawah tenda, dan jika Anda tetap berpegang pada ini dengan rajin, ini akan memungkinkan Anda untuk tingkat kebersihan tertentu bahkan dalam fungsi yang kompleks.

Kerrek SB
sumber
5

Sementara beberapa jawaban lain telah mencakup kasus sederhana menggunakan setjmpdan longjmp, dalam aplikasi nyata ada dua masalah yang sangat penting.

  1. Bersarang dari blok coba / tangkap. Menggunakan satu variabel global untuk Anda jmp_bufakan membuat ini tidak berfungsi.
  2. Threading. Satu variabel global untuk Anda jmp_bufakan menyebabkan semua jenis rasa sakit dalam situasi ini.

Solusi untuk ini adalah mempertahankan tumpukan thread-lokal jmp_bufyang diperbarui saat Anda pergi. (Saya pikir inilah yang digunakan lua secara internal).

Jadi alih-alih ini (dari jawaban JaredPar yang luar biasa)

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);
}

Anda akan menggunakan sesuatu seperti:

#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);
}

Sekali lagi versi yang lebih realistis dari ini akan menyertakan beberapa cara untuk menyimpan informasi kesalahan ke dalam exception_state, penanganan yang lebih baikMAX_EXCEPTION_DEPTH (mungkin menggunakan realoc untuk mengembangkan buffer, atau semacamnya).

PENAFIAN: Kode di atas ditulis tanpa pengujian apa pun. Ini murni agar Anda mendapatkan ide tentang bagaimana menyusun sesuatu. Sistem yang berbeda dan kompiler yang berbeda perlu menerapkan penyimpanan lokal thread secara berbeda. Kode tersebut mungkin berisi kesalahan kompilasi dan kesalahan logika - jadi meskipun Anda bebas menggunakannya sesuka Anda, UJI sebelum menggunakannya;)

Michael Anderson
sumber
4

Pencarian google cepat menghasilkan solusi kludgey seperti ini yang menggunakan setjmp / longjmp seperti yang disebutkan orang lain. Tidak ada yang sesederhana dan seanggun C ++ / Java's try / catch. Saya agak lebih memilih pengecualian Ada menangani diri saya sendiri.

Periksa semuanya dengan pernyataan if :)

James Adam
sumber
4

Hal ini dapat dilakukan dengan setjmp/longjmpC. P99 memiliki toolset yang cukup nyaman untuk ini yang juga konsisten dengan model thread baru C11.

Jens Gustedt
sumber
2

Ini adalah cara lain untuk melakukan penanganan error di C yang lebih berkinerja daripada menggunakan setjmp / longjmp. Sayangnya, ini tidak akan bekerja dengan MSVC tetapi jika hanya menggunakan GCC / Clang adalah sebuah opsi, maka Anda dapat mempertimbangkannya. Secara khusus, ini menggunakan ekstensi "label sebagai nilai", yang memungkinkan Anda mengambil alamat label, menyimpannya dalam nilai, dan melompat ke sana tanpa syarat. Saya akan menyajikannya menggunakan contoh:

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;
}

Jika Anda mau, Anda dapat memfaktorkan ulang kode umum di definisikan, yang secara efektif menerapkan sistem penanganan kesalahan Anda sendiri.

/* 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); }

Kemudian contohnya menjadi

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;
}
keebus
sumber
2

Peringatan: berikut ini tidak terlalu bagus tetapi berhasil.

#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)

Pemakaian:

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);
})

Keluaran:

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 

Perlu diingat bahwa ini menggunakan fungsi bertingkat dan __COUNTER__. Anda akan berada di sisi yang aman jika Anda menggunakan gcc.

Naheel
sumber
1

Redis menggunakan goto untuk mensimulasikan coba / menangkap, IMHO sangat bersih dan elegan:

/* 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;
}
Forrest Ye
sumber
Kode rusak. errnohanya boleh digunakan setelah panggilan sistem gagal dan bukan tiga panggilan kemudian.
ceving
Kode ini menduplikasi logika penanganan kesalahan di banyak tempat dan mungkin melakukan hal yang salah seperti memanggil fclose (fp) beberapa kali. Akan jauh lebih baik menggunakan beberapa label dan menyandikan apa yang masih perlu diklaim ulang menggunakan label tersebut (daripada hanya satu untuk semua kesalahan) dan kemudian melompat ke tempat penanganan kesalahan yang benar tergantung di mana dalam kode kesalahan terjadi.
jschultz410
1

Di C, Anda dapat "mensimulasikan" pengecualian bersama dengan "reklamasi objek" otomatis melalui penggunaan manual if + goto untuk penanganan error eksplisit.

Saya sering menulis kode C seperti berikut (diringkas untuk menyoroti penanganan kesalahan):

#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;        
}

Ini sepenuhnya ANSI C standar, memisahkan penanganan kesalahan dari kode jalur utama Anda, memungkinkan pelepasan tumpukan (manual) objek yang diinisialisasi seperti yang dilakukan C ++, dan sangat jelas apa yang terjadi di sini. Karena Anda secara eksplisit menguji kegagalan di setiap titik, itu membuatnya lebih mudah untuk memasukkan logging tertentu atau penanganan kesalahan di setiap tempat kesalahan dapat terjadi.

Jika Anda tidak keberatan dengan sedikit keajaiban makro, maka Anda dapat membuatnya lebih ringkas sambil melakukan hal-hal lain seperti mencatat kesalahan dengan jejak tumpukan. Sebagai contoh:

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

#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, 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;        
}

Tentu saja, ini tidak seanggun C ++ exception + destructors. Misalnya, menumpuk beberapa tumpukan penanganan kesalahan dalam satu fungsi dengan cara ini tidak terlalu bersih. Sebaliknya, Anda mungkin ingin memecahnya menjadi sub fungsi mandiri yang menangani kesalahan dengan cara yang sama, menginisialisasi + menyelesaikan secara eksplisit seperti ini.

Ini juga hanya berfungsi dalam satu fungsi dan tidak akan terus meningkatkan tumpukan kecuali pemanggil tingkat yang lebih tinggi menerapkan logika penanganan kesalahan eksplisit yang serupa, sedangkan pengecualian C ++ hanya akan terus meningkatkan tumpukan hingga menemukan penangan yang sesuai. Itu juga tidak memungkinkan Anda untuk melempar tipe arbitrer, tetapi hanya kode kesalahan.

Pengodean secara sistematis dengan cara ini (yaitu - dengan satu entri dan satu titik keluar) juga membuatnya sangat mudah untuk memasukkan logika pra dan posting ("akhirnya") yang akan dijalankan apa pun yang terjadi. Anda hanya menempatkan logika "akhirnya" Anda setelah label AKHIR.

jschultz410.dll
sumber
1
Sangat bagus. Saya cenderung melakukan hal serupa. goto sangat bagus untuk skenario ini. Satu-satunya perbedaan adalah saya tidak melihat perlunya "goto END" yang terakhir, saya hanya memasukkan pengembalian sukses pada saat itu, pengembalian gagal setelah yang lain.
Neil Roy
1
Terima kasih @NeilRoy Alasan untuk goto END adalah saya suka sebagian besar fungsi saya memiliki satu titik masuk dan satu titik keluar. Dengan cara itu jika saya ingin menambahkan beberapa logika "akhirnya" ke fungsi apa pun, saya selalu dapat dengan mudah tanpa perlu khawatir ada beberapa pengembalian tersembunyi lainnya yang bersembunyi di suatu tempat. :)
jschultz410
0

Jika Anda menggunakan C dengan Win32, Anda dapat memanfaatkan Structured Exception Handling (SEH) untuk mensimulasikan coba / tangkap.

Jika Anda menggunakan C di platform yang tidak mendukung setjmp()dan longjmp(), lihat Penanganan Pengecualian pjsip library ini, ia menyediakan implementasinya sendiri

onmyway133
sumber
-1

Mungkin bukan bahasa utama (sayangnya), tetapi di APL, ada operasi ⎕EA (singkatan dari Execute Alternate).

Penggunaan: 'Y' ⎕EA 'X' di mana X dan Y adalah cuplikan kode yang diberikan sebagai string atau nama fungsi.

Jika X mengalami kesalahan, Y (biasanya penanganan kesalahan) akan dijalankan.

mappo
sumber
2
Halo mappo, selamat datang di StackOverflow. Meskipun menarik, pertanyaannya secara khusus tentang melakukan ini di C. Jadi ini tidak benar-benar menjawab pertanyaan itu.
luser droog