C语言中比goto还要"霸道"的跳转方式

f9a0021caf8beedeb87cec38e667e67a.gif

正文


大家好,我是bug菌~

相信当你看到这个标题,可能隐隐约约知道今天要谈的话题了。

没错,今天跟大家介绍一种比goto还要任性的跳转方式,即C函数库中的如下两个函数:

1//所需头文件
2#include <setjmp.h>
3
4int setjump(jmp_buf buf)
5void longjump(jmp_buf buf, int i) 
6

一些朋友该说了,我从来不用这些跳转,免得出问题。

还是一直以来的那句话,"存在即合理"~

我们来看看这两个函数到底有什么可以推敲的东西。

1

函数介绍

有研究过RTOS的朋友应该对此不难理解,setjump主要是保存当前函数调用点的现场环境(或者叫上下文),比如各种寄存器、堆栈等等,那么这些环境信息就记录在jmp_buf所定义的buf中。

而当我们在其他位置调用longjump函数就相当于一个长跳转,传入之前保存在buf中的信息,即可跳回到之前setjump所调用的位置(理解为恢复setjump所保存的环境也是可以的)。

所以这里值得注意的是不要率先调用longjump,否则程序不知道飞去哪里了。

其实跟RTOS中进行任务切换有着异曲同工之妙。

你大概已经注意到setjump有一个返回值,其主要分为两种情况:

  • 当直接调用setjump函数,则返回0;

  • 当调用longjump跳转到setjump位置,则其返回longjump的第二个非零参数。

2

跟goto有啥区别? 

以前我也跟大家介绍过goto这匹野马被驯服的方式(goto关键字你不知道的"那些事"(C语言提升)),在C语言中goto只能实现函数内部的跳转,无法实现跨函数的直接跳转,比如函数嵌套多层的跳转等等。

当然你也可以借助goto与函数返回配合完成函数之间的跳转,不过那太麻烦了,所以这两个库函数该派上用场了。

这样的跳转太过于霸道,我们还是限制一下,切不可滥用,但其为异常处理代码的模块化带来了福音,在非常多的开源库中都有实际应用。

下面给大家一个参考示例 :

1#include <stdio.h>
 2#include <setjmp.h>
 3
 4jmp_buf mark;
 5int  fperr;
 6void fpcheck(void);
 7
 8/*********************************************
 9 * Function: main
10 * Description : 主任务函数
11 * Note:(公众号:最后一个bug)
12 *********************************************/
13int  main( void )
14{
15    int jmpret;
16
17    //记录异常代码与正常代码分支位置
18    jmpret = setjmp(mark);
19    if( jmpret == 0 )
20    {
21        //正常用户程序运行
22
23    }
24    else
25    {
26        //在正常用户程序运行过程中发生异常
27        fpcheck();  
28    }
29}
30/*********************************************
31 * Function: Errorhandler
32 * Description : 异常中断,在正常用户程序运行过程中发生异常处理函数
33 * Note:(公众号:最后一个bug)
34 *********************************************/
35void Errorhandler(void)
36{
37    fperr = num;
38    longjmp( mark, -1 ); //进行长跳转到异常处理
39}
40
41/*********************************************
42 * Function: fpcheck
43 * Description : 故障处理函数
44 * Note:(公众号:最后一个bug)
45 *********************************************/
46void fpcheck(void)
47{
48
49    switch( fperr )
50    {
51        case INVALID:
52            //user Code 
53            break;
54
55        case OVERFLOW:
56            //user Code 
57            break;
58
59        case ZERODIVIDE:
60            //user Code 
61            break;
62        default:
63            break;
64    }
65
66}

3

局限性

这组函数除了前面介绍的注意事项,还有一个非常值得注意的点就是longjump的调用时机必须在setjump被调用的所在函数返回前。

因为setjump保存有堆栈信息等,一旦setjump的被调用的函数返回则相应的环境会被释放,导致longjump无法在恢复到setjump调用位置,可能造成程序奔溃。

最后

      好了,今天先就跟大家分享这么多了,这一块还有一些东西可以挖掘,后面再整理一下分享出来,如果你觉得有所收获,一定记得点个~,

最后一个bug,bug菌唯一创作平台~

2aee9b22a2cfbc42481a76579f41a09e.png

推荐专辑  点击蓝色字体即可跳转

☞  MCU进阶专辑 932435788670102c97f7bdc2406494c3.gif

☞  嵌入式C语言进阶专辑 cf8e6b5a15d77ee7cde086258c7b1e49.gif

☞  “bug说”专辑 92bfe4681dc60844547664d75b5b9b61.gif

☞ 专辑|Linux应用程序编程大全

☞ 专辑|学点网络知识

☞ 专辑|手撕C语言

☞ 专辑|手撕C++语言

☞ 专辑|经验分享

☞ 专辑|电能控制技术

☞ 专辑 | 从单片机到Linux

be67b105c1faf86dfc837847f7e5cc3e.gif

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值