จะโยนข้อยกเว้นใน C ได้อย่างไร?


106

ฉันพิมพ์สิ่งนี้ลงใน Google แต่พบเฉพาะ howtos ใน C ++

วิธีการทำใน C?


7
C ไม่รองรับการจัดการข้อยกเว้น ในการสร้างข้อยกเว้นใน C คุณต้องใช้แพลตฟอร์มบางอย่างที่เฉพาะเจาะจงเช่นการจัดการข้อยกเว้นที่มีโครงสร้างของ Win32 แต่เพื่อให้ความช่วยเหลือใด ๆ เราจำเป็นต้องทราบแพลตฟอร์มที่คุณสนใจ
Jerry Coffin

18
... และอย่าใช้การจัดการข้อยกเว้นที่มีโครงสร้าง Win32
Brian R.Bondy

4
ในทางทฤษฎีการใช้ setjmp () และ longjmp () ควรใช้งานได้ แต่ฉันไม่คิดว่ามันคุ้มค่ากับปัญหา
Joseph Quinsey

คำตอบ:


79

ไม่มีข้อยกเว้นใน C. ใน C ข้อผิดพลาดจะได้รับแจ้งจากค่าที่ส่งคืนของฟังก์ชันค่าออกของกระบวนการส่งสัญญาณไปยังกระบวนการ ( สัญญาณข้อผิดพลาดของโปรแกรม (GNU libc) ) หรือการหยุดชะงักของฮาร์ดแวร์ CPU (หรือการแจ้งเตือนอื่น ๆ เกิดข้อผิดพลาดจาก CPU หากมี) ( ตัวประมวลผลจัดการกรณีการหารด้วยศูนย์อย่างไร )

มีการกำหนดข้อยกเว้นใน C ++ และภาษาอื่น ๆ การจัดการข้อยกเว้นใน C ++ ระบุไว้ในมาตรฐาน C ++ "การจัดการข้อยกเว้น S.15" ไม่มีส่วนที่เทียบเท่าในมาตรฐาน C


4
ดังนั้นใน C รับประกันว่าจะไม่มีข้อยกเว้นอย่างไร?
httpinterpret

65
@httpinterpret: ในภาษา C "รับประกัน" ว่าไม่มีข้อยกเว้นในลักษณะเดียวกับที่ "รับประกัน" ว่าไม่มีเทมเพลตหรือไม่มีเงาสะท้อนหรือไม่มียูนิคอร์น ข้อกำหนดภาษาไม่ได้กำหนดสิ่งนั้น ๆ มี setjmp / longjmp ซึ่งสามารถใช้เพื่อออกจากฟังก์ชันโดยไม่ต้องย้อนกลับ ดังนั้นโปรแกรมสามารถสร้างกลไกที่เหมือนข้อยกเว้นของตนเองได้หากต้องการหรือการใช้งาน C สามารถกำหนดส่วนขยายให้เป็นมาตรฐานได้
Steve Jessop

2
โปรแกรมที่มีmainอยู่ใน.cไฟล์สามารถรวม C ++ ได้ดังนั้นจึงอาจมีการโยนและจับข้อยกเว้นในโปรแกรมได้ แต่ส่วนของรหัส C จะยังคงเพิกเฉยต่อสิ่งที่เกิดขึ้นทั้งหมดนี้ยกเว้นว่าการโยนและการจับข้อยกเว้นมักอาศัยฟังก์ชันที่เขียนด้วย C ซึ่งอยู่ในไลบรารี C ++ C ถูกใช้เนื่องจากคุณไม่สามารถเสี่ยงกับฟังก์ชันที่เรียกว่าthrowต้องทำโดยต้องทิ้งข้อยกเว้นเอง อาจมีวิธีเฉพาะของคอมไพเลอร์ / ไลบรารี / กำหนดเป้าหมายในการโยน / จับข้อยกเว้น แต่การโยนอินสแตนซ์คลาสจะมีปัญหาในตัวเอง
nategoose

61
@ สตีฟ: โปรดแจ้งให้เราทราบหากคุณพบภาษาที่มียูนิคอร์นฉันรอสิ่งนี้มาหลายปีแล้ว
Brian R.Bondy

5
@ BrianR.Bondy นี่คือภาษาที่มียูนิคอร์น (ฉันรับประกันแบบเดียวกับที่รับประกันในภาษา C)
Tofandel

38

ใน C คุณสามารถใช้การรวมกันของsetjmp()และlongjmp()ฟังก์ชันที่กำหนดไว้ในsetjmp.h. ตัวอย่างจาก Wikipedia

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

static jmp_buf buf;

void second(void) {
    printf("second\n");         // prints
    longjmp(buf,1);             // jumps back to where setjmp 
                                //   was called - making setjmp now return 1
}

void first(void) {
    second();
    printf("first\n");          // does not print
}

int main() {   
    if ( ! setjmp(buf) ) {
        first();                // when executed, setjmp returns 0
    } else {                    // when longjmp jumps back, setjmp returns 1
        printf("main");         // prints
    }

    return 0;
}

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


ฉันเคยเห็น setjump / longjump ทำงานไม่ถูกต้องในโปรแกรม C ++ แม้ว่าจะไม่มีวัตถุที่ต้องการการทำลายเมื่อมีการใช้ข้อยกเว้นก็ตาม ฉันไม่ค่อยใช้มันในโปรแกรม C
nategoose

นี่เป็นแนวทางที่น่าสนใจโดยใช้ setjmp on-time.com/ddj0011.htmแต่ใช่โดยพื้นฐานแล้วคุณต้องประดิษฐ์ด้วยตัวเองหากคุณต้องการเรียกใช้โค้ดนอกวงโดยไม่ต้องคลายสแต็ก
Dwayne Robinson

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

1
อย่างไรก็ตามการจัดการข้อยกเว้นเป็นสิ่งที่ฉันหวังเป็นอย่างยิ่งว่าจะลงเอยใน C11 แม้จะมีความเชื่อทั่วไป แต่ก็ไม่ต้องการให้ OOP ทำงานอย่างถูกต้องและ C ต้องทนทุกข์ทรมานอย่างไม่มีที่สิ้นสุดเมื่อทุกการเรียกใช้ฟังก์ชันต้องใช้if()ไฟล์. ฉันมองไม่เห็นอันตรายในนั้น มีใครอยู่ที่นี่ได้ไหม

@ user4229245 ฉันอยู่ภายใต้การแสดงผล (anecdotal) ทุกสิ่งที่ "ทำเพื่อคุณ" จะไม่อยู่ในข้อกำหนดของภาษา c ให้มากที่สุดโดยเฉพาะเพื่อให้ c มีความเกี่ยวข้องกับโลหะเปล่าและการเขียนโปรแกรมเครื่องจักรโดยที่ปัจจัยพื้นฐานของสถานะเดิมเช่นแปดไบต์ยังไม่มี ไม่เป็นสากลเพียงแค่ความคิดของฉันฉันไม่ใช่ผู้เขียนข้อมูลจำเพาะ
ThorSummoner

21

C แบบเก่าธรรมดาไม่สนับสนุนข้อยกเว้นโดยกำเนิด

คุณสามารถใช้กลยุทธ์อื่นในการจัดการข้อผิดพลาดเช่น:

  • ส่งคืนรหัสข้อผิดพลาด
  • กลับFALSEและใช้last_errorตัวแปรหรือฟังก์ชัน

ดูhttp://en.wikibooks.org/wiki/C_Programming/Error_handling


นอกจากนี้ windows api ยังมีวิธีการเดียวกันในการจัดการ erros ตัวอย่างเช่นGetLastError()ใน windows API
BattleTested

20

ไม่มีในตัวกลไกการยกเว้นใน C; คุณต้องจำลองข้อยกเว้นและความหมาย โดยปกติจะทำได้โดยอาศัยsetjmpและlongjmpและ

มีห้องสมุดอยู่ไม่กี่แห่งและฉันกำลังใช้งานอีกแห่งหนึ่ง เรียกว่าexceptions4c ; แบบพกพาและฟรี คุณอาจลองดูและเปรียบเทียบกับทางเลือกอื่นเพื่อดูว่าตัวเลือกใดเหมาะกับคุณมากที่สุด


ฉันอ่านตัวอย่างโค้ดของคุณฉันเริ่มโครงการที่คล้ายกันด้วยโครงสร้าง แต่ใช้ uuid แทนสตริงเพื่อระบุ "ข้อยกเว้น" แต่ละรายการ โครงการของคุณมีแนวโน้มดีมาก
umlcat

ทางเลือกในการเชื่อมโยงในขณะนี้ควรชี้ไปที่github.com/guillermocalvo/exceptions4c/wiki/alternatives
Marcel Waldvogel

คุณ (หรือใครก็ตาม) ทราบเกี่ยวกับการเปรียบเทียบระหว่างแพ็คเกจข้อยกเว้นต่างๆหรือไม่?
Marcel Waldvogel

11

C สามารถโยนข้อยกเว้น C ++ ได้ซึ่งเป็นรหัสเครื่องอยู่ดี ตัวอย่างเช่นใน bar.c

// begin bar.c
#include <stdlib.h>
#include <stdint.h>
extern void *__cxa_allocate_exception(size_t thrown_size);
extern void __cxa_throw (void *thrown_exception, void* *tinfo, void (*dest) (void *) );
extern void * _ZTIl; // typeinfo of long
int bar1()
{
   int64_t * p = (int64_t*)__cxa_allocate_exception(8);
   *p = 1976;
   __cxa_throw(p,&_ZTIl,0);
  return 10;
}
// end bar.c

ใน a.cc

#include <stdint.h>
#include <cstdio>
extern "C" int bar1();
void foo()
{
  try{
    bar1();
  }catch(int64_t x){
    printf("good %ld",x);
  }
}
int main(int argc, char *argv[])
{
  foo();
  return 0;
}

เพื่อรวบรวม

gcc -o bar.o -c bar.c && g++ a.cc bar.o && ./a.out

เอาท์พุท

good 1976

http://mentorembedded.github.io/cxx-abi/abi-eh.htmlมีข้อมูลรายละเอียดเพิ่มเติมเกี่ยวกับ__cxa_throwมีข้อมูลรายละเอียดเพิ่มเติมเกี่ยวกับ

ฉันไม่แน่ใจว่าเป็นแบบพกพาหรือไม่และฉันทดสอบด้วย 'gcc-4.8.2' บน Linux


2
"_ZTIl" คืออะไร ??? ฉันไม่พบข้อมูลอ้างอิงใด ๆ จากที่ใดและเป็นเรื่องลึกลับว่าจะทำให้รหัส C เข้าถึง std :: type_info ได้อย่างไร ได้โปรด ... ช่วยฉันด้วย!
AreusAstarte

1
_ZTIIวิธีการเช่นtypeinfo for long echo '_ZTIl' | c++filt มันเป็นโครงการโกง g ++
wcy

และเพื่อการวัดที่ดีอย่าลืมเรียกสัญลักษณ์ ac ในบล็อกจับเพื่อหลีกเลี่ยง c ++ ให้มากที่สุด: P (PS ขอบคุณสำหรับการแบ่งปันตัวอย่างจริงที่ยอดเยี่ยม!)
ThorSummoner

8

คำถามนี้เก่ามาก แต่ฉันเพิ่งสะดุดและคิดว่าฉันจะแบ่งปันเทคนิค: หารด้วยศูนย์หรือหักล้างตัวชี้โมฆะ

คำถามคือ "วิธีขว้าง" ไม่ใช่วิธีจับหรือแม้กระทั่งวิธีการโยนข้อยกเว้นบางประเภท ฉันมีสถานการณ์เมื่อนานมาแล้วที่เราจำเป็นต้องเรียกใช้ข้อยกเว้นจาก C เพื่อให้ถูกจับใน C ++ โดยเฉพาะอย่างยิ่งเรามีรายงานข้อผิดพลาด "pure virtual function call" เป็นครั้งคราวและจำเป็นต้องโน้มน้าวให้ฟังก์ชัน _purecall ของ C runtime โยนบางสิ่ง ดังนั้นเราจึงเพิ่มฟังก์ชัน _purecall ของเราเองซึ่งหารด้วยศูนย์และบูมเราได้รับข้อยกเว้นที่เราสามารถจับ C ++ ได้และยังใช้สแต็คสนุก ๆ เพื่อดูว่ามีอะไรผิดพลาดบ้าง


@AreusAstarte ในกรณีที่มีคนต้องการให้แสดง C โดยหารด้วยศูนย์:int div_by_nil(int x) { return x/0; }

7

ใน Win with MSVCมี__try ... __except ...แต่มันแย่มากและคุณไม่ต้องการใช้มันหากคุณสามารถหลีกเลี่ยงได้ ดีกว่าที่จะบอกว่าไม่มีข้อยกเว้น


1
จริงๆแล้วข้อยกเว้น C ++ นั้นสร้างขึ้นที่ด้านบนของ SEH ด้วยเช่นกัน
Calmarius

@Calmarius SEHคืออะไร?
Marcel Waldvogel

1
@MarcelWaldvogel Structured Exception Handling (วิธีของ Windows ในการจัดการข้อยกเว้นของ CPU)
Calmarius


3

ดังที่กล่าวไว้ในเธรดจำนวนมากวิธีการ "มาตรฐาน" คือการใช้ setjmp / longjmp ฉันโพสต์วิธีแก้ปัญหาดังกล่าวไปที่https://github.com/psevon/exceptions-and-raii-in-c นี่คือความรู้ของฉันวิธีแก้ปัญหาเดียวที่อาศัยการล้างทรัพยากรที่จัดสรรโดยอัตโนมัติ มันใช้สมาร์ทพอยน์เตอร์ที่ไม่ซ้ำใครและใช้ร่วมกันและอนุญาตให้ฟังก์ชันระดับกลางปล่อยให้ข้อยกเว้นผ่านไปโดยไม่ต้องจับและยังคงมีการจัดสรรทรัพยากรในพื้นที่อย่างถูกต้อง


2

C ไม่รองรับข้อยกเว้น คุณสามารถลองรวบรวมรหัส C ของคุณเป็น C ++ ด้วย Visual Studio หรือ G ++ และดูว่าจะรวบรวมตามที่เป็นอยู่หรือไม่ แอปพลิเคชัน C ส่วนใหญ่จะรวบรวมเป็น C ++ โดยไม่มีการเปลี่ยนแปลงที่สำคัญจากนั้นคุณสามารถใช้ไวยากรณ์ try ... catch


0

หากคุณเขียนโค้ดด้วยรูปแบบการออกแบบ Happy path (เช่นสำหรับอุปกรณ์ฝังตัว) คุณอาจจำลองการประมวลผลข้อผิดพลาดข้อยกเว้น (aka deffering หรือการจำลองในที่สุด) ด้วยตัวดำเนินการ "goto"

int process(int port)
{
    int rc;
    int fd1;
    int fd2;

    fd1 = open("/dev/...", ...);
    if (fd1 == -1) {
      rc = -1;
      goto out;
    }

    fd2 = open("/dev/...", ...);
    if (fd2 == -1) {
      rc = -1;
      goto out;
    }

    // Do some with fd1 and fd2 for example write(f2, read(fd1))

    rc = 0;

   out:

    //if (rc != 0) {
        (void)close(fd1);
        (void)close(fd2);
    //}

    return rc;
}

มันไม่ได้เป็นตัวจัดการข้อยกเว้น แต่จะนำคุณไปสู่วิธีจัดการกับข้อผิดพลาดที่ทางออก fucntion

ป.ล. คุณควรระมัดระวังในการใช้ goto จากขอบเขตที่ลึกเท่ากันหรือมากกว่าเท่านั้นและห้ามข้ามการประกาศตัวแปร


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