程序中通常会出现三种错误:用户错误、运行期错误以及异常

欢迎关注我的个人博客:www.wuyudong.com, 更多精彩文章与您分享

标准库函数setjmp和longjmp

在C语言中,标准库函数setjmp和longjmp形成了结构化异常工具的基础。简单的说就是setjmp实例化处理程序,而longjmp产生异常

setjmp和longjmp是C语言所独有的,它们部分弥补了C语言有限的转移能力。与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。

为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。

函数说明:

int setjmp(jmp_buf env)

建立本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回,setjmp返回值为0。如果是从longjmp恢复的程序调用环境返回,setjmp返回非零值。

void longjmp(jmp_buf env, int value)

恢复env所指的缓冲区中的程序调用环境上下文,env所指缓冲区的内容是由setjmp子程序调用所保存。value的值从longjmp传递给setjmplongjmp完成后,程序从对应的setjmp调用处继续执行,如同setjmp调用刚刚完成。如果value传递给longjmp零值,setjmp的返回值为1;否则,setjmp的返回值为value

成员类型:

jmp_buf 数组类型,例如:struct int[16]struct __jmp_buf_tag,用于保存恢复调用环境所需的信息。

jmp_buf 的定义:

typedef struct _jmp_buf
{
int _jp[_JBLEN+];
} jmp_buf[];

这个是 setjmp.h 里的一行定义,把一个 struct 定义成一个数组。这样,在声明 jmp_buf 的时候,可以把数据分配到堆栈上。但是作为参数传递的时候则作为一个指针。

原理非常简单:

1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。

2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)

通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。

本文地址:http://www.cnblogs.com/archimedes/p/c-exception-assert.html,转载请注明源地址。

一个简单的例子:

#include <stdio.h>
#include <setjmp.h> static jmp_buf buf;
void second(void) {
printf("second\n"); // 打印
longjmp(buf,); // 跳回setjmp的调用处 - 使得setjmp返回值为1
}
void first(void) {
second();
printf("first\n"); // 不可能执行到此行
}
int main() {
if ( ! setjmp(buf) ) {
first(); // 进入此行前,setjmp返回0
} else { // 当longjmp跳转回,setjmp返回1,因此进入此行
printf("main\n"); // 打印
}
return ;
}

运行结果:

second
main

在下例中,setjmp被用于包住一个例外处理,类似trylongjmp调用类似于throw语句,允许一个异常返回给setjmp一个异常值。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h> void first(void);
void second(void);
static jmp_buf exception_env;
static int exception_type; int main(void) {
void *volatile mem_buffer; mem_buffer = NULL; if (setjmp(exception_env)) {
/* 如果运行到这将产生一个异常*/
printf("first failed, exception type %d\n", exception_type);
} else {
printf("calling first\n");
first();
mem_buffer = malloc(); /* 分配内存 */
printf("%s",strcpy((char*)mem_buffer, "first succeeded!")); /* ... 不会被执行 */
}
if (mem_buffer)
free((void*) mem_buffer); /* 小心释放内存 */
return ;
} void first(void) {
jmp_buf my_env; printf("calling second\n");
memcpy(my_env, exception_env, sizeof(jmp_buf));
switch (setjmp(exception_env)) {
case :
/* 如果运行到这,表示有异常 */
printf("second failed with type 3 exception; remapping to type 1.\n");
exception_type = ; default:
memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
longjmp(exception_env, exception_type); /* continue handling the exception */ case :
/* normal, desired operation */
second();
printf("second succeeded\n"); /* not reached */
}
memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
} void second(void) {
printf("entering second\n" );
exception_type = ;
longjmp(exception_env, exception_type);
printf("leaving second\n");
}

运行结果:

calling first
calling second
entering second
second failed with type 3 exception; remapping to type 1.
first failed, exception type 1

接口

Except接口在一系列宏指令和函数中包装了setjmp和longjmp,它们一起提供了一个结构化异常处理工具

异常是Except_T类型的一个全局或静态变量:

#ifndef EXCEPT_INCLUDED
#define EXCEPT_INCLUDED
#include <setjmp.h>
#define T Except_T
typedef struct T {
char *reason;
} T;

Except_T结构只有一个字段,它可以初始化为一个描述异常的字符串,当发生一个未处理的异常时,才把字符串打印出来

异常处理程序处理的是异常的地址。异常必须是全局的或静态的变量,因此它们的地址唯一地标志了它们,异常e由宏指令引发或由函数引发:

#define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
void Except_raise(const T *e, const char *file,int line);

处理程序是由TRY-EXCEPT和TRY-FINALLY语句来实例化的,这两个语句用宏指令实现,这两个语句可以处理嵌套异常,也可以管理异常状态的数据

TRY-EXCEPT的语法是:

TRY

  S

EXCEPT(e1)

  S1

EXCEPT(e2)

  S2

……

EXCEPT(en)

  Sn

ELSE

  S0

END_TRY

看下面的代码:

int Allocation_handle = ;
jmp_buf Allocate_Failed; void *allocate(unsigned n)
{
void *new = malloc(n);
if(new)
return new;
if(Allocation_handle)
longjmp(Allocate_Failed, );
assert();
} char *buf;
Allocation_handle = ;
if(setjmp(Allocate_Failed)) {
fprintf(stderr, "cound't allocate the buff\n");
exit(EXIT_FAILURE);
}
buf = allocate();
Allocation_handle = ;

上面的代码没有提供嵌套的处理程序,Allocation_handle标志的使用也很麻烦。

把Allocation_Failed变成一个异常,该异常是在malloc返回一个空指针时由allocate引发:

Except_T Allocate_Failed = {"Allocation failed"};
void *allocate(unsigned n)
{
void *new = malloc(n);
if(new)
return new;
RAISE(Allocation_Failed);
assert();
}

如果客户调用程序代码想处理这个异常,那么它需要在TRY-EXCEPT语句内调用allocate:

extern Except_T Allocate_Failed;
char *buf;
TRY
buf = allocate();
EXCEPT(Allocate_Failed)
fprintf(stderr, "could't allocate the buff\n");
exit(EXIT_FAILURE);
END_TRY;

TRY-EXCEPT语句是用setjmp和longjmp来实现的

TRY-FINALLY语句的语法是:

TRY

  S

FINALLY

  S1

END_TRY

如果S没有产生任何异常,那么执行S1,然后继续执行END_TRY,如果S产生了异常,那么S的执行被中断,控制立即转给S1。S1执行完后,引起S1执行的异常重新产生,使得它可以由前一个实例化的处理程序来处理。注意:S1是在两种情况中都必须执行的,处理程序可以用RERAISE宏指令显示地重新产生异常

#define RERAISE Except_raise(Except_frame.exception, \
Except_frame.file, Except_frame.line)

接口中的最后一个宏指令是:

#define RETURN switch (Except_stack = Except_stack->prev,0) default: return

RETURN宏指令用在TRY语句的内部,用来代替return语句

实现

Except接口中的宏指令和函数一起维护了一个记录异常状态以及实例化处理结构的堆栈。结构中的字段env就是setjmp和longjmp使用的某个jmp_buf,这个堆栈可以处理嵌套的异常

typedef struct Except_Frame Except_Frame;
struct Except_Frame {
Except_Frame *prev;
jmp_buf env;
const char *file;
int line;
const T *exception;
};
extern Except_Frame *Except_stack;

Except_stack指向异常栈顶的异常帧,每个帧的prev字段指向它的前一帧,产生一个异常就是将异常的地址存在exception字段中,并分别在file和line字段中保存异常的附属信息--异常产生的文件以及行号

TRY从句将一个新的Except_Frame压入异常栈,并调用setjmp,由RAISE和RERAISE调用Except_raise填充栈顶帧的字段exception、file和line,从异常栈中弹出栈顶Exception_Frame,然后调用longjmp,EXCEPT从句检查该帧中的exception字段,决定应该用哪个处理程序。FINALLY从句执行清除代码,并重新产生已弹出的异常帧中存储的异常。

宏指令TRY、EXCEPT、ELSE、FINALLY_TRY一起将TRY-EXCEPT语句转化成如下形式的语句:

do {

  creat and push an Except_Frame

  if(first return from setjmp) {

    S

  } else if (exception is e1) {

    S1

  ……

  } else if (exception is en) {

    Sn

  } else {

    S0

  }

  if(an exception occurrend and wasn't handled)

    RERAISE;

} while(0)

Exception_Frame的空间分配很简单,在由TRY开始的do-while主体中的复合语句内部声明一个该类型的局部变量即可:

#define TRY do { \
volatile int Except_flag; \
Except_Frame Except_frame; \
Except_frame.prev = Except_stack; \
Except_stack = &Except_frame; \
Except_flag = setjmp(Except_frame.env); \
if (Except_flag == Except_entered) {

在TRY语句内有四种状态,由下面的枚举标识符给出

enum { Except_entered=, Except_raised,
Except_handled, Except_finalized };

setjmp的第一个返回值将Except_flag设置为Except_entered,表示进入TRY语句,并且将某个异常帧压入异常栈,Except_entered必须为0,因为setjmp首次调用的返回值为0,随后,setjmp的返回值将被设为Except_raised,表示发生了异常,处理程序将Except_flag的值设成Except_handled,表示处理程序已经对异常进行了处理。

#define TRY do { \
volatile int Except_flag; \
Except_Frame Except_frame; \
Except_frame.prev = Except_stack; \
Except_stack = &Except_frame; \
Except_flag = setjmp(Except_frame.env); \
if (Except_flag == Except_entered) {
#define EXCEPT(e) \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} else if (Except_frame.exception == &(e)) { \
Except_flag = Except_handled;
#define ELSE \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} else { \
Except_flag = Except_handled;
#define FINALLY \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} { \
if (Except_flag == Except_entered) \
Except_flag = Except_finalized;
#define END_TRY \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} if (Except_flag == Except_raised) RERAISE; \
} while ()

最后实现代码如下:

#include <stdlib.h>
#include <stdio.h>
#include "assert.h"
#include "except.h"
#define T Except_T
#ifdef WIN32
__declspec(thread)
#endif
Except_Frame *Except_stack = NULL;
void Except_raise(const T *e, const char *file,
int line) {
Except_Frame *p = Except_stack;
assert(e);
if (p == NULL) {
fprintf(stderr, "Uncaught exception");
if (e->reason)
fprintf(stderr, " %s", e->reason);
else
fprintf(stderr, " at 0x%p", e);
if (file && line > )
fprintf(stderr, " raised at %s:%d\n", file, line);
fprintf(stderr, "aborting...\n");
fflush(stderr);
abort();
}
p->exception = e;
p->file = file;
p->line = line;
Except_stack = Except_stack->prev;
longjmp(p->env, Except_raised);
}

参考资料

《C语言接口与实现--创建可重用软件的技术》

C语言异常与断言接口与实现的更多相关文章

  1. Java核心技术卷一基础知识-第11章-异常、断言、日志和调试-读书笔记

    第11章 异常.断言.日志和调试 本章内容: * 处理错误 * 捕获异常 * 使用异常机制的技巧 * 使用断言 * 日志 * 调试技巧 * GUI程序排错技巧 * 使用调试器 11.1 处理错误 如果 ...

  2. 初识 go 语言:方法,接口及并发

    目录 方法,接口及并发 方法 接口 并发 信道 结束语 前言: go语言的第四篇文章,主要讲述go语言中的方法,包括指针,结构体,数组,切片,映射,函数闭包等,每个都提供了示例,可直接运行. 方法,接 ...

  3. Atitti 跨语言异常的转换抛出 java js

    Atitti 跨语言异常的转换抛出 java js 异常的转换,直接反序列化为json对象e对象即可.. Js.没有完整的e机制,可以参考java的实现一个stack层次机制的e对象即可.. 抛出Ru ...

  4. 两张图解读Java异常与断言

    两张图解读Java异常与断言                                 --转载请注明出处:coder-pig 本节引言: 前天公布的"七张图解析Java多线程&quo ...

  5. Go语言 异常panic和恢复recover用法

    Go语言 异常panic和恢复recover用法 背景:Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在 ...

  6. Atitit.跨语言异常转换机制 java c# php到js的异常转换

    Atitit.跨语言异常转换机制 java c# php到js的异常转换 1. bizEx   直接抓取,然后js catchEX1 2. Chkec runtimeEx1 3. Other异常..J ...

  7. Go语言_方法和接口

    方法和接口 本节课包含了方法和接口,可以用这种构造来定义对象及其行为. Go 作者组编写,Go-zh 小组翻译. https://tour.go-zh.org/methods/1 方法 Go 没有类. ...

  8. Java核心技术-异常、断言和日志

    程序发生错误时至少做到以下几点: *向用户通告错误 *保存所有的工作结果 *允许用户以妥善的形式退出程序 Java使用一种称为异常处理的错误捕获机制处理异常. 本章第一部分介绍Java的异常,第二部分 ...

  9. .Java中的异常、断言、日志【草稿下,Log4j专题】

    (本章主要讲解Java里面比较核心的一块内容--异常处理,Java异常处理机制,一致都是比较复杂的一块,而很多时候如果写程序的时候能够适当地注意对应的一些异常处理情况,那么就会在开发过程节省一大部分时 ...

随机推荐

  1. 自定义一个叫 ReadOnlyXmlMembershipProvider 的 MembershipProvider,用 XML 作为用户储藏室

    1. 配置 web.config <membership defaultProvider="AspNetReadOnlyXmlMembershipProvider"> ...

  2. 在 VS 中嵌套文件

    效果如下: 用到扩展工具:NestIn 可以通过 VS->工具->扩展管理器->联机库 搜索安装. In WPF: How to create resource dictionary ...

  3. Unity 中的协同程序

    今天咱就说说,协同程序coroutine.(这文章是在网吧敲的,没有unity,但是所有结论都被跑过,不管你信得过我还是信不过我,都要自己跑一下看看,同时欢迎纠错)先说说啥是协程:协同程序是一个非常让 ...

  4. VirtualBox Bridged 无线网卡

    启动虚拟机后选择右键单击右下角的网络链接图标,  弹出的窗口中选择Bridged Adapter,  wlan0 然后选择OK 查看virtual Box主页面中setting中网络的配置是否和刚才一 ...

  5. 看那记不住命令的猿,如何使用GitHub

    什么是GitHub呢? GitHub是什么?好吧, 请看百科:http://baike.baidu.com/view/3366456.htm 准备:msysgit.tortoisegit 首先,我们得 ...

  6. Unity3D 角色(物体) 移动方法 合集

    1. 简介 在Unity3D中,有多种方式可以改变物体的坐标,实现移动的目的,其本质是每帧修改物体的position. 2. 通过Transform组件移动物体 Transform 组件用于描述物体在 ...

  7. Android开发总结

    出来工作半年多了,没啥好交代的,就说说自己半年来的Android开发经历. 1.IDE      这半年来,从Eclipse到Android Studio,经历了两个IDE,在这里做一下简单的评价. ...

  8. SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第1部分)

    为了缩小读取操作所涉及范围,本文首先着眼于简单的SELECT查询,然后引入执行更新操作有关的附加过程.最后你会读到,优化性能时SQLServer使用还原工具的相关术语和流程. 关系和存储引擎 如图所示 ...

  9. Django--models一对多实例

    需求 models一对多表的构建,创建数据,查询数据,数据前端展示等​. 速查 1.创建数据 1 2 all_data = obj.clean()  #{'username': u'user1', ' ...

  10. Enum Helper

    public static class EnumHelper { #region get /// <summary> /// 获得枚举类型所包含的全部项的列表 /// </summa ...