การใช้งาน setjmp และ longjmp ในภาษา C


99

ใครช่วยอธิบายหน่อยได้ไหมว่าที่ไหนsetjmp()และlongjmp()ฟังก์ชันที่สามารถใช้ในการเขียนโปรแกรมแบบฝังได้ ฉันรู้ว่าสิ่งเหล่านี้มีไว้เพื่อจัดการข้อผิดพลาด แต่ฉันต้องการทราบบางกรณีการใช้งาน


สำหรับการจัดการข้อผิดพลาดเช่นเดียวกับการเขียนโปรแกรมอื่น ๆ ไม่เห็นความแตกต่างในการใช้งาน ???
Tony The Lion


เพื่อความรวดเร็ว? ใช่. เนื่องจาก a) มันทำงานช้ากว่าลูปและ b) เนื่องจากไม่สามารถปรับให้เหมาะสมได้ง่าย (เช่นการลบการหน่วงเวลาหรือสองครั้ง) ดังนั้น setjmp & longjmp กฎชัด ๆ !
TheBlastOne

คำตอบที่มากกว่าคำตอบคือที่นี่stackoverflow.com/questions/7334595/… คุณอาจใช้longjmp()เพื่อออกจากตัวจัดการสัญญาณโดยเฉพาะสิ่งต่างๆเช่นไฟล์BUS ERROR. โดยปกติสัญญาณนี้จะไม่สามารถรีสตาร์ทได้ แอปพลิเคชันแบบฝังอาจต้องการจัดการกรณีนี้เพื่อความปลอดภัยและการทำงานที่มีประสิทธิภาพ
ไร้เสียงรบกวน

และเกี่ยวกับความแตกต่างของผลการดำเนินงานsetjmpระหว่าง BSD และ Linux ดู"Timing setjmp และความสุขของมาตรฐาน"sigsetjmpซึ่งแสดงให้เห็นการใช้
Ioannis Filippidis

คำตอบ:


85

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

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

นั่นเป็นสถานการณ์ที่ setjmp / longjmp เหมาะสม สถานการณ์เหล่านี้คล้ายกับสถานการณ์ที่มีข้อยกเว้นในภาษาอื่น ๆ (C ++, Java) เหมาะสม

Coroutines
นอกจากการจัดการข้อผิดพลาดแล้วฉันยังนึกถึงสถานการณ์อื่นที่คุณต้องการ setjmp / longjmp ใน C:

มันเป็นกรณีที่เมื่อคุณจำเป็นต้องใช้coroutines

นี่คือตัวอย่างการสาธิตเล็กน้อย ฉันหวังว่ามันจะเป็นไปตามคำขอจาก Sivaprasad Palas สำหรับโค้ดตัวอย่างและตอบคำถามของ TheBlastOne ว่า setjmp / longjmp รองรับการใช้งาน corroutines ได้อย่างไร (เท่าที่ฉันเห็นว่ามันไม่ได้ขึ้นอยู่กับพฤติกรรมที่ไม่ได้มาตรฐานหรือพฤติกรรมใหม่ ๆ )

แก้ไข:
มันอาจเป็นไปได้ว่ามันจริงเป็นพฤติกรรมที่ไม่ได้กำหนดที่จะทำlongjmp ลง callstack (ดูความคิดเห็นของ MikeMB แม้ว่าผมยังไม่ได้มีโอกาสที่จะตรวจสอบว่า)

#include <stdio.h>
#include <setjmp.h>

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration 

void routineA()
{
    int r ;

    printf("(A1)\n");

    r = setjmp(bufferA);
    if (r == 0) routineB();

    printf("(A2) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20001);

    printf("(A3) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20002);

    printf("(A4) r=%d\n",r);
}

void routineB()
{
    int r;

    printf("(B1)\n");

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10001);

    printf("(B2) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10002);

    printf("(B3) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10003);
}


int main(int argc, char **argv) 
{
    routineA();
    return 0;
}

รูปต่อไปนี้แสดงขั้นตอนการดำเนินการ:
ขั้นตอนการดำเนินการ

หมายเหตุคำเตือน
เมื่อใช้ setjmp / longjmp โปรดทราบว่าตัวแปรเหล่านี้มีผลต่อความถูกต้องของตัวแปรท้องถิ่นซึ่งมักไม่ได้รับการพิจารณา
Cf. คำถามของฉันเกี่ยวกับหัวข้อนี้


2
เนื่องจาก setjmp เตรียมการและ longjmp ดำเนินการกระโดดออกจากขอบเขตการโทรปัจจุบันกลับไปที่ขอบเขต setjmp สิ่งนั้นจะสนับสนุนการใช้โครูทีนอย่างไร ฉันไม่เห็นว่าใครจะดำเนินกิจวัตรที่ยาวนานต่อไปได้อย่างไร
TheBlastOne

2
@TheBlastOne ดูบทความวิกิพีเดีย คุณสามารถดำเนินการต่อได้หากคุณอยู่setjmpก่อนหน้าlongjmpนี้ สิ่งนี้ไม่เป็นมาตรฐาน
Potatoswatter

11
โครูทีนจำเป็นต้องทำงานบนสแต็กที่แยกจากกันไม่ใช่แบบเดียวกับที่แสดงในตัวอย่างของคุณ ในฐานะที่เป็นroutineAและroutineBใช้สแต็กเดียวกันมันใช้ได้กับโครูทีนดั้งเดิมเท่านั้น หากroutineAสายซ้อนลึกroutineCหลังจากที่สายแรกที่routineBและroutineCวิ่งroutineBเป็น coroutine แล้วroutineBยังอาจทำลายกองผลตอบแทน (ไม่เพียง แต่ตัวแปรท้องถิ่น) routineCของ ดังนั้นหากไม่มีการจัดสรรสแต็คพิเศษ (ผ่านalloca()หลังจากโทรrountineB?) คุณจะประสบปัญหาร้ายแรงกับตัวอย่างนี้หากใช้เป็นสูตร
Tino

7
โปรดระบุในคำตอบของคุณว่าการกระโดดลงมาจาก callstack (จาก A ถึง B) เป็นพฤติกรรมที่ไม่ได้กำหนด)
MikeMB

2
มันไม่มีกำหนดแน่นอน คุณจะต้องทำให้แต่ละฟังก์ชั่นทำงานบนสแต็กอิสระของตัวเองเพื่อสลับบริบทอย่างสวยงาม
อยากรู้อยากเห็น

19

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

เช่นเดียวกับทฤษฎีที่ชาญฉลาดทุกอย่างสิ่งนี้แตกสลายเมื่อพบกับความจริง ฟังก์ชันระดับกลางของคุณจะจัดสรรหน่วยความจำคว้าล็อกเปิดไฟล์และทำสิ่งต่างๆทุกประเภทที่ต้องการการล้าง ดังนั้นในทางปฏิบัติsetjmp/ longjmpมักเป็นความคิดที่ไม่ดียกเว้นในสถานการณ์ที่ จำกัด มากที่คุณสามารถควบคุมสภาพแวดล้อมของคุณได้ทั้งหมด (แพลตฟอร์มแบบฝังบางตัว)

จากประสบการณ์ของฉันในกรณีส่วนใหญ่เมื่อใดก็ตามที่คุณคิดว่าการใช้setjmp/ longjmpจะใช้งานได้โปรแกรมของคุณมีความชัดเจนและเรียบง่ายเพียงพอที่ทุกการเรียกใช้ฟังก์ชันระดับกลางในห่วงโซ่การโทรสามารถจัดการข้อผิดพลาดได้หรือมันยุ่งมากและเป็นไปไม่ได้ที่จะแก้ไขที่คุณควรทำexitเมื่อคุณ พบข้อผิดพลาด


3
โปรดดูที่libjpeg. เช่นเดียวกับใน C ++ คอลเลกชันของรูทีน C ส่วนใหญ่ใช้เวลาstruct *ดำเนินการกับบางสิ่งบางอย่างเป็นส่วนรวม แทนที่จะจัดเก็บการจัดสรรหน่วยความจำฟังก์ชันระดับกลางของคุณเป็นภาษาท้องถิ่นสามารถจัดเก็บไว้ในโครงสร้างได้ สิ่งนี้ช่วยให้longjmp()ตัวจัดการสามารถเพิ่มหน่วยความจำได้ นอกจากนี้ยังไม่มีตารางข้อยกเว้นที่เสียหายมากนักที่คอมไพเลอร์ C ++ ทั้งหมดยังคงสร้างขึ้น 20 ปีหลังจากข้อเท็จจริง
ไร้เสียงรบกวน

Like every clever theory this falls apart when meeting reality.อันที่จริงการจัดสรรชั่วคราวและสิ่งที่คล้ายกันทำให้longjmp()ยุ่งยากเนื่องจากคุณต้องsetjmp()หลายครั้งใน call stack (หนึ่งครั้งสำหรับทุกฟังก์ชั่นที่ต้องทำการล้างข้อมูลบางประเภทก่อนที่จะออกซึ่งจะต้อง โดยlongjmp()อิงตามบริบทที่ได้รับในตอนแรก) จะเลวร้ายยิ่งขึ้นหากทรัพยากรเหล่านั้นได้รับการแก้ไขหลังจากsetjmp()นั้นเนื่องจากคุณต้องประกาศว่าvolatileจะป้องกันไม่ให้เกิดการlongjmp()อุดตัน
sevko

10

การรวมกันของsetjmpและlongjmpเป็น "พลังพิเศษgoto" ใช้กับ EXTREME care. อย่างไรก็ตามตามที่คนอื่นอธิบายไว้ a longjmpมีประโยชน์มากในการออกจากสถานการณ์ข้อผิดพลาดที่น่ารังเกียจเมื่อคุณต้องการget me back to the beginningอย่างรวดเร็วแทนที่จะต้องปล่อยข้อความแสดงข้อผิดพลาดกลับมาสำหรับฟังก์ชัน 18 ชั้น

อย่างไรก็ตามเช่นเดียวกับgotoแต่ที่แย่กว่านั้นคือคุณต้องระมัดระวังในการใช้สิ่งนี้จริงๆ A longjmpจะพาคุณกลับไปที่จุดเริ่มต้นของรหัส จะไม่ส่งผลกระทบต่อสถานะอื่น ๆ ทั้งหมดที่อาจมีการเปลี่ยนแปลงระหว่างsetjmpและกลับไปที่จุดsetjmpเริ่มต้น ดังนั้นการจัดสรรการล็อกโครงสร้างข้อมูลที่เริ่มต้นครึ่งหนึ่ง ฯลฯ จึงยังคงถูกจัดสรรล็อกและเริ่มต้นครึ่งหนึ่งเมื่อคุณกลับไปยังจุดที่setjmpถูกเรียก ซึ่งหมายความว่าคุณต้องดูแลสถานที่ที่คุณทำเช่นนี้จริงๆจึงสามารถโทรติดต่อได้longjmpโดยไม่ทำให้เกิดปัญหามากขึ้น แน่นอนว่าหากสิ่งต่อไปที่คุณทำคือ "รีบูต" [หลังจากจัดเก็บข้อความเกี่ยวกับข้อผิดพลาดอาจ] - ในระบบฝังตัวที่คุณพบว่าฮาร์ดแวร์อยู่ในสถานะไม่ดีตัวอย่างเช่นก็ใช้ได้

ฉันยังเคยเห็นsetjmp/ longjmpเคยจัดให้มีกลไกเธรดขั้นพื้นฐาน แต่นั่นเป็นกรณีพิเศษ - และไม่ใช่วิธีการทำงานของเธรด "มาตรฐาน"

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

struct 
{
    void (*destructor)(void *ptr);
};


void LockForceUnlock(void *vlock)
{
   LOCK* lock = vlock;
}


LOCK func_lock;


void func()
{
   ref = add_destructor(LockForceUnlock, mylock);
   Lock(func_lock)
   ... 
   func2();   // May call longjmp. 

   Unlock(func_lock);
   remove_destructor(ref);
}

ด้วยระบบนี้คุณสามารถ "จัดการข้อยกเว้นอย่างสมบูรณ์เช่น C ++" ได้ แต่มันค่อนข้างยุ่งและขึ้นอยู่กับรหัสที่เขียนได้ดี


แน่นอนว่า +1 ในทางทฤษฎีคุณสามารถใช้การจัดการข้อยกเว้นที่สะอาดได้โดยเรียกร้องsetjmpให้ป้องกันการเริ่มต้นทุกครั้ง a la C ++ ... และควรค่าแก่การกล่าวถึงว่าการใช้มันสำหรับเธรดนั้นไม่เป็นมาตรฐาน
Potatoswatter

8

เนื่องจากคุณพูดถึงสิ่งที่ฝังไว้ฉันคิดว่ามันคุ้มค่าที่จะสังเกตกรณีที่ไม่ใช้งาน : เมื่อมาตรฐานการเข้ารหัสของคุณห้ามไว้ ตัวอย่างเช่น MISRA (MISRA-C: 2004: Rule 20.7) และJFS (AV Rule 20): "ห้ามใช้มาโคร setjmp และฟังก์ชัน longjmp"


8

setjmpและlongjmpมีประโยชน์มากในการทดสอบหน่วย

สมมติว่าเราต้องการทดสอบโมดูลต่อไปนี้:

#include <stdlib.h>

int my_div(int x, int y)
{
    if (y==0) exit(2);
    return x/y;
}

โดยปกติถ้าฟังก์ชันที่จะทดสอบเรียกใช้ฟังก์ชันอื่นคุณสามารถประกาศฟังก์ชันสตับเพื่อเรียกใช้ซึ่งจะเลียนแบบสิ่งที่ฟังก์ชันจริงทำเพื่อทดสอบโฟลว์บางอย่าง อย่างไรก็ตามในกรณีนี้ฟังก์ชันจะเรียกใช้exitซึ่งไม่ส่งคืน ต้นขั้วต้องเลียนแบบพฤติกรรมนี้อย่างใด setjmpและlongjmpสามารถทำเพื่อคุณได้

ในการทดสอบฟังก์ชันนี้เราสามารถสร้างโปรแกรมทดสอบต่อไปนี้:

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

// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))

// the function to test
int my_div(int x, int y);

// main result return code used by redefined assert
static int rslt;

// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;

// test suite main variables
static int done;
static int num_tests;
static int tests_passed;

//  utility function
void TestStart(char *name)
{
    num_tests++;
    rslt = 1;
    printf("-- Testing %s ... ",name);
}

//  utility function
void TestEnd()
{
    if (rslt) tests_passed++;
    printf("%s\n", rslt ? "success" : "fail");
}

// stub function
void exit(int code)
{
    if (!done)
    {
        assert(should_exit==1);
        assert(expected_code==code);
        longjmp(jump_env, 1);
    }
    else
    {
        _exit(code);
    }
}

// test case
void test_normal()
{
    int jmp_rval;
    int r;

    TestStart("test_normal");
    should_exit = 0;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(12,3);
    }

    assert(jmp_rval==0);
    assert(r==4);
    TestEnd();
}

// test case
void test_div0()
{
    int jmp_rval;
    int r;

    TestStart("test_div0");
    should_exit = 1;
    expected_code = 2;
    if (!(jmp_rval=setjmp(jump_env)))
    {
        r = my_div(2,0);
    }

    assert(jmp_rval==1);
    TestEnd();
}

int main()
{
    num_tests = 0;
    tests_passed = 0;
    done = 0;
    test_normal();
    test_div0();
    printf("Total tests passed: %d\n", tests_passed);
    done = 1;
    return !(tests_passed == num_tests);
}

ในตัวอย่างนี้คุณใช้setjmpก่อนที่จะเข้าสู่ฟังก์ชั่นเพื่อทดสอบจากนั้นexitคุณlongjmpจะเรียกกลับไปที่กรณีทดสอบโดยตรงในกรณีทดสอบ

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


7

ฉันได้เขียนJava เหมือนยกเว้นกลไกการจัดการใน C ใช้setjmp(), longjmp()และการทำงานของระบบ มันจับข้อยกเว้นที่กำหนดเอง SIGSEGVแต่ยังส่งสัญญาณเช่น มันมีการซ้อนกันของบล็อกการจัดการข้อยกเว้นแบบไม่สิ้นสุดซึ่งทำงานผ่านการเรียกใช้ฟังก์ชันและสนับสนุนการใช้งานเธรดที่พบบ่อยที่สุดสองแบบ ช่วยให้คุณสามารถกำหนดลำดับชั้นต้นไม้ของคลาสข้อยกเว้นที่มีคุณลักษณะการสืบทอดเวลาลิงก์และcatchคำสั่งจะนำเสนอโครงสร้างนี้เพื่อดูว่าจำเป็นต้องจับหรือส่งต่อหรือไม่

นี่คือตัวอย่างลักษณะของโค้ดโดยใช้สิ่งนี้:

try
{
    *((int *)0) = 0;    /* may not be portable */
}
catch (SegmentationFault, e)
{
    long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' };
    ((void(*)())f)();   /* may not be portable */
}
finally
{
    return(1 / strcmp("", ""));
}

และนี่คือส่วนหนึ่งของไฟล์รวมที่มีตรรกะมากมาย:

#ifndef _EXCEPT_H
#define _EXCEPT_H

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include "Lifo.h"
#include "List.h"

#define SETJMP(env)             sigsetjmp(env, 1)
#define LONGJMP(env, val)       siglongjmp(env, val)
#define JMP_BUF                 sigjmp_buf

typedef void (* Handler)(int);

typedef struct _Class *ClassRef;        /* exception class reference */
struct _Class
{
    int         notRethrown;            /* always 1 (used by throw()) */
    ClassRef    parent;                 /* parent class */
    char *      name;                   /* this class name string */
    int         signalNumber;           /* optional signal number */
};

typedef struct _Class Class[1];         /* exception class */

typedef enum _Scope                     /* exception handling scope */
{
    OUTSIDE = -1,                       /* outside any 'try' */
    INTERNAL,                           /* exception handling internal */
    TRY,                                /* in 'try' (across routine calls) */
    CATCH,                              /* in 'catch' (idem.) */
    FINALLY                             /* in 'finally' (idem.) */
} Scope;

typedef enum _State                     /* exception handling state */
{
    EMPTY,                              /* no exception occurred */
    PENDING,                            /* exception occurred but not caught */
    CAUGHT                              /* occurred exception caught */
} State;

typedef struct _Except                  /* exception handle */
{
    int         notRethrown;            /* always 0 (used by throw()) */
    State       state;                  /* current state of this handle */
    JMP_BUF     throwBuf;               /* start-'catching' destination */
    JMP_BUF     finalBuf;               /* perform-'finally' destination */
    ClassRef    class;                  /* occurred exception class */
    void *      pData;                  /* exception associated (user) data */
    char *      file;                   /* exception file name */
    int         line;                   /* exception line number */
    int         ready;                  /* macro code control flow flag */
    Scope       scope;                  /* exception handling scope */
    int         first;                  /* flag if first try in function */
    List *      checkList;              /* list used by 'catch' checking */
    char*       tryFile;                /* source file name of 'try' */
    int         tryLine;                /* source line number of 'try' */

    ClassRef    (*getClass)(void);      /* method returning class reference */
    char *      (*getMessage)(void);    /* method getting description */
    void *      (*getData)(void);       /* method getting application data */
    void        (*printTryTrace)(FILE*);/* method printing nested trace */
} Except;

typedef struct _Context                 /* exception context per thread */
{
    Except *    pEx;                    /* current exception handle */
    Lifo *      exStack;                /* exception handle stack */
    char        message[1024];          /* used by ExceptGetMessage() */
    Handler     sigAbrtHandler;         /* default SIGABRT handler */
    Handler     sigFpeHandler;          /* default SIGFPE handler */
    Handler     sigIllHandler;          /* default SIGILL handler */
    Handler     sigSegvHandler;         /* default SIGSEGV handler */
    Handler     sigBusHandler;          /* default SIGBUS handler */
} Context;

extern Context *        pC;
extern Class            Throwable;

#define except_class_declare(child, parent) extern Class child
#define except_class_define(child, parent)  Class child = { 1, parent, #child }

except_class_declare(Exception,           Throwable);
except_class_declare(OutOfMemoryError,    Exception);
except_class_declare(FailedAssertion,     Exception);
except_class_declare(RuntimeException,    Exception);
except_class_declare(AbnormalTermination, RuntimeException);  /* SIGABRT */
except_class_declare(ArithmeticException, RuntimeException);  /* SIGFPE */
except_class_declare(IllegalInstruction,  RuntimeException);  /* SIGILL */
except_class_declare(SegmentationFault,   RuntimeException);  /* SIGSEGV */
except_class_declare(BusError,            RuntimeException);  /* SIGBUS */


#ifdef  DEBUG

#define CHECKED                                                         \
        static int checked

#define CHECK_BEGIN(pC, pChecked, file, line)                           \
            ExceptCheckBegin(pC, pChecked, file, line)

#define CHECK(pC, pChecked, class, file, line)                          \
                 ExceptCheck(pC, pChecked, class, file, line)

#define CHECK_END                                                       \
            !checked

#else   /* DEBUG */

#define CHECKED
#define CHECK_BEGIN(pC, pChecked, file, line)           1
#define CHECK(pC, pChecked, class, file, line)          1
#define CHECK_END                                       0

#endif  /* DEBUG */


#define except_thread_cleanup(id)       ExceptThreadCleanup(id)

#define try                                                             \
    ExceptTry(pC, __FILE__, __LINE__);                                  \
    while (1)                                                           \
    {                                                                   \
        Context *       pTmpC = ExceptGetContext(pC);                   \
        Context *       pC = pTmpC;                                     \
        CHECKED;                                                        \
                                                                        \
        if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) &&            \
            pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0)           \
        {                                                               \
            pC->pEx->scope = TRY;                                       \
            do                                                          \
            {

#define catch(class, e)                                                 \
            }                                                           \
            while (0);                                                  \
        }                                                               \
        else if (CHECK(pC, &checked, class, __FILE__, __LINE__) &&      \
                 pC->pEx->ready && ExceptCatch(pC, class))              \
        {                                                               \
            Except *e = LifoPeek(pC->exStack, 1);                       \
            pC->pEx->scope = CATCH;                                     \
            do                                                          \
            {

#define finally                                                         \
            }                                                           \
            while (0);                                                  \
        }                                                               \
        if (CHECK_END)                                                  \
            continue;                                                   \
        if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0)          \
            pC->pEx->ready = 1;                                         \
        else                                                            \
            break;                                                      \
    }                                                                   \
    ExceptGetContext(pC)->pEx->scope = FINALLY;                         \
    while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC))   \
        while (ExceptGetContext(pC)->pEx->ready-- > 0)

#define throw(pExceptOrClass, pData)                                    \
    ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__)

#define return(x)                                                       \
    {                                                                   \
        if (ExceptGetScope(pC) != OUTSIDE)                              \
        {                                                               \
            void *      pData = malloc(sizeof(JMP_BUF));                \
            ExceptGetContext(pC)->pEx->pData = pData;                   \
            if (SETJMP(*(JMP_BUF *)pData) == 0)                         \
                ExceptReturn(pC);                                       \
            else                                                        \
                free(pData);                                            \
        }                                                               \
        return x;                                                       \
    }

#define pending                                                         \
    (ExceptGetContext(pC)->pEx->state == PENDING)

extern Scope    ExceptGetScope(Context *pC);
extern Context *ExceptGetContext(Context *pC);
extern void     ExceptThreadCleanup(int threadId);
extern void     ExceptTry(Context *pC, char *file, int line);
extern void     ExceptThrow(Context *pC, void * pExceptOrClass,
                            void *pData, char *file, int line);
extern int      ExceptCatch(Context *pC, ClassRef class);
extern int      ExceptFinally(Context *pC);
extern void     ExceptReturn(Context *pC);
extern int      ExceptCheckBegin(Context *pC, int *pChecked,
                                 char *file, int line);
extern int      ExceptCheck(Context *pC, int *pChecked, ClassRef class,
                            char *file, int line);


#endif  /* _EXCEPT_H */

นอกจากนี้ยังมีโมดูล C ที่มีตรรกะในการจัดการสัญญาณและการทำบัญชี

มันยากมากที่จะนำไปใช้ฉันบอกคุณได้และฉันเกือบจะเลิกใช้ ฉันผลักดันให้มันใกล้เคียงกับ Java มากที่สุด ฉันพบว่ามันน่าประหลาดใจแค่ไหนที่ฉันไปได้ไกลแค่ C

ตะโกนบอกฉันถ้าคุณสนใจ


1
ฉันประหลาดใจที่เป็นไปได้หากไม่มีการสนับสนุนคอมไพเลอร์จริงสำหรับข้อยกเว้นที่กำหนดเอง แต่สิ่งที่น่าสนใจจริงๆคือการแปลงสัญญาณเป็นข้อยกเว้นได้อย่างไร
Paul Stelian

ฉันจะถามสิ่งหนึ่ง: แล้วข้อยกเว้นที่จะไม่ถูกจับได้ล่ะ? main () จะออกอย่างไร?
Paul Stelian

1
@PaulStelian และนี่คือคำตอบของคุณว่าmain()จะออกจากการกำจัดที่ไม่ถูกจับได้อย่างไร โปรดโหวตคำตอบนี้ :-)
meaning-

1
@PaulStelian อาฉันเข้าใจแล้วว่าตอนนี้คุณหมายถึงอะไร ข้อยกเว้นรันไทม์ที่ไม่ถูกจับฉันเชื่อว่าถูกยกขึ้นอีกครั้งเพื่อให้ใช้คำตอบทั่วไป (ขึ้นอยู่กับแพลตฟอร์ม) ไม่พบข้อยกเว้นที่กำหนดเองถูกพิมพ์และละเว้น ดูProgagationหัวข้อในREADMEฉันได้โพสต์รหัสเมษายน 1999 ไปยัง GitHub (ดูลิงก์ในคำตอบที่แก้ไข) ได้ดู; มันเป็นถั่วที่ยากที่จะแตก จะเป็นการดีที่จะได้ยินสิ่งที่คุณคิด
ความหมาย - เรื่อง

2
ดู README สั้น ๆ น่ารักดีที่นั่น ดังนั้นโดยพื้นฐานแล้วมันจะแพร่กระจายไปยัง try block ด้านนอกสุดและมีการรายงานคล้ายกับฟังก์ชัน async ของ JavaScript ดี. ฉันจะดูซอร์สโค้ดเองในภายหลัง
Paul Stelian

1

การใช้ setjmp / longjmp ที่สำคัญที่สุดคือการทำหน้าที่เป็น "การกระโดดโกโตที่ไม่ใช่ในพื้นที่" คำสั่ง Goto (และมีอินสแตนซ์ที่หายากที่คุณจะต้องใช้ goto มากกว่าและในขณะที่ลูป) ถูกใช้มากที่สุดอย่างปลอดภัยในขอบเขตเดียวกัน หากคุณใช้ goto เพื่อข้ามขอบเขต (หรือข้ามการจัดสรรอัตโนมัติ) คุณมักจะทำให้สแต็กของโปรแกรมเสียหาย setjmp / longjmp หลีกเลี่ยงสิ่งนี้โดยบันทึกข้อมูลสแต็กในตำแหน่งที่คุณต้องการข้ามไป จากนั้นเมื่อคุณกระโดดมันจะโหลดข้อมูลกองนี้ หากไม่มีคุณสมบัตินี้โปรแกรมเมอร์ C มักจะต้องหันไปใช้การเขียนโปรแกรมแอสเซมบลีเพื่อแก้ปัญหาที่เฉพาะ setjmp / longjmp เท่านั้นที่สามารถแก้ไขได้ ขอบคุณพระเจ้าที่มีอยู่จริง ทุกสิ่งในไลบรารี C มีความสำคัญอย่างยิ่ง คุณจะรู้ว่าเมื่อไหร่ที่คุณต้องการ


1
"ทุกสิ่งในไลบรารี C มีความสำคัญอย่างยิ่ง" มีของมากมายที่เลิกใช้งานและสิ่งที่ไม่ดีเลยเช่นภาษา
qwr

0

นอกเหนือจากการจัดการข้อผิดพลาดแล้วสิ่งอื่น ๆ ที่คุณสามารถทำได้และไม่ได้กล่าวถึงก่อนหน้านี้คือการใช้การคำนวณ tail rectursive ใน C อย่างชาญฉลาด

นี่คือวิธีการใช้งานความต่อเนื่องใน C โดยไม่ต้องแปลงรหัสอินพุตในรูปแบบการส่งต่อแบบต่อเนื่อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.