网页资讯视频图片知道文库贴吧地图采购
进入贴吧全吧搜索

 
 
 
日一二三四五六
       
       
       
       
       
       

签到排名:今日本吧第个签到,

本吧因你更精彩,明天继续来努力!

本吧签到人数:0

一键签到
成为超级会员,使用一键签到
一键签到
本月漏签0次!
0
成为超级会员,赠送8张补签卡
如何使用?
点击日历上漏签日期,即可进行补签。
连续签到:天  累计签到:天
0
超级会员单次开通12个月以上,赠送连续签到卡3张
使用连续签到卡
08月13日漏签0天
c语言吧 关注:798,975贴子:4,358,789
  • 看贴

  • 图片

  • 吧主推荐

  • 视频

  • 游戏

  • 30回复贴,共1页
<<返回c语言吧
>0< 加载中...

setjmp/longjmp实现简单的异常处理

  • 只看楼主
  • 收藏

  • 回复
  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
之前还有一些没发完的存货,这次再发一点
在下载MinGW发行版时,经常可以看到类似下图的版本选择页面:

那么同一段MinGW源码,内部版本号相同,为什么会有这么多不同的发行版本呢?它们又都代表什么意思呢?
这三段字符的含义是架构-线程模型-异常处理模型,它们分别有下面几种不同的选择:
1.架构:x86_64和i686,分别代表64位编译组件和32位编译组件,它们分别编译64位和32位的程序。
2.线程模型:posix和win32,posix线程模型也就是pthread,win32线程模型也就是Windows原生线程,CreateThread或_beginthread创建的线程。
3.异常处理模型:sjlj, dwarf和seh,其中seh是Windows原生的结构化异常处理,只能用于64位程序;drawf移植自linux,只能用于32位程序(Windows),而sjlj两种架构都可用。
今天要介绍的是异常处理模型中的sjlj,全称是setjmp/longjmp,由两个函数setjmp()和longjmp()构成,它们可以在用户空间实现程序中任意位置的相互跳转,从而实现异常处理、协程等特性。


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
先来看这段最简单的异常处理程序
try
{
printf("Hello World!\n");
throw(1);
printf("This line won't be printed\n");
}
catch(int e)
{
printf("exception catched: 1\n");
}
finally
{
printf("finally\n");
}
程序在打印Hello World!后,抛出异常值1,被catch捕获,打印exception catched: 1,然后进入finally,打印finally。
那么假如你是编译器,你会怎样实现这段逻辑呢?


2025-08-13 07:05:18
广告
不感兴趣
开通SVIP免广告
  • 贴吧用户_G32KK3W280
  • 异能力者
    6
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
为啥 mingw 只有64位的 seh 呢


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
很容易想到的做法是goto语句,它可以实现到指定标签的跳转。只要用goto代替throw,用对应的标签代替catch,就可以模拟这段逻辑了。
{
printf("Hello World!\n");
goto LABEL_1;
printf("This line won't be printed\n");
}
LABEL_1:
{
printf("exception catched: 1\n");
}
{
printf("finally\n");
}
你可能会问:那么finally呢?其实很简单,finally一定会在try/catch之后执行,所以它是顺序结构,什么都不用做就行了
于是我们用相应的宏TRY、CATCH、FINALLY和ENDTRY写出第一个头文件sjlj1.h:

测试一下:


完美


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
真的完美吗?
别忘了异常的一个特性是可以跨函数栈,那么如果我拿出这样的一个程序

在当前函数中抛出异常,上层函数捕获,阁下又该如何应对呢?
很显然这里无法使用goto,因为goto不能跨函数跳转,sjlj1.h在这里将会发生编译错误。那么有什么方式可以实现通用的跨函数跳转吗?当然有,那就是本期的主角setjmp和longjmp。这两个函数位于<setjmp.h>中,它们的原型如下:
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp用于记录当前的上下文env,longjmp用于带值跳转到setjmp记录的上下文env中。这样描述可能仍然一头雾水,不妨用一个简单的示例来说明:


从输出结果中可以注意到12行和19行的printf都没有执行,这是因为18行的longjmp让程序流程直接跳到了主函数的30行,跳过了func1()和func2()中余下的部分。
setjmp()和fork()有点像,它会返回两次,第一次是执行setjmp()函数后的返回值,固定为0;第二次是执行longjmp()时传递的值val,longjmp()函数会从setjmp()函数中返回。这一奇妙的性质可以实现函数间的goto语句。于是我们可以用setjmp/longjmp来实现这个程序。
用setjmp/longjmp重写TRY/CATCH宏,得到第二个版本的sjlj2.h:

预编译后的等价程序是这样的

测试一下

完美


  • XeO2
  • 小吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
原来sjlj是setjmp/longjmp,之前一直都不知道,只下载过dwarf和seh的MinGW


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
longjmp()函数会从setjmp()函数中返回,这种神奇的特性是怎么实现的呢?上下文env又是个什么东西?
这其实没有什么黑魔法。env是个jmp_buf类型的结构体,用于存放各寄存器的值,setjmp()将当前上下文中各寄存器的值保存到env中,而longjmp()从env结构体中恢复各寄存器的值,也包括指令寄存器EIP,于是程序就恢复到setjmp()时的上下文中继续执行了。
setjmp/longjmp这两个函数都是汇编实现的,以x86程序为例
这是jmp_buf结构体的定义

主要保存了6个寄存器ebx、esp、ebp、esi、edi、eip的值。没有保存eax、ecx和edx的值,因为ABI规定它们属于暂存寄存器,值不需要跨函数保存。
setjmp函数的实现

longjmp函数的实现


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
注意longjmp()只能从调用堆栈的栈顶向下跳转,而不能反过来。不能longjmp()跳转到一个已经退出的函数中。因为已退出的函数栈是无效的。以下的代码将导致未定义行为:


2025-08-13 06:59:19
广告
不感兴趣
开通SVIP免广告
  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
那么这次问题完美解决了吗?
别着急,来看看第三个程序sjlj3.c

把头文件换成sjlj2.h试试:

怎么会事呢?
别忘了每级函数调用中都可以有try块,那么sjlj2.h在这个程序中能正常工作吗?显然不能,因为我们只有一个记录上下文的全局变量_sjlj_ctx,而这两个try块每个都要独自记录上下文。这会导致main函数中setjmp的上下文被func_throws_inside中的覆盖。
很显然我们需要一个对应的上下文堆栈,而不是单个变量。继续改进第三版sjlj3.h:

测试一下

完美


  • GTA小鸡
  • 吧主
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
那么这次,真的完美了。。吗?
别高兴得太早,因为还有一种情况叫uncaught exception,如果一个异常一直未被捕获,它将一直向上传播,直到主函数中,然后终止程序。来看看第4个程序sjlj4.c

func_throws_9()函数中抛出了9,而catch覆盖的值只有1和2。把头文件换成sjlj3.h试试

哦豁,好像不太对。异常发生后func中一段不应该被打印的代码被打印了出来,而main函数中也捕获到了错误的异常值。这是为什么呢?
再回看一遍sjlj3.h的实现方式吧。

代码逻辑展开后是这样的:
int sjret = setjmp(push(env_stack));
if (sjret == 0) // try
{
...
}
else if (sjret == num) // catch num
{
...
}
{ // finally
...
}
pop(env_stack);
可以看到缺少了一种“兜底”逻辑:如果所有的else if均未命中,那么应该终止当前函数的执行,并向上传播异常。因此,我们需要一个flag变量,在throw时标记,在catch时清除,并在try块结束时检查flag的值,如果仍被标记则表示存在未捕获的异常,需要longjmp()到上级try块。如果当前已经是顶级try块,则终止进程。
继续改进第4版sjlj4.h:

使用了一个thread_local变量_sjlj_uncaught来记录未捕获的异常,并在ENDTRY中检查,如果值为真则跳转到上个上下文。
测试一下

这次终于得到了一个可用的简单异常处理模型


  • 君木乐
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
这个呢


  • YujiSY
  • 小吧主
    12
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
高质量贴,不愧是吧主


  • GaeL
  • 小吧主
    13
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
,在学协程的时候见过


  • aashhwghh
  • 毛蛋
    1
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
大佬,我是大三学生,我大四找实习时想找c 或c加加工程师的工作,可是我不知道要学习些什么,很迷茫,b站上也没找到学习路线,能不能给我说一下学习路线,和推荐的网课视频啊?


登录百度账号

扫二维码下载贴吧客户端

下载贴吧APP
看高清直播、视频!
  • 贴吧页面意见反馈
  • 违规贴吧举报反馈通道
  • 贴吧违规信息处理公示
  • 30回复贴,共1页
<<返回c语言吧
分享到:
©2025 Baidu贴吧协议|隐私政策|吧主制度|意见反馈|网络谣言警示