Hãy thử câu lệnh bắt trong C


101

Hôm nay tôi đã nghĩ về các khối try / catch tồn tại trong các ngôn ngữ khác. Googled trong một thời gian này nhưng không có kết quả. Từ những gì tôi biết, không có thứ gọi là try / catch trong C. Tuy nhiên, có cách nào để "mô phỏng" chúng không?
Chắc chắn, có khẳng định và các thủ thuật khác nhưng không có gì giống như thử / bắt, cũng bắt được ngoại lệ được nêu ra. Cảm ơn bạn


3
Các cơ chế giống như ngoại lệ nói chung sẽ không hữu ích nếu không có cơ chế tự động giải phóng tài nguyên khi ngăn xếp chưa được liên kết. C ++ sử dụng RAII; Java, C #, Python, v.v. sử dụng trình thu gom rác. (Và lưu ý rằng trình thu gom rác chỉ giải phóng bộ nhớ. Để tự động giải phóng các loại tài nguyên khác, họ cũng thêm những thứ như trình hoàn thiện hoặc trình quản lý ngữ cảnh ...)
jamesdlin

@jamesdlin, Tại sao chúng ta không thể thực hiện RAII với C?
Pacerier

1
@Pacerier RAII yêu cầu các hàm tự động gọi khi các đối tượng bị hủy (tức là hàm hủy). Làm thế nào để bạn đề xuất làm điều đó trong C?
jamesdlin

Câu trả lời:


90

Bản thân C không hỗ trợ các ngoại lệ nhưng bạn có thể mô phỏng chúng ở một mức độ với setjmplongjmpcác cuộc gọi.

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

Trang web này có một hướng dẫn hay về cách mô phỏng các ngoại lệ với setjmplongjmp


1
giải pháp tuyệt vời! là giải pháp này chéo? Nó hoạt động với tôi trên MSVC2012 nhưng không hoạt động trong trình biên dịch MacOSX Clang.
mannysz

1
gợi ý cho tôi: Tôi nghĩ rằng mệnh đề try catch cho phép bạn bắt các ngoại lệ (như chia cho số không). Chức năng này dường như chỉ cho phép bạn bắt các trường hợp ngoại lệ mà bạn tự đặt ra. Các ngoại lệ thực sự không được ném ra bằng cách gọi longjmp phải không? Nếu tôi sử dụng mã này để làm điều gì đó giống như try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; nó sẽ không hoạt động đúng không?
Sam

Devide by zero thậm chí không phải là một ngoại lệ trong C ++, để xử lý nó, bạn cần phải kiểm tra số chia có phải là 0 và xử lý nó hoặc xử lý SIGFPE được ném ra khi bạn chạy công thức devide by zero.
James

25

Bạn sử dụng goto trong C cho các tình huống xử lý lỗi tương tự.
Đó là trường hợp ngoại lệ tương đương gần nhất mà bạn có thể nhận được trong C.


3
@JensGustedt Đây chính xác là những gì goto hiện đang được sử dụng rất thường xuyên và ví dụ về nơi nó có ý nghĩa (setjmp / ljmp là lựa chọn thay thế tốt hơn, nhưng nhãn + goto thường được sử dụng nhiều hơn).
Tomas Pruzina

1
@AoeAoe, có lẽ gotođược sử dụng nhiều hơn để xử lý lỗi, nhưng vậy thì sao? Câu hỏi không phải là về việc xử lý lỗi như vậy mà là về các tương đương try / catch. gotokhông phải là một thử / bắt vì nó bị hạn chế ở cùng một chức năng.
Jens Gustedt

@JensGustedt Tôi đã phản ứng trước việc ghét / sợ goto và những người sử dụng nó (các giáo viên của tôi cũng kể cho tôi nghe những câu chuyện đáng sợ về việc sử dụng goto ở trường đại học). [OT] Chỉ có một điều thực sự, thực sự mạo hiểm và 'vẩn đục' về goto là 'goto ngược', nhưng tôi đã thấy điều đó trong Linux VFS (anh chàng trách móc đã thề rằng nó mang lại hiệu suất rất có lợi).
Tomas Pruzina

Xem các nguồn systemctl để biết cách sử dụng hợp pháp gotonhư một cơ chế thử / bắt được sử dụng trong một nguồn hiện đại, được chấp nhận rộng rãi, được đánh giá ngang hàng. Tìm kiếm gototương đương "ném" và tương đương finish"bắt".
Stewart

13

Ok, tôi không thể cưỡng lại việc trả lời điều này. Đầu tiên tôi xin nói rằng tôi không nghĩ là một ý kiến ​​hay nếu mô phỏng điều này trong C vì nó thực sự là một khái niệm xa lạ với C.

Chúng ta có thể sử dụng lạm dụng tiền xử lý và địa phương stack biến để cung cấp cho sử dụng một phiên bản hạn chế của C ++ try / ném / catch.

Phiên bản 1 (ném phạm vi cục bộ)

#include <stdbool.h>

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

Phiên bản 1 chỉ là một phiên bản cục bộ (không thể rời khỏi phạm vi của chức năng). Nó dựa vào khả năng khai báo các biến trong mã của C99 (nó sẽ hoạt động trong C89 nếu lần thử là điều đầu tiên trong hàm).

Hàm này chỉ tạo một var cục bộ để nó biết nếu có lỗi và sử dụng goto để chuyển đến khối bắt.

Ví dụ:

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

Điều này có hiệu quả như:

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

Phiên bản 2 (nhảy phạm vi)

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

Phiên bản 2 phức tạp hơn rất nhiều nhưng về cơ bản hoạt động theo cùng một cách. Nó sử dụng một bước nhảy dài ra khỏi chức năng hiện tại đến khối thử. Sau đó, khối try sử dụng if / else để bỏ qua khối mã đến khối catch, khối này sẽ kiểm tra biến cục bộ để xem nó có bắt được không.

Ví dụ lại mở rộng:

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

Điều này sử dụng một con trỏ toàn cục để longjmp () biết lần thử cuối cùng là gì. Chúng tôi đang sử dụng lạm dụng ngăn xếp để các chức năng con cũng có thể có một khối try / catch.

Sử dụng mã này có một số mặt trái (nhưng là một bài tập tinh thần thú vị):

  • Nó sẽ không giải phóng bộ nhớ được cấp phát vì không có bộ giải cấu trúc nào được gọi.
  • Bạn không thể có nhiều hơn 1 lần thử / bắt trong một phạm vi (không lồng nhau)
  • Bạn thực sự không thể ném các ngoại lệ hoặc dữ liệu khác như trong C ++
  • Không an toàn chút nào
  • Bạn đang thiết lập các lập trình viên khác thất bại vì họ có thể sẽ không nhận thấy bị hack và thử sử dụng chúng như khối try / catch trong C ++.

các giải pháp thay thế tốt đẹp.
HaseeB Mir

phiên bản 1 là một ý tưởng hay, nhưng biến __HadError đó sẽ cần được đặt lại hoặc mở rộng phạm vi. Nếu không, bạn sẽ không thể sử dụng nhiều hơn một lần thử bắt trong cùng một khối. Có thể sử dụng một chức năng toàn cục như thế nào bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. Nhưng biến cục bộ cũng sẽ được định nghĩa lại, vì vậy mọi thứ trở nên khó khăn hơn một chút.
flamewave000

Có, nó được giới hạn cho một lần thử bắt trong cùng một chức năng. Tuy nhiên, một vấn đề lớn hơn đối với biến là nhãn vì bạn không thể có các nhãn trùng lặp trong cùng một hàm.
Paul Hutchinson

10

Trong C99, bạn có thể sử dụng setjmp/ longjmpcho luồng điều khiển không cục bộ.

Trong một phạm vi duy nhất, mẫu mã chung, có cấu trúc cho C khi có nhiều phân bổ tài nguyên và sử dụng nhiều lần thoát goto, như trong ví dụ này . Điều này tương tự như cách C ++ thực hiện các lệnh gọi hàm hủy của các đối tượng tự động bên dưới, và nếu bạn chăm chỉ thực hiện điều này, nó sẽ cho phép bạn rõ ràng nhất định ngay cả trong các hàm phức tạp.


5

Trong khi một số câu trả lời khác đã đề cập đến các trường hợp đơn giản sử dụng setjmplongjmp, trong một ứng dụng thực tế, có hai mối quan tâm thực sự quan trọng.

  1. Lồng các khối thử / bắt. Sử dụng một biến toàn cục duy nhất cho của bạn jmp_bufsẽ làm cho những biến này không hoạt động.
  2. Phân luồng. Một biến toàn cục duy nhất đối với bạn jmp_bufsẽ gây ra tất cả các loại đau đớn trong tình huống này.

Giải pháp cho những điều này là duy trì một ngăn xếp luồng cục bộ jmp_bufđược cập nhật khi bạn di chuyển. (Tôi nghĩ đây là những gì lua sử dụng nội bộ).

Vì vậy, thay vì điều này (từ câu trả lời tuyệt vời của 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);
}

Bạn sẽ sử dụng một cái gì đó như:

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

Một lần nữa, một phiên bản thực tế hơn của điều này sẽ bao gồm một số cách để lưu trữ thông tin lỗi vào exception_state, xử lý tốt hơn MAX_EXCEPTION_DEPTH(có thể sử dụng realloc để phát triển bộ đệm hoặc một cái gì đó tương tự).

KHUYẾN CÁO: Đoạn mã trên được viết mà không có bất kỳ thử nghiệm nào. Nó hoàn toàn là để bạn có được ý tưởng về cách cấu trúc mọi thứ. Các hệ thống khác nhau và các trình biên dịch khác nhau sẽ cần triển khai lưu trữ cục bộ luồng khác nhau. Mã có thể chứa cả lỗi biên dịch và lỗi logic - vì vậy trong khi bạn có thể sử dụng nó tùy ý, hãy KIỂM TRA mã này trước khi sử dụng;)


4

Một cách nhanh chóng sản lượng tìm kiếm google kludgey giải pháp như này rằng việc sử dụng setjmp / longjmp như những người khác đã đề cập. Không gì đơn giản và thanh lịch bằng thử / bắt của C ++ / Java. Tôi khá thích thú với việc tự xử lý ngoại lệ của Ada.

Kiểm tra mọi thứ bằng câu lệnh if :)


4

Điều này có thể được thực hiện với setjmp/longjmpC. P99 có một bộ công cụ khá thoải mái cho việc này cũng phù hợp với mô hình luồng mới của C11.


2

Đây là một cách khác để thực hiện xử lý lỗi trong C hiệu quả hơn so với sử dụng setjmp / longjmp. Thật không may, nó sẽ không hoạt động với MSVC nhưng nếu chỉ sử dụng GCC / Clang là một lựa chọn, thì bạn có thể cân nhắc. Cụ thể, nó sử dụng phần mở rộng "label as value", cho phép bạn lấy địa chỉ của nhãn, lưu trữ nó trong một giá trị và chuyển đến nó vô điều kiện. Tôi sẽ trình bày nó bằng một ví dụ:

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

Nếu muốn, bạn có thể cấu trúc lại mã chung trong các định nghĩa, triển khai hiệu quả hệ thống xử lý lỗi của riêng bạn.

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

Sau đó, ví dụ trở thành

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

2

Cảnh báo: những điều sau đây không hay lắm nhưng nó hoạt động tốt.

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

Sử dụng:

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

Đầu ra:

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 

Hãy nhớ rằng điều này đang sử dụng các hàm lồng nhau và __COUNTER__. Bạn sẽ được an toàn nếu bạn đang sử dụng gcc.


1

Redis sử dụng goto để mô phỏng thử / bắt, IMHO nó rất sạch sẽ và thanh lịch:

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

Mã bị hỏng. errnochỉ được sử dụng ngay sau cuộc gọi hệ thống không thành công và không được sử dụng ba cuộc gọi sau đó.
ngừng

Mã này sao chép logic xử lý lỗi ở nhiều nơi và có thể thực hiện những việc không chính xác như gọi fclose (fp) nhiều lần. Sẽ tốt hơn nhiều nếu sử dụng nhiều nhãn và mã hóa những gì vẫn cần được khôi phục lại bằng các nhãn đó (thay vì chỉ một nhãn cho tất cả các lỗi) và sau đó chuyển sang vị trí xử lý lỗi chính xác tùy thuộc vào vị trí lỗi xảy ra trong mã.
jschultz410 17/07/18

1

Trong C, bạn có thể "mô phỏng" các ngoại lệ cùng với "hoàn nguyên đối tượng" tự động thông qua việc sử dụng thủ công if + goto để xử lý lỗi rõ ràng.

Tôi thường viết mã C như sau (đun sôi để làm nổi bật xử lý lỗi):

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

Đây hoàn toàn là ANSI C tiêu chuẩn, tách biệt việc xử lý lỗi khỏi mã dòng chính của bạn, cho phép tháo cuộn (thủ công) các đối tượng được khởi tạo giống như C ++ và hoàn toàn rõ ràng những gì đang xảy ra ở đây. Bởi vì bạn đang kiểm tra lỗi một cách rõ ràng tại mỗi thời điểm, điều đó giúp bạn dễ dàng chèn nhật ký cụ thể hoặc xử lý lỗi tại mỗi nơi mà lỗi có thể xảy ra.

Nếu bạn không ngại một chút ma thuật macro, thì bạn có thể làm cho điều này ngắn gọn hơn trong khi thực hiện những việc khác như ghi lại lỗi với dấu vết ngăn xếp. Ví dụ:

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

Tất nhiên, điều này không thanh lịch như C ++ ngoại lệ + hủy. Ví dụ: lồng nhiều ngăn xếp xử lý lỗi trong một chức năng theo cách này không thật gọn gàng. Thay vào đó, bạn có thể muốn chia chúng ra thành các hàm phụ có khả năng xử lý lỗi tương tự, khởi tạo + hoàn thiện rõ ràng như thế này.

Điều này cũng chỉ hoạt động trong một hàm duy nhất và sẽ không tiếp tục nhảy lên ngăn xếp trừ khi người gọi cấp cao hơn thực hiện logic xử lý lỗi rõ ràng tương tự, trong khi ngoại lệ C ++ sẽ tiếp tục nhảy lên ngăn xếp cho đến khi nó tìm thấy trình xử lý thích hợp. Nó cũng không cho phép bạn ném một kiểu tùy ý mà thay vào đó chỉ là một mã lỗi.

Mã hóa một cách có hệ thống theo cách này (tức là - với một mục nhập duy nhất và một điểm thoát duy nhất) cũng giúp bạn dễ dàng chèn logic trước và sau ("cuối cùng") sẽ thực thi bất kể điều gì. Bạn chỉ cần đặt logic "cuối cùng" của mình sau nhãn END.


1
Rất đẹp. Tôi có xu hướng làm điều gì đó tương tự. goto là tuyệt vời cho kịch bản này. Sự khác biệt duy nhất là tôi không thấy cần thiết cho "goto END" cuối cùng đó, tôi chỉ chèn một lần trả lại thành công vào thời điểm đó, một lần trả lại thất bại sau phần còn lại.
Neil Roy

1
Cảm ơn @NeilRoy Lý do cho goto END là tôi thích phần lớn các chức năng của mình có một điểm vào duy nhất và một điểm thoát duy nhất. Bằng cách đó nếu tôi muốn thêm một số logic "cuối cùng" vào bất kỳ hàm nào tôi luôn có thể dễ dàng thực hiện mà không cần phải lo lắng có một số trả về ẩn khác đang ẩn nấp ở đâu đó. :)
jschultz410


-1

Có lẽ không phải là một ngôn ngữ chính (không may), nhưng trong APL, có phép toán ⎕EA (viết tắt của Execute Alternate).

Cách sử dụng: 'Y' ⎕EA 'X' trong đó X và Y là các đoạn mã được cung cấp dưới dạng chuỗi hoặc tên hàm.

Nếu X gặp lỗi, Y (thường là xử lý lỗi) sẽ được thực thi thay thế.


2
Chào mappo, chào mừng bạn đến với StackOverflow. Trong khi thú vị, câu hỏi đặc biệt là về việc làm điều này trong C. Vì vậy, điều này không thực sự trả lời câu hỏi.
luser droog
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.