C 语言中的 try…catch

Try-Catch 机制在 Python、C++ 和 JavaScript 等许多编程语言中都很常见。一般结构如下。

try {
    /*
    Insert some lines of code that will probably give you errors
    */
}
catch {
    /*
    Write some code to handle the errors you're getting.
    */
}

它们允许你编写代码而无需测试每个语句。如果在 try 块中运行的程序遇到异常,则将异常传递给 catch 块。

如果异常与某些异常类型匹配,则执行 catch 块内的代码。否则,异常将传递回 try 块。

C 语言中的 Try-Catch

C 不支持异常处理。至少,它没有任何内置机制。

本指南将演示在 C 语言中提供 try-catch 功能的可能解决方案。应该注意,该解决方案不一定是完整的。

如果没有在遍历堆栈时释放内存的机制,异常处理系统是不完整和安全的,并且 C 没有垃圾收集器。我们可能还需要包含上下文管理器来释放内存。

该解决方案不打算提供完整而广泛的 try-catch 机制。这个概念在技术上可以用来处理一些异常。

我们将逐步构建解决方案,对代码进行更新。我们将使用 C 提供的两个函数,longjmpsetjmp,它们可以从 setjmp.h 头文件中获得。

我们将仔细研究这两个函数的定义。

int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

setjmp 接受一个 jmp_buf 类型的变量。直接调用此函数时,它返回 0

longjmp 接受两个变量,当使用相同的 jmp_buf 变量调用 longjmp 时,setjmp 函数返回与 longjmp 的第二个参数(val)相同的值。

这里的 env 变量本质上是调用环境,代表了寄存器的状态和函数调用时在代码中的位置。当调用 longjmp 时,调用环境中的状态被复制到处理器,并返回存储在 longjmpval 参数中的值。

对于一个简单的 Try-Catch 块,想法是将 Try 语句映射到 if 语句,然后 Catch 语句将成为条件的 else。在这里,我们可以巧妙地利用 setjmp 可以返回不同值的事实。

如果函数返回 0,那么我们知道唯一运行的代码是 TRY 块中的代码。如果函数返回任何其他内容,我们需要以与开始时相同的状态进入我们的 CATCH 块。

当我们 THROW 异常时,我们可以调用 longjmp 函数。

正如你将在下面的代码中看到的,我们还需要关闭 TRY 块。我们创建了一个 ENDTRY 函数,它提供了 do-while 块的结束部分。

这也有助于我们在同一个块中创建多个 TRY 语句。应该注意的是,它们不能嵌套,因为我们将重用 buf_state 变量。

下面是这个实现的一个例子。

#include <stdio.h>#include <setjmp.h>#define TRY do { jmp_buf buf_state; if ( !setjmp(buf_state)) {
#define CATCH } else {
#define ENDTRY }} while(0)
#define THROW longjmp(buf_state, 1)
int main() {
    TRY {
        printf("Testing Try statement \n");
        THROW;
        printf("Statement should not appear, as the THROW block has already thrown the exception \n");
    }
    CATCH {
        printf("Got Exception \n");
    }
    ENDTRY;
    return 0;
}

输出:

Testing Try statement
Got Exception

对于实际系统,这还不够。我们需要有不同类型的异常。

上面的例子只支持一种异常。再一次,我们可以使用 setjmp 的不同返回值。

代替使用 if-else,我们将使用 switch-case 来切换它。

设计如下:TRY 语句将使用 switch 语句,CATCH 将是一个带有参数的宏,表示异常类型。每个 CATCH 语句的条件是它必须使用 break 关闭前一个 case

#include <stdio.h>#include <setjmp.h>#define TRY do { jmp_buf buf_state; switch(setjmp (buf_state)) { case 0:
#define CATCH(x) break; case x:
#define ENDTRY }} while(0)
#define THROW(x) longjmp(buf_state, x)
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
    TRY {
        printf("Inside Try statement \n");
        THROW(EXCEPTION2);
        printf("This does not appear as exception has already been called \n");
    }
    CATCH(EXCEPTION1) {
        printf("Exception 1 called \n");
    }
    CATCH(EXCEPTION2) {
        printf("Exception 2 called \n");
    }
    CATCH(EXCEPTION3) {
        printf("Exception 3 called \n");
    }
    ENDTRY;
    return 0;
}

输出:

Inside Try statement
Exception 2 called

在 C 语言中将 Finally 添加到 Try-Catch

我们需要为完整的功能性 Try-Catch 实现添加一个 FINALLY 块。finally 块通常在 trycatch 块完成后执行。

无论是否抛出异常,它都会执行。

我们将如何实现这一点?关键思想是使用 switch 案例的 default 案例来实现 FINALLY 块。

但是,如果在正常情况下调用了异常,则 switch-case 将不会运行 default 情况。

我们将使用类似于 Duff’s Device 的机制。我们基本上将把 switch-case 语句与 do-while 语句交织在一起。

它的逻辑是这样的。

switch (an expression)
{
    case 0: while (1) {
        // code for case 0
        break;
    case 1:
        // code for case 1
        break;
    }
    default:
        // code for default case
}

我们使用了 C 语言中最具争议的特性之一:在每个 case 标签之前开关不会自动断开。在这里,当调用 break 时,while 语句嵌套在 switch-case 中;它退出 while 循环并继续遍历案例。

在我们的代码上下文中(如下所示),switch 案例是我们所有的异常,default 案例在我们的 FINALLY 块中。自然,它会落入默认情况,因为异常代码已经被执行。

这显示在下面的代码中。

#include <stdio.h>#include <setjmp.h>#define TRY do { jmp_buf buf_state; switch(setjmp (buf_state)) { case 0: while(1) {
#define CATCH(x) break; case x:
#define ENDTRY }} while(0)
#define THROW(x) longjmp(buf_state, x)
#define FINALLY break; } default:
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)
int main() {
    TRY {
        printf("Inside Try statement \n");
        THROW(EXCEPTION2);
        printf("This does not appear as exception has already been called \n");
    }
    CATCH(EXCEPTION1) {
        printf("Exception 1 called \n");
    }
    CATCH(EXCEPTION2) {
        printf("Exception 2 called \n");
    }
    CATCH(EXCEPTION3) {
        printf("Exception 3 called \n");
    }
    FINALLY {
        printf("This will always be called! \n");
    }
    ENDTRY;
    return 0;
}

输出:

Inside Try statement
Exception 2 called
This will always be called!

用 C 语言制作 try-catch 系统的指南到此结束。当然,这里可能存在内存问题和一些限制(例如缺乏对嵌套 try-catch 系统的支持),但这是一个 C 语言中的功能性 try-catch 实现。