在 C 语言中,我们不能使用 goto 语句来跳转到另一个函数中的某个 label 处;但提供了两个函数——setjmp 和 longjmp来完成这种类型的分支跳转。后面我们会看到这两个函数在处理异常上面的非常有用。

setjmp 和 longjmp 使用方法

我们都知道要想在一个函数内进行跳转,可以使用 goto 语句(不知怎么该语句在中国学生眼中就是臭名昭著,几乎所有国内教材都一刀切地教大家尽量不要使用它,但在我看来,这根本不是语言的问题,而是使用该语言的人,看看 Linux 内核中遍地是 goto 语句的应用吧!),但如果从一个函数内跳转到另一个函数的某处,goto 是不能完成的,那该如何实现呢?

函数间跳转原理

我们要实现的一个 GOTO 语句(我自己定义的),能实现在函数间进行任意跳转,如下例,在函数 g() 中有条语句GOTO Label; 可以跳转到 f() 函数的 Label: 标签所指向的位置,那么我们该如何实现呢?

void f()
{
//...
Label:
//...
} void g()
{
//...
GOTO Label;
//...
}

首先我们要知道,实现这种类型的跳转,和操作系统中任务切换的上下文切换有点类似,我们只需要恢复 Label 标签处函数上下文即可。函数的上下文包括以下内容:

  • 函数栈帧,主要是栈帧指针BP和栈顶指针SP
  • 程序指针PC,此处为指向 Label 语句的地址
  • 其它寄存器,这是和体系相关的,在 x86 体系下需要保存有的 AX/BX/CX 等等 callee-regs。

这样,在执行 GOTO Label; 这条语句,我们恢复 Label 处的上下文,即完成跳转到 Label 处的功能。

如果你读过 Linux 操作系统进程切换的源码,你会很明白 Linux 会把进程的上下文保存在 task_struct 结构体中,切换时直接恢复。这里我们也可以这样做,将 Label 处的函数上下文保存在某个结构体中,但执行到 GOTO Label 语句时,我们从该结构体中恢复函数的上下文。

这就是函数间进行跳转的基本原理,而 C 语言中 setjmp 和 longjmp 就为我们完成了这样的保存上下文和切换上下文的工作。

函数原型

#include <setjmp.h>
int setjmp(jmp_buf env);

setjmp 函数的功能是将函数在此处的上下文保存在 jmp_buf 结构体中,以供 longjmp 从此结构体中恢复。

  • 参数 env 即为保存上下文的 jmp_buf 结构体变量;
  • 如果直接调用该函数,返回值为 0; 若该函数从 longjmp 调用返回,返回值为非零,由 longjmp 函数提供。根据函数的返回值,我们就可以知道 setjmp 函数调用是第一次直接调用,还是由其它地方跳转过来的。
void longjmp(jmp_buf env, int val);

longjmp 函数的功能是从 jmp_buf 结构体中恢复由 setjmp 函数保存的上下文,该函数不返回,而是从 setjmp 函数中返回。

  • 参数 env 是由 setjmp 函数保存过的上下文。
  • 参数 val 表示从 longjmp 函数传递给 setjmp 函数的返回值,如果 val 值为0, setjmp 将会返回1,否则返回 val。
  • longjmp 不直接返回,而是从 setjmp 函数中返回,longjmp 执行完之后,程序就像刚从 setjmp 函数返回一样。

简单实例

下面是个简单的例子,虽然还只是函数内跳转,但足以说明这两个函数的功能了。

运行该程序得到的结果为:

i = 0
i = 2

C 语言异常处理

Java、C# 等面向对象语言中都有异常处理的机制,如下就是典型的 Java 中异常处理的代码,两个数相除,如果被除数为0抛出异常,在函数 f() 中可以获取该异常并进行处理:

double divide(double to, double by) throws Bad {
if(by == 0)
throw new Bad ("Cannot / 0");
return to / by;
} void f() {
try {
divide(2, 0);
//...
} catch (Bad e) {
print(e.getMessage());
}
print("done");
}

在 C 语言中虽然没有类似的异常处理机制,但是我们可以使用 setjmp 和 longjmp 来模拟实现该功能,这也是这两个函数的一个重要的应用:

static jmp_buf env;

double divide(double to, double by)
{
if(by == 0)
longjmp(env, 1);
return to / by;
} void f()
{
if (setjmp(env) == 0)
divide(2, 0);
else
printf("Cannot / 0");
printf("done");
}

如果复杂一点,可以根据 longjmp 传递的返回值来判断各种不同的异常,来进行区别的处理,代码结构如下:

switch(setjmp(env)):
case 0: //default
//...
case 1: //exception 1
//...
case 2: //exception 2
//...
//...

关于使用 C 语言来处理异常,可以参见这篇文章,介绍了更多复杂的结构,但无外乎就是 setjmp 和 longjmp 的应用。

参考资料

C 语言中 setjmp 和 longjmp的更多相关文章

  1. C语言中setjmp与longjmp学习笔记

    C语言中setjmp与longjmp学习笔记 一.基础介绍 头文件:#include<setjmp.h> 原型:  int setjmp(jmp_buf envbuf) ,然而longjm ...

  2. C中的setjmp与longjmp

    setjmp与longjmp是属于C语言中的,当然,C++也会有这两个函数了.他们的原型如下: int setjmp( jmp_buf env ); 作用:第一次调佣时,将寄存器的当前状态信息全部存入 ...

  3. 【转载】setjmp和longjmp函数使用详解

    [说明]本文上半部分转载自 wykwdy007 的转载文章 http://blog.csdn.net/wykwdy007/article/details/6535322 --------------- ...

  4. setjmp和longjmp函数使用详解

    源地址:http://blog.csdn.net/zhuanshenweiliu/article/details/41961975 非局部跳转语句---setjmp和longjmp函数.非局部指的是, ...

  5. C语言中file文件指针概念及其操作 (转载)

    文件 文件的基本概念 所谓"文件"是指一组相关数据的有序集合. 这个数据集有一个名称,叫做文件名.实际上在前面的各章中我们已经多次使用了文件,例如源程序文件.目标文件.可执行文件. ...

  6. (C)非局部跳转语句(setjmp和longjmp)

    1. 特点 非goto语句在函数内实施跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一语句. 头文件包含#include Void longjmp(jmp_buf env,int val ...

  7. C语言中,头文件和源文件的关系(转)

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句, ...

  8. c语言中的scanf在java中应该怎么表达,Scanner类。

    1 java是面向对象的语言 它没有像C语言中的scanf()函数,但是它的类库中有含有scanf功能的函数 2 java.util包下有Scanner类 Scanner类的功能与scanf类似 3 ...

  9. C语言中do...while(0)的妙用(转载)

    转载来自:C语言中do...while(0)的妙用,感谢分享. 在linux内核代码中,经常看到do...while(0)的宏,do...while(0)有很多作用,下面举出几个: 1.避免goto语 ...

随机推荐

  1. sessionPageState与视图状态存储

    这个配置节甚是简单,在MSDN中的介绍也甚是简单:为 ASP.NET 应用程序配置页的视图状态设置. historySize的作用是设置要存储在页历史记录中的项数. 但是这根本是看不明白他是干嘛的,百 ...

  2. 如何在IIS添加MIME扩展类型

    在iis7中默认的MIME类型并不包含所有的后缀名文件,像现在比较热门的apk,ipa文件都是需要手动添加的. 那如何在IIS添加MIME类型?步骤如下: 1.打开iis7,选择你要设置网站,打开mi ...

  3. java protected 的细节

    1. java的权限控制--大部分人都被错误洗脑了. 一个重大的坑,或者一个重大的误区,或者说一个洗脑了成千上万java编程者的错误概念就是: public private protected 是基于 ...

  4. c++ const 成员函数

    第一个事实: 某类中可以这么声明定义两个函数,可以重载(overload) void pa(){ cout<<"a"<<endl; } void pa() ...

  5. Java开发web的几种开发模式

    Java Web开发方案有多种可供选择,这里列举一些经典的开发模式进行横向比较,为Java Web的开发模式选择提供参考.除此之外还有好多方案(如Tapestry和Wicket等等)并不了解,这里就不 ...

  6. POI导入导出

    一.使用POI导出Execl表格 需要的jar包 package cn.yxj.poi; import java.io.FileOutputStream; import java.util.Date; ...

  7. 网页中tab标签切换分别用jquery和javascript源码实现

    //HTML布局<ul id="tabTitle"> <li class="active">HTML5</li> <l ...

  8. 天津政府应急系统之GIS一张图(arcgis api for flex)讲解(四)地图导航控件模块

    config.xml文件的配置如下: <widget left="10" top="50" config="widgets/Navigation ...

  9. maven:用appassembler-maven-plugin打包含有自定义目录的JAVA程序

    问题说明: 用maven构建了一个项目,目录结构如下: appassemblerd的配置: <plugin> <groupId>org.codehaus.mojo</gr ...

  10. GeoEvent使用问题及解决方法整理

    假如GeoEvent的部署环境是一个典型的WebGIS架构(Portal+GIS Server),往往会遇到一些问题,例如: 问题:发布的StreamService流服务无法查看. 原因:默认发布的S ...