如何在 C 中抛出异常?

Posted

技术标签:

【中文标题】如何在 C 中抛出异常?【英文标题】:How can I throw an exception in C? 【发布时间】:2011-02-22 22:06:54 【问题描述】:

我在 Google 中输入了这个,但我只找到了 C++ 中的操作方法。

如何在 C 中做到这一点?

【问题讨论】:

C 不支持异常处理。要在 C 中引发异常,您需要使用特定于平台的东西,例如 Win32 的结构化异常处理——但要提供任何帮助,我们需要知道您关心的平台。 ...并且不要使用 Win32 结构化异常处理。 使用 setjmp() 和 longjmp() 理论上应该可以工作,但我认为这不值得。 【参考方案1】:

C 中没有异常。在 C 中,错误是通过函数的返回值、进程的退出值、给进程的信号(Program Error Signals (GNU libc))或 CPU 硬件中断(或其他通知错误)来通知的如果有,则形成 CPU)(How processor handles the case of division by zero)。

异常是用 C++ 和其他语言定义的。 C++ 中的异常处理在 C++ 标准“S.15 异常处理”中指定,C 标准中没有等效部分。

【讨论】:

所以在 C 中保证不会有异常,如何? @httpinterpret:在 C 中,“保证”没有例外,就像“保证”没有模板、没有反射或没有独角兽一样。语言规范根本没有定义任何这样的事情。不过,有 setjmp/longjmp,可用于退出函数而不返回。因此,程序可以根据需要构建自己的类异常机制,或者 C 实现可以定义标准的扩展。 .c 文件中带有main 的程序可以包含一些C++,因此可能会在程序中抛出和捕获异常,但是C 代码部分将仍然不知道所有这一切除了异常抛出和捕获通常依赖于驻留在 C++ 库中的用 C 语言编写的函数之外。使用 C 是因为你不能冒险调用函数来执行 throw 需要自己抛出异常。不过,可能有一种编译器/库/目标特定的方式来抛出/捕获异常。但是抛出一个类实例会有它自己的问题。 @Steve:如果你发现一种语言有独角兽,请告诉我,我已经等了很多年了。 @BrianR.Bondy Here 是一种独角兽语言(我保证它的方式与在 C 中保证的方式相同)【参考方案2】:

在 C 中,您可以使用 setjmp.h 中定义的 setjmp()longjmp() 函数的组合。 Example from 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 程序中使用它们。 这是一种使用 setjmp 的有趣方法。 on-time.com/ddj0011.htm 但是,是的,如果你想在不展开堆栈的情况下进行带外代码执行,基本上你必须自己发明它们。 当问题是关于 C 和 C++ 时,我不确定为什么在 C++ 中使用它们的警告无论如何都有例外。在任何情况下,重要的是 OP 知道,为了防止 setjmp/longjmp 实现让您的腿受伤,请始终牢记您首先需要访问错误处理程序(设置它),并且该错误处理程序需要返回两次。 顺便说一句,异常处理是我真正希望在 C11 中结束的东西。尽管普遍认为,它确实需要 OOP 才能正常运行,并且 C 会因每个需要 if() 的函数调用而无休止地遭受痛苦。我看不出有什么危险;这里还有其他人吗? @user4229245 我有一种(轶事)印象,任何“为你做”的事情都尽可能地被排除在 c 语言规范之外,特别是为了让 c 与裸机和机器编程保持相关,甚至像 8 位字节这样的现状基本原理并不普遍,只是我的想法,我不是 c 规范作者【参考方案3】:

C 中没有内置异常机制;您需要模拟异常及其语义。这通常是依靠setjmplongjmp来实现的。

周围有很多库,我正在实现另一个库。它被称为exceptions4c;它便携且免费。您可以查看一下,并将其与 other alternatives 进行比较,看看哪个最适合您。

【讨论】:

我读了你的代码示例,我用一个结构开始了类似的项目,但使用 uuid 而不是字符串来识别每个“异常”。你的项目看起来很有前途。 alternatives 链接现在应该指向github.com/guillermocalvo/exceptions4c/wiki/alternatives 您(或其他任何人)知道不同异常包之间的比较吗?【参考方案4】:

普通的旧 C 实际上并不原生支持异常。

您可以使用其他错误处理策略,例如:

返回错误代码 返回 FALSE 并使用 last_error 变量或函数。

见http://en.wikibooks.org/wiki/C_Programming/Error_handling。

【讨论】:

windows api也有同样的方法来处理错误。例如 Windows API 中的 GetLastError()【参考方案5】:

C 能够抛出 C++ 异常。反正是机器码。

例如,在文件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;

在文件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

https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html 有更多关于__cxa_throw 的详细信息。

我不确定它是否可移植,我在 Linux 上使用 'gcc-4.8.2' 对其进行测试。

【讨论】:

什么是“_ZTIl”???我在任何地方都找不到对它的任何引用,这完全是一个谜,C 代码如何能够访问 std::type_info。请...帮帮我! _ZTII 表示typeinfo for long,例如echo '_ZTIl' | c++filt 。这是 g++ 修改方案。 为了更好的措施,请务必在 catch 块中调用 c 符号,以尽可能避免使用 c++ :P(P.S. 感谢您分享一个优秀的真实示例!) 链接已损坏 (404)。 谢谢@PeterMortensen,我已经修复了链接。【参考方案6】:

这个问题非常古老,但我只是偶然发现它并认为我会分享一种技术:除以零,或取消引用空指针。

问题只是“如何抛出”,而不是如何捕获,甚至是如何抛出特定类型的异常。很久以前我遇到过一种情况,我们需要从 C 触发异常以在 C++ 中捕获。具体来说,我们偶尔会报告“纯虚函数调用”错误,需要说服 C 运行时的 _purecall 函数抛出一些东西。所以我们添加了我们自己的 _purecall 函数,该函数除以零,然后,我们得到了一个可以在 C++ 上捕获的异常,甚至使用一些堆栈乐趣来查看哪里出了问题。

【讨论】:

@AreusAstarte 以防万一有人真的需要显示一个 C 除零:int div_by_nil(int x) return x/0; 【参考方案7】:

在带有 Microsoft Visual C++ (MSVC) 的 Windows 上,有 __try ... __except ...,但它真的很可怕,如果可以避免的话,你不想使用它。最好说没有例外。

【讨论】:

实际上 C++ 异常也是建立在 SEH 之上的。 @Calmarius 什么是SEH @MarcelWaldvogel 结构化异常处理(Windows 处理 CPU 异常的方式)。【参考方案8】:

C 没有异常。

有各种 hacky 实现尝试这样做(一个示例位于:http://adomas.org/excc/)。

【讨论】:

【参考方案9】:

正如许多线程中提到的,执行此操作的“标准”方式是使用 setjmp/longjmp。我向https://github.com/psevon/exceptions-and-raii-in-c 发布了另一个这样的解决方案 据我所知,这是唯一依赖于自动清理分配资源的解决方案。它实现了唯一和共享的智能指针,并允许中间函数让异常通过而不捕获,并且仍然可以正确清理它们本地分配的资源。

【讨论】:

【参考方案10】:

C 不支持异常。您可以尝试使用 Visual Studio 或 G++ 将 C 代码编译为 C++,看看它是否会按原样编译。大多数 C 应用程序无需重大更改即可编译为 C++,然后您可以使用 try...catch 语法。

【讨论】:

【参考方案11】:

如果您使用 happy path 设计模式编写代码(例如,对于嵌入式设备),您可以使用运算符“goto”模拟异常错误处理(AKA 延迟或最终模拟)。

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;

它实际上不是一个异常处理程序,但它为您提供了一种在函数退出时处理错误的方法。

P.S.:你应该小心只在相同或更深的范围内使用 goto,并且永远不要跳转变量声明。

【讨论】:

您能否为“延迟”或“最终仿真”添加一些参考?例如,您的意思是 deferring(而不是 "defering")?还是differing? - 也就是说,第一个是否拼写错误?无论如何,您可以添加引用,以便更清楚您的意思吗?【参考方案12】:

Implementing exceptions in CEric Roberts。

C Interfaces and Implementations 的第 4 章,作者 Hanson。

A Discipline of Error Handling 道格·摩恩

Implementing Exceptions in C(详细 E. Roberts 的文章)

【讨论】:

【参考方案13】:

在 C 中我们不能使用 try case 来处理错误。 但如果您可以使用 Windows.h,那么您可以:

#include <stdio.h>
#include <Windows.h>
#include <setjmp.h>
jmp_buf Buf;

NTAPI Error_Handler(struct _EXCEPTION_POINTERS *ExceptionInfo)

    printf("co loi roi ban oi.!!!\r\n");
    longjmp(Buf, 1);


void main()

    AddVectoredExceptionHandler(1, Error_Handler);
    int x = 0;
    printf("start main\r\n");
    if (setjmp(Buf) == 0)
    
        int y = 1 / x;
    
    printf("end main\r\n");
    

【讨论】:

以上是关于如何在 C 中抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

在锁 c#2 中抛出异常

如何在扩展中抛出异常?

C#如何在中间层类中抛出异常时获得控制焦点

StringToByteArray() 在 C# 2.0 中抛出异常

如何在 Symfony2 中抛出 403 异常?

在“错误的行”中抛出异常(C#、Windows 窗体、VS2017)