如何利用C语言中的setjmp和longjmp实现异常捕获和协程?

道哥分享

    这是道哥的第 017 篇原创
    一、前言
    二、函数语法介绍
    与 goto 语句比较
    与 fork 函数比较
    与 Python 语言中的 yield/resume 比较
    三、利用 setjmp/longjmp 实现异常捕获
    四、利用 setjmp/longjmp 实现协程
    五、总结
    一、前言
    在 C 标准库中,有两个威力很猛的函数:setjmp 和 longjmp,不知道各位小伙伴在代码中是否使用过?我问了身体的几位同事,一部分人不认识这两个函数,有一部分人知道这个函数,但从来没有使用过。
    从知识点范围来看,这两个函数的功能比较单纯,一个简单的示例代码就能说清楚了。但是,我们需要从这个知识点进行发散、思考,在不同的维度上,把这个知识点与这个编程语言中其它类似的知识进行联想、对比;与其他编程语言中类似的概念进行比较;然后再思考这个知识点可以使用在哪些场合,别人是怎么来使用它的。
    今天,我们就来掰扯掰扯这两个函数。虽然在一般的程序中使用不上,但是在今后的某个场合,当你需要处理一些比较奇特的程序流程时,也许它们可以给你带来意想不到的效果。
    例如:我们会把 setjmp/longjmp 与  goto 语句进行功能上的比较;与 fork 函数从返回值上进行类比;与 Python/Lua 语言中的协程进行使用场景上的比较。
    二、函数语法介绍 
    1. 最简示例
    先不讲道理,直接看一下这个最简单的示例代码,看不懂也没关系,混个脸熟:
    int main(){    // 一个缓冲区,用来暂存环境变量    jmp_buf buf;    printf("line1 ");        // 保存此刻的上下文信息    int ret = setjmp(buf);    printf("ret = %d ", ret);        // 检查返回值类型    if (0 == ret)    {        // 返回值0:说明是正常的函数调用返回        printf("line2 ");                // 主动跳转到 setjmp 那条语句处        longjmp(buf, 1);    }    else    {        // 返回值非0:说明是从远程跳转过来的        printf("line3 ");    }    printf("line4 ");    return 0;}
    执行结果:
    
    执行顺序如下(如果不明白就不要深究,看完下面的解释再回过头来看):
    
    2. 函数说明
    首先来看下这个 2 个函数的签名:
    int setjmp(jmp_buf env);void longjmp(jmp_buf env, int value);
    它们都在头文件 setjmp.h 中进行声明,维基百科的解释如下:
    setjmp: Sets up the local jmp_buf buffer and initializes it for the jump. This routine saves the program's calling environment in the environment buffer specified by the env argument for later use by longjmp. If the return is from a direct invocation, setjmp returns 0. If the return is from a call to longjmp, setjmp returns a nonzero value。
    longjmp:Restores the context of the environment buffer env that was saved by invocation of the setjmp routine in the same invocation of the program. Invoking longjmp from a nested signal handler is undefined. The value specified by value is passed from longjmp to setjmp. After longjmp is completed, program execution continues as if the corresponding invocation of setjmp had just returned. If the value passed to longjmp is 0, setjmp will behave as if it had returned 1; otherwise, it will behave as if it had returned value。
    下面我再用自己的理解把上面这段英文解释一下:
    setjmp 函数
    功能:把执行这个函数时的各种上下文信息保存起来,主要就是一些寄存器的值;参数:用来保存上下文信息的缓冲区,相当于把当前的上下文信息拍一个快照保存起来;返回值:有 2 种返回值,如果是直接调用 setjmp 函数时,返回值是 0;如果是调用 longjmp 函数跳转过来时,返回值是非 0;  这里可以与创建进程的函数 fork 进行一下类比。
    longjmp 函数
    功能:跳转到参数 env 缓冲区中保存的上下文(快照)中去执行;参数:env 参数指定跳转到哪个上下文中(快照)去执行, value 用来给 setjmp 函数提供返回判断信息,也就是说:调用 longjmp 函数时,这个参数 value 将会作为 setjmp 函数的返回值;返回值:没有返回值。因为在调用这个函数时,就直接跳转到其他地方的代码去执行了,不会再回来了。
    小结:这 2 个函数是配合使用的,用来实现程序的跳转。
    
    
    1  2  下一页>