让 C 支持 try-catch

C 语言开发中,Segement Fault 段错误一定是老相识,错误释放内存、访问悬空指针、内存越界,总有一款适合你。一般情况下,一旦出现段错误,程序就会抛出异常,然后彻底罢工了,用户非常崩溃,开发者似乎只能尴尬的说 “怪我,对不起咯”。

实际上,除了眼睁睁的看着段错误发生,还是可以做一些补救工作的,比如,让一切不要那么尴尬。

两个函数

实际上,当出现内存异常的时候,会抛出 SIGSEGV 信号,默认情况下操作系统会将对应的进程停止。如果我们捕获了进程的 SIGSEGV 信号,就可以自己决定发生段错误时怎么办。如果想结果信号处理,只需要借助 signal 函数就可以实现目的。可是,这只能让进程不尴尬的退出,如果希望进程可以从错误中恢复,就需要借助两个帮手:

  • int sigsetjmp(sigjmp_buf env, int savesigs)
  • void siglongjmp(sigjmp_buf env, int savesigs)

sigsetjmp 会保存调用时所有寄存器状态到 env 中,之后某个时刻可以再借助 siglongjmp 和 env 跳转回刚才的位置。sigsetjmp 相当于对 CPU 进行快照,siglongjmp 则会将这个快照恢复给 CPU,就像从未被打断一样。效果上与 goto 语句非常接近,goto 只能在函数内跳转,siglongjmp 可以在函数间 “goto”。

注意到 sigsetjmp 会被执行两次(如果调用了 siglongjmp),可以通过返回值确定当前是哪一次执行:

  • 第一次进行 “快照” 执行, 返回值为 0;
  • 从 siglongjmp 处 “跳转” 回来执行,返回值 !0;

从异常中恢复

对于一段可能出现异常的代码:

char * ptr = NULL;
char c = *ptr;
复制代码

显然执行到第二行会触发 SIGSEGV 信号。

我们可以在这两行代码前注册 SIGSEGV 信号处理回调,接管系统默认行为;在异常代码前调用sigsetjmp, 再巧妙的将 siglongjump 安排到信号处理回调函数中,就可以实现从异常中恢复。

image.png

第一次执行 flag = sigsetjmp(env, 1),flag 为 0,执行异常代码,当访问 NULL 指针触发段错误时,由信号处理回调接手,并借助 siglongjump 再一次回到 flag = sigsetjmp(env, 1), flag 不为 0,这样我们就知道,应该执行恢复代码 printf(“exception!\n”),并继续执行之后的代码。

一点 tricks

大概总结一下,如何在 C 中实现 try-catch 的效果:

  • 异常代码块前,保留 CPU 寄存器状态,根据 flag 决定之后的路径;
  • flag 为 0 则第一次执行,运行异常代码;
  • flag 非 0,则已经发生异常,并从信号处理回调返回,运行恢复代码。

我们将前面提到的一些函数改写为宏,可以看起来像这样:

    TRY
        char * a = NULL;
        *a = 'x';
    /*catch segement falut*/
    CATCH_ALL
        /* segement fault catch */
        printf("exception!\n");
    ENDTRY
复制代码

这里只实现了捕获段错误,实际上可以添加更多信号类型,支持更多异常捕获。

你可以在 c_exception 中看到完整的实现,其中还实现了除零异常、用户抛出异常。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享