ลองจับข้อความใน C


104

วันนี้ฉันคิดเกี่ยวกับบล็อก try / catch ที่มีอยู่ในภาษาอื่น Googled ในขณะนี้ แต่ไม่มีผลลัพธ์ จากสิ่งที่ฉันรู้ไม่มีสิ่งที่เหมือนกับ try / catch ใน C อย่างไรก็ตามมีวิธี "จำลอง" หรือไม่?
แน่นอนว่ามีการยืนยันและเทคนิคอื่น ๆ แต่ไม่มีอะไรเหมือนกับการลอง / จับที่จับข้อยกเว้นที่เพิ่มขึ้น ขอบคุณ


4
กลไกที่เหมือนข้อยกเว้นจะไม่เป็นประโยชน์โดยทั่วไปหากไม่มีกลไกในการปลดปล่อยทรัพยากรโดยอัตโนมัติเมื่อสแต็กถูกคลายออก C ++ ใช้ RAII; Java, C #, Python ฯลฯ ใช้ตัวเก็บขยะ (และโปรดทราบว่าผู้รวบรวมขยะจะทำให้หน่วยความจำว่างเพียงอย่างเดียวหากต้องการเพิ่มทรัพยากรประเภทอื่นโดยอัตโนมัติพวกเขายังเพิ่มสิ่งต่างๆเช่นโปรแกรมสุดท้ายหรือผู้จัดการบริบท ... )
jamesdlin

@jamesdlin ทำไมเราถึงทำ RAII กับ C ไม่ได้?
Pacerier

1
@Pacerier RAII ต้องการฟังก์ชั่นการเรียกโดยอัตโนมัติเมื่อวัตถุถูกทำลาย (เช่นตัวทำลาย) คุณเสนอให้ทำใน C ได้อย่างไร?
jamesdlin

คำตอบ:


92

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

เว็บไซต์นี้มีคำแนะนำที่ดีเกี่ยวกับวิธีการจำลองข้อยกเว้นด้วยsetjmpและlongjmp


1
ทางออกที่ยอดเยี่ยม! การแก้ปัญหานี้ข้ามหรือไม่ มันใช้ได้กับฉันใน MSVC2012 แต่ไม่ได้อยู่ในคอมไพเลอร์ MacOSX Clang
mannysz

1
เบาะแสฉัน: ฉันคิดว่าการลองจับประโยคช่วยให้คุณจับข้อยกเว้นได้ (เช่นหารด้วยศูนย์) ฟังก์ชั่นนี้ดูเหมือนจะอนุญาตให้คุณจับเฉพาะข้อยกเว้นที่คุณโยนเองเท่านั้น ข้อยกเว้นจริงไม่ได้โยนโดยเรียก longjmp ใช่ไหม? ถ้าฉันใช้รหัสนี้เพื่อทำบางสิ่งบางอย่างtry{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; มันจะไม่ทำงานใช่ไหม
แซม

การเบี่ยงเบนด้วยศูนย์ไม่ใช่ข้อยกเว้นใน C ++ เพื่อจัดการกับมันคุณต้องตรวจสอบว่าตัวหารไม่ใช่ศูนย์และจัดการหรือจัดการ SIGFPE ที่ถูกโยนทิ้งเมื่อคุณเรียกใช้สูตรที่เบี่ยงเบนโดยศูนย์
เจมส์

25

คุณใช้gotoใน C สำหรับสถานการณ์การจัดการข้อผิดพลาดที่คล้ายกัน
นั่นเท่ากับข้อยกเว้นที่ใกล้เคียงที่สุดที่คุณจะได้รับใน C


3
@JensGustedt นี่คือสิ่งที่ goto ใช้บ่อยมากในปัจจุบันและเป็นตัวอย่างที่เหมาะสม (setjmp / ljmp เป็นทางเลือกที่ดีกว่า แต่โดยทั่วไป label + goto จะใช้มากกว่า)
Tomas Pruzina

1
@AoeAoe น่าจะgotoถูกใช้มากกว่าสำหรับการจัดการข้อผิดพลาด แต่แล้วไงล่ะ? คำถามไม่ได้เกี่ยวกับการจัดการข้อผิดพลาด แต่อย่างชัดเจนเกี่ยวกับ try / catch equivalents gotoไม่เทียบเท่าลอง / จับเนื่องจากถูก จำกัด ไว้ที่ฟังก์ชั่นเดียวกัน
Jens Gustedt

@JensGustedt ฉันตอบสนองต่อความเกลียดชัง / ความกลัวของ goto และผู้คนที่ใช้มัน (อาจารย์ของฉันเล่าเรื่องที่น่ากลัวเกี่ยวกับการใช้ goto ในมหาวิทยาลัยด้วย) [OT] มีเพียงสิ่งเดียวที่มีความเสี่ยงจริงๆและสิ่งที่ 'ขุ่นมัว' เกี่ยวกับ goto คือการ 'ถอยหลัง' แต่ฉันเคยเห็นสิ่งนั้นใน Linux VFS (git ตำหนิผู้ชายสาบานว่ามันเป็นประสิทธิภาพที่สำคัญและเป็นประโยชน์)
Tomas Pruzina

ดูแหล่งที่มาของ systemctlสำหรับการใช้gotoกลไก try / catch ที่ถูกต้องซึ่งใช้ในแหล่งที่มาที่ผ่านการตรวจสอบโดยเพื่อนที่ทันสมัยและได้รับการยอมรับอย่างกว้างขวาง ค้นหาgotoค่าเทียบเท่า "throw" และfinishสำหรับ "catch" ที่เทียบเท่า
Stewart

14

โอเคฉันอดไม่ได้ที่จะตอบกลับสิ่งนี้ ก่อนอื่นฉันขอบอกว่าฉันไม่คิดว่าการจำลองสิ่งนี้ใน 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 หากการลองเป็นสิ่งแรกในฟังก์ชัน)

ฟังก์ชั่นนี้สร้างเฉพาะ var เพื่อให้รู้ว่ามีข้อผิดพลาดหรือไม่และใช้ 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 มีความซับซ้อนกว่ามาก แต่โดยพื้นฐานแล้วจะทำงานในลักษณะเดียวกัน ใช้การข้ามจากฟังก์ชันปัจจุบันไปยังบล็อกลอง จากนั้นลองบล็อกใช้ 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 ได้

การใช้รหัสนี้มีข้อเสียอยู่หลายประการ (แต่เป็นการฝึกจิตที่สนุกสนาน):

  • หน่วยความจำที่จัดสรรจะไม่เป็นอิสระเนื่องจากไม่มีการเรียกตัวถอดรหัส
  • คุณสามารถลอง / จับในขอบเขตได้ไม่เกิน 1 ครั้ง (ไม่มีการซ้อน)
  • คุณไม่สามารถโยนข้อยกเว้นหรือข้อมูลอื่น ๆ เช่นใน C ++ ได้
  • ไม่ปลอดภัยเลย
  • คุณกำลังตั้งค่าโปรแกรมเมอร์คนอื่นสำหรับความล้มเหลวเนื่องจากอาจไม่สังเกตเห็นการแฮ็กและลองใช้เช่น C ++ try / catch blocks

โซลูชันทางเลือกที่ดี
HaseeB Mir

เวอร์ชัน 1 เป็นความคิดที่ดี แต่ตัวแปร __HadError นั้นจะต้องถูกรีเซ็ตหรือกำหนดขอบเขต มิฉะนั้นคุณจะไม่สามารถใช้ลองจับมากกว่าหนึ่งครั้งในบล็อกเดียวกันได้ อาจใช้ฟังก์ชันระดับโลกเช่นbool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. แต่ตัวแปรท้องถิ่นก็จะได้รับการกำหนดใหม่ด้วยดังนั้นสิ่งต่าง ๆ จึงอยู่ในมือเล็กน้อย
flamewave000

ใช่ จำกัด การลองจับหนึ่งครั้งในฟังก์ชันเดียวกัน ปัญหาที่ใหญ่กว่านั้นตัวแปรคือป้ายกำกับเนื่องจากคุณไม่สามารถมีป้ายกำกับซ้ำในฟังก์ชันเดียวกันได้
Paul Hutchinson

10

ใน C99คุณสามารถใช้setjmp/ longjmpสำหรับโฟลว์การควบคุมที่ไม่ใช่โลคัล

ภายในขอบเขตเดียวรูปแบบการเข้ารหัสที่มีโครงสร้างทั่วไปสำหรับ C ต่อหน้าการจัดสรรทรัพยากรหลายรายการและการออกหลายรายการgotoเช่นในตัวอย่างนี้ สิ่งนี้คล้ายกับวิธีที่ C ++ ใช้การเรียกตัวทำลายของวัตถุอัตโนมัติภายใต้ประทุนและหากคุณยึดติดกับสิ่งนี้อย่างขยันขันแข็งมันจะช่วยให้คุณมีความสะอาดในระดับหนึ่งแม้ในฟังก์ชันที่ซับซ้อน


5

ในขณะที่คำตอบอื่น ๆ บางส่วนได้กล่าวถึงกรณีง่ายๆที่ใช้setjmpและlongjmpในแอปพลิเคชันจริงมีข้อกังวลสองประการที่สำคัญจริงๆ

  1. การซ้อนบล็อกลอง / จับ การใช้ตัวแปรส่วนกลางเพียงตัวแปรเดียว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 เพื่อขยายบัฟเฟอร์หรืออะไรทำนองนั้น)

การปฏิเสธความรับผิด: โค้ดด้านบนเขียนขึ้นโดยไม่มีการทดสอบใด ๆ มันเป็นเพียงความคิดที่จะจัดโครงสร้างสิ่งต่างๆ ระบบที่แตกต่างกันและคอมไพเลอร์ที่แตกต่างกันจะต้องใช้ที่เก็บเธรดโลคัลแตกต่างกัน รหัสอาจมีทั้งข้อผิดพลาดในการคอมไพล์และข้อผิดพลาดทางตรรกะดังนั้นในขณะที่คุณใช้งานได้ฟรีตามที่คุณเลือกโปรดทดสอบก่อนใช้งาน;)


4

การค้นหาโดย Google อย่างรวดเร็วทำให้เกิดโซลูชันที่ไม่น่าเชื่อถือเช่นนี้ที่ใช้ setjmp / longjmp ตามที่คนอื่นกล่าวถึง ไม่มีอะไรตรงไปตรงมาและสวยงามเท่ากับการลอง / จับของ C ++ / Java ฉันค่อนข้างบางส่วนกับข้อยกเว้นของ Ada ที่จัดการตัวเอง

ตรวจสอบทุกอย่างด้วย if statement :)


4

สิ่งนี้สามารถทำได้โดยsetjmp/longjmpใน C. P99มีชุดเครื่องมือที่ค่อนข้างสะดวกสำหรับสิ่งนี้ซึ่งสอดคล้องกับรูปแบบเธรดใหม่ของ C11


2

นี่เป็นอีกวิธีหนึ่งในการจัดการข้อผิดพลาดใน 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;
}

หากคุณต้องการคุณสามารถ refactor รหัสทั่วไปเพื่อกำหนดโดยใช้ระบบจัดการข้อผิดพลาดของคุณเองอย่างมีประสิทธิภาพ

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

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


1

Redis ใช้ goto เพื่อจำลองลอง / จับ IMHO มันสะอาดและสง่างามมาก:

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

โค้ดเสีย errnoจะต้องใช้หลังจากการเรียกระบบล้มเหลวเท่านั้นและไม่ใช่การโทรสามครั้งในภายหลัง
สิ้นสุด

รหัสนี้ซ้ำกับตรรกะการจัดการข้อผิดพลาดในหลาย ๆ ที่และอาจทำสิ่งที่ไม่ถูกต้องเช่นการเรียก fclose (fp) หลายครั้ง ควรใช้ป้ายกำกับหลายป้ายและเข้ารหัสสิ่งที่ยังต้องเรียกคืนโดยใช้ป้ายกำกับเหล่านั้น (แทนที่จะใช้เพียงป้ายเดียวสำหรับข้อผิดพลาดทั้งหมด) จากนั้นข้ามไปยังจุดจัดการข้อผิดพลาดที่ถูกต้องโดยขึ้นอยู่กับว่าข้อผิดพลาดเกิดขึ้นที่ใดในโค้ด
jschultz410

1

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

นี่เป็นมาตรฐาน ANSI C โดยสิ้นเชิงแยกการจัดการข้อผิดพลาดออกจากรหัสเมนไลน์ของคุณอนุญาตให้สแต็ก (ด้วยตนเอง) คลี่คลายวัตถุเริ่มต้นได้เหมือนกับที่ C ++ ทำและเห็นได้ชัดว่าเกิดอะไรขึ้นที่นี่ เนื่องจากคุณกำลังทดสอบความล้มเหลวอย่างชัดเจนในแต่ละจุดจึงทำให้ง่ายต่อการแทรกการบันทึกหรือการจัดการข้อผิดพลาดในแต่ละที่จึงอาจเกิดข้อผิดพลาดได้

หากคุณไม่สนใจเวทมนตร์มาโครเล็กน้อยคุณสามารถทำให้สิ่งนี้กระชับมากขึ้นในขณะที่ทำสิ่งอื่น ๆ เช่นการบันทึกข้อผิดพลาดด้วยสแต็กเทรซ ตัวอย่างเช่น:

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

แน่นอนว่านี่ไม่ได้หรูหราเท่ากับข้อยกเว้น C ++ + ตัวทำลาย ตัวอย่างเช่นการซ้อนสแต็กการจัดการข้อผิดพลาดหลายรายการภายในฟังก์ชันเดียววิธีนี้ไม่สะอาดนัก แต่คุณอาจต้องการแยกสิ่งเหล่านั้นออกเป็นฟังก์ชันย่อยที่มีอยู่ในตัวซึ่งจัดการข้อผิดพลาดในทำนองเดียวกันเริ่มต้น + สรุปอย่างชัดเจนเช่นนี้

นอกจากนี้ยังใช้งานได้ภายในฟังก์ชั่นเดียวและจะไม่กระโดดขึ้นสแต็กเว้นแต่ผู้โทรในระดับที่สูงกว่าจะใช้ตรรกะการจัดการข้อผิดพลาดอย่างชัดเจนที่คล้ายกันในขณะที่ข้อยกเว้น C ++ จะกระโดดขึ้นไปเรื่อย ๆ จนกว่าจะพบตัวจัดการที่เหมาะสม และไม่อนุญาตให้คุณโยนประเภทโดยพลการ แต่เป็นเพียงรหัสข้อผิดพลาดแทน

การเข้ารหัสด้วยวิธีนี้อย่างเป็นระบบ (เช่น - ด้วยการเข้าเพียงครั้งเดียวและจุดทางออกเดียว) ทำให้ง่ายมากในการแทรกตรรกะก่อนและหลัง ("ในที่สุด") ซึ่งจะดำเนินการไม่ว่าจะเกิดอะไรขึ้นก็ตาม คุณแค่ใส่ตรรกะ "สุดท้าย" ไว้หลังป้ายกำกับ END


1
ดีมาก. ฉันมักจะทำอะไรคล้าย ๆ กัน goto เหมาะสำหรับสถานการณ์นี้ ข้อแตกต่างเพียงอย่างเดียวคือฉันไม่เห็นความจำเป็นสำหรับ "goto END" ครั้งสุดท้ายฉันแค่ใส่ผลตอบแทนความสำเร็จ ณ จุดนั้นผลตอบแทนที่ล้มเหลวหลังจากที่เหลือ
Neil Roy

1
ขอบคุณ @NeilRoy เหตุผลที่ goto END คือฉันชอบฟังก์ชั่นส่วนใหญ่ของฉันที่มีจุดเข้าเดียวและจุดทางออกเดียว ด้วยวิธีนี้หากฉันต้องการเพิ่มลอจิก "สุดท้าย" ให้กับฟังก์ชันใด ๆ ที่ฉันทำได้อย่างง่ายดายโดยไม่ต้องกังวลว่าจะมีผลตอบแทนอื่น ๆ ที่ซ่อนอยู่ซ่อนอยู่ที่ไหนสักแห่ง :)
jschultz410

0

หากคุณใช้ C กับ Win32 คุณสามารถใช้ประโยชน์จากStructured Exception Handling (SEH)เพื่อจำลอง try / catch

หากคุณใช้ C ในแพลตฟอร์มที่ไม่รองรับsetjmp()และlongjmp()ดูที่การจัดการข้อยกเว้นของไลบรารี pjsip นี้จะมีการใช้งานของตัวเอง


-1

อาจไม่ใช่ภาษาหลัก (น่าเสียดาย) แต่ใน APL มีการดำเนินการ⎕EA (ย่อมาจาก Execute Alternate)

การใช้งาน: 'Y' ⎕EA 'X' โดยที่ X และ Y เป็นข้อมูลโค้ดที่ให้มาเป็นสตริงหรือชื่อฟังก์ชัน

หาก X เกิดข้อผิดพลาด Y (โดยปกติจะจัดการข้อผิดพลาด) จะถูกดำเนินการแทน


2
สวัสดี mappo ยินดีต้อนรับสู่ StackOverflow ในขณะที่น่าสนใจคำถามคือเฉพาะเกี่ยวกับการทำสิ่งนี้ใน C. ดังนั้นนี่จึงไม่ตอบคำถามจริงๆ
luser droog
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.