Làm thế nào để ném một ngoại lệ trong C?


100

Tôi đã nhập cái này vào google nhưng chỉ tìm thấy howtos trong C ++,

làm thế nào để làm điều đó trong C?


6
C không hỗ trợ xử lý ngoại lệ. Để đưa ra một ngoại lệ trong C, bạn cần sử dụng một nền tảng cụ thể nào đó, chẳng hạn như xử lý ngoại lệ có cấu trúc của Win32 - nhưng để cung cấp bất kỳ trợ giúp nào về điều đó, chúng tôi sẽ cần biết nền tảng bạn quan tâm.
Jerry Coffin

18
... và không sử dụng xử lý ngoại lệ có cấu trúc Win32.
Brian R. Bondy

4
Về lý thuyết, việc sử dụng setjmp () và longjmp () có hiệu quả, nhưng tôi không nghĩ nó đáng gặp rắc rối.
Joseph Quinsey

Câu trả lời:


77

Không có ngoại lệ trong C. Trong C, các lỗi được thông báo bằng giá trị trả về của hàm, giá trị thoát của quá trình, các tín hiệu cho quá trình ( Tín hiệu lỗi chương trình (GNU libc) ) hoặc ngắt phần cứng CPU (hoặc thông báo khác lỗi hình thành CPU nếu có) ( Cách bộ xử lý xử lý trường hợp chia cho 0 ).

Tuy nhiên, các ngoại lệ được định nghĩa trong C ++ và các ngôn ngữ khác. Xử lý ngoại lệ trong C ++ được quy định trong tiêu chuẩn C ++ "S.15 Xử lý ngoại lệ", không có phần tương đương trong tiêu chuẩn C.


4
Vì vậy, trong C nó được đảm bảo sẽ không có ngoại lệ, làm thế nào?
httpinterpret

62
@httpinterpret: trong C, nó được "đảm bảo" rằng không có ngoại lệ giống như cách được "đảm bảo" rằng không có khuôn mẫu, hoặc không có phản chiếu, hoặc không có kỳ lân. Đặc tả ngôn ngữ chỉ đơn giản là không xác định bất kỳ điều gì như vậy. Tuy nhiên, có setjmp / longjmp, có thể được sử dụng để thoát một hàm mà không quay lại. Vì vậy, các chương trình có thể xây dựng các cơ chế giống như ngoại lệ của riêng mình nếu họ muốn hoặc triển khai C có thể xác định các phần mở rộng cho tiêu chuẩn.
Steve Jessop

2
Một chương trình có maintrong .ctệp có thể bao gồm một số C ++, và do đó các ngoại lệ có thể được ném và bắt trong chương trình, nhưng các phần mã C sẽ không biết về tất cả những điều này đang diễn ra ngoại trừ việc ném và bắt ngoại lệ thường dựa vào các hàm được viết bằng C. nằm trong các thư viện C ++. C được sử dụng vì bạn không thể mạo hiểm với hàm được gọi là throwcần thực hiện chính nó. Tuy nhiên, có thể có một trình biên dịch / thư viện / đích cụ thể để ném / bắt các ngoại lệ. Nhưng ném một cá thể lớp sẽ có vấn đề riêng.
nategoose

61
@Steve: Vui lòng cho tôi biết nếu bạn tìm thấy một ngôn ngữ với kỳ lân, tôi đã chờ đợi một điều như vậy trong nhiều năm.
Brian R. Bondy

5
@ BrianR.Bondy Dưới đây là một ngôn ngữ với kỳ lân (Anh tin chắc rằng cùng một cách như nó được bảo đảm bằng C)
Tofandel

37

Trong C, bạn có thể sử dụng kết hợp các hàm setjmp()longjmp(), được định nghĩa trong setjmp.h. Ví dụ từ 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;
}

Lưu ý: Tôi thực sự khuyên bạn không nên sử dụng chúng vì chúng hoạt động tồi tệ với C ++ (hàm hủy của các đối tượng cục bộ sẽ không được gọi) và thực sự khó hiểu chuyện gì đang xảy ra. Thay vào đó, hãy trả lại một số loại lỗi.


Tôi đã thấy setjump / longjump không hoạt động chính xác trong các chương trình C ++ ngay cả khi không có đối tượng nào cần phá hủy khi sử dụng ngoại lệ. Tôi hiếm khi sử dụng chúng trong các chương trình C.
nategoose

Đây là một cách tiếp cận thú vị bằng cách sử dụng setjmp. on-time.com/ddj0011.htm Nhưng có, về cơ bản bạn phải tự mình phát minh ra chúng nếu bạn muốn thực thi mã ngoài dải mà không cần tháo ngăn xếp.
Dwayne Robinson

Tôi không chắc tại sao phải báo trước về việc sử dụng chúng trong C ++ khi câu hỏi là về C và C ++ dù sao cũng có ngoại lệ. Trong mọi trường hợp, điều quan trọng là OP biết rằng để giữ cho việc triển khai setjmp / longjmp không làm hỏng chân của bạn, hãy luôn nhớ rằng trước tiên bạn cần phải truy cập trình xử lý lỗi (để thiết lập nó) và trình xử lý lỗi đó cần phải trả lại hai lần.

1
Nhân tiện, xử lý ngoại lệ là thứ mà tôi thực sự hy vọng sẽ có trong C11. Mặc dù có niềm tin phổ biến, nó không yêu cầu OOP hoạt động bình thường và C phải chịu đựng liên tục bởi mọi lệnh gọi hàm yêu cầu một if(). Tôi không thể nhìn thấy một mối nguy hiểm trong đó; có ai khác ở đây được không?

@ user4229245 Tôi đang có ấn tượng (giai thoại) bất cứ điều gì "làm cho bạn" được giữ ngoài thông số ngôn ngữ c càng nhiều càng tốt, cụ thể là để giữ cho c phù hợp với kim loại trần và lập trình máy, nơi ngay cả các nguyên tắc cơ bản hiện trạng như tám byte bit cũng không không phổ quát, chỉ là suy nghĩ của tôi tho, tôi không phải là tác giả đặc biệt
ThorSummoner

21

Nguyên bản C cũ không hỗ trợ các ngoại lệ.

Bạn có thể sử dụng các chiến lược xử lý lỗi thay thế, chẳng hạn như:

  • trả lại mã lỗi
  • quay lại FALSEvà sử dụng một last_errorbiến hoặc hàm.

Xem http://en.wikibooks.org/wiki/C_Programming/Error_handling .


Ngoài ra windows api cũng có phương pháp tương tự để xử lý các lỗi. ví dụ GetLastError()trong API windows.
BattleTised

20

Không có built-in cơ chế ngoại lệ trong C; bạn cần mô phỏng các ngoại lệ và ngữ nghĩa của chúng. Điều này thường đạt được bằng cách dựa vào setjmplongjmp.

Có khá nhiều thư viện xung quanh và tôi đang triển khai một thư viện khác. Nó được gọi là exceptions4c ; nó di động và miễn phí. Bạn có thể xem xét nó và so sánh nó với các lựa chọn thay thế khác để xem cái nào phù hợp với bạn nhất.


Tôi đọc cho bạn ví dụ mã, tôi đã bắt đầu dự án tương tự với một cấu trúc, nhưng sử dụng uuid thay vì một chuỗi để xác định từng "ngoại lệ". Dự án của bạn có vẻ rất hứa hẹn.
umlcat

Các giải pháp thay thế liên kết bây giờ sẽ trỏ đến github.com/guillermocalvo/exceptions4c/wiki/alternatives
Marcel Waldvogel

Bạn (hoặc bất kỳ ai khác) biết về sự so sánh giữa các gói ngoại lệ khác nhau?
Marcel Waldvogel

11

C có thể ném ngoại lệ C ++, dù sao chúng cũng là mã máy. Ví dụ: trong 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

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

biên dịch nó

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

đầu ra

good 1976

http://mentorembedded.github.io/cxx-abi/abi-eh.html có thêm thông tin chi tiết về __cxa_throw.

Tôi không chắc liệu nó có phải là portable hay không và tôi đã kiểm tra nó với 'gcc-4.8.2' trên Linux.


2
"_ZTIl" là cái quái gì vậy ??? Tôi không thể tìm thấy bất kỳ tài liệu tham khảo nào về nó ở bất kỳ đâu và đó là một bí ẩn hoàn toàn về việc làm thế nào để mã C có thể truy cập vào std :: type_info. Làm ơn giúp tôi!
AreusAstarte

1
_ZTIInghĩa là typeinfo for long, ví dụ echo '_ZTIl' | c++filt . Đó là lược đồ mangling g ++.
wcy

và chỉ để có biện pháp tốt, hãy nhớ gọi biểu tượng ac trong khối bắt để tránh c ++ càng nhiều càng tốt: P (Tái
bút

8

Câu hỏi này rất cũ, nhưng tôi chỉ tình cờ gặp nó và nghĩ rằng tôi muốn chia sẻ một kỹ thuật: chia cho không, hoặc bỏ tham chiếu một con trỏ rỗng.

Câu hỏi chỉ đơn giản là "làm thế nào để ném", không phải làm thế nào để bắt, hoặc thậm chí làm thế nào để ném một loại ngoại lệ cụ thể. Tôi đã gặp một tình huống từ rất lâu trước đây khi chúng tôi cần kích hoạt một ngoại lệ từ C để được bắt trong C ++. Cụ thể, chúng tôi thỉnh thoảng có báo cáo về lỗi "lệnh gọi hàm ảo thuần túy" và cần phải thuyết phục hàm _purecall của thời gian chạy C ném thứ gì đó. Vì vậy, chúng tôi đã thêm hàm _purecall của riêng mình chia cho 0 và boom, chúng tôi có một ngoại lệ mà chúng tôi có thể bắt gặp trên C ++ và thậm chí sử dụng một số trò chơi thú vị để xem mọi thứ đã sai ở đâu.


@AreusAstarte chỉ trong trường hợp ai đó thực sự cần được hiển thị C chia cho 0:int div_by_nil(int x) { return x/0; }

7

Trên Win với MSVC__try ... __except ...nhưng nó thực sự khủng khiếp và bạn không muốn sử dụng nó nếu có thể tránh được. Tốt hơn để nói rằng không có ngoại lệ.


1
Trên thực tế, các ngoại lệ C ++ cũng được xây dựng trên đầu của SEH.
Calmarius

@Calmarius SEH là gì?
Marcel Waldvogel

1
@MarcelWaldvogel Xử lý ngoại lệ có cấu trúc (cách của Windows để xử lý ngoại lệ CPU).
Calmarius


3

Như đã đề cập trong nhiều chủ đề, cách "tiêu chuẩn" để thực hiện việc này là sử dụng setjmp / longjmp. Tôi đã đăng thêm một giải pháp khác như vậy lên https://github.com/psevon/exceptions-and-raii-in-c Đây là giải pháp duy nhất dựa vào việc tự động dọn dẹp tài nguyên được phân bổ. Nó triển khai các điểm thông minh duy nhất và được chia sẻ, đồng thời cho phép các chức năng trung gian cho phép các ngoại lệ đi qua mà không cần bắt và vẫn có các tài nguyên được phân bổ cục bộ của chúng được dọn dẹp đúng cách.


2

C không hỗ trợ các trường hợp ngoại lệ. Bạn có thể thử biên dịch mã C của mình dưới dạng C ++ bằng Visual Studio hoặc G ++ và xem liệu nó có biên dịch nguyên trạng hay không. Hầu hết các ứng dụng C sẽ biên dịch dưới dạng C ++ mà không có thay đổi lớn và sau đó bạn có thể sử dụng cú pháp try ... catch.


0

Nếu bạn viết mã với mẫu thiết kế đường dẫn Happy (tức là cho thiết bị nhúng), bạn có thể mô phỏng xử lý lỗi ngoại lệ (còn gọi là deffering hoặc cuối cùng là mô phỏng) với toán tử "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;
}

Nó không thực sự có trình xử lý ngoại lệ nhưng nó sẽ giúp bạn có một cách để xử lý lỗi khi thoát khỏi fucntion.

PS Bạn nên cẩn thận chỉ sử dụng goto từ các phạm vi tương tự hoặc nhiều hơn và không bao giờ nhảy khai báo biến.

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.