[TOC]
对错误处理而言,goto 语句只能是在函数的局部作用域内跳转,而不能实现跨函数跳转。但在实际错误处理中,经常会遇到这样的场景:在一个深度嵌套的函数调用中发生了错误,需要放弃当前任务,从多层函数调用中返回,并且在较高层级的函数中继续执行(或许是在 main() 函数中)。要做到这一点,可以让每个函数都返回一个状态值,由函数的调用者检查并做相应处理。
很显然,遇到这种场景,goto 语句就显得无能为力了,而 setjmp 宏和 longjmp 函数组合就提供了这一功能,它们能够提供一种在程序中实现“非本地局部跳转”(non-local goto)的机制。其原型定义如下:
~~~
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int value);
~~~
从上面的原型定义中可以看出,setjmp 宏与 longjmp 函数都使用 jmp\_buf 结构作为形参以保存程序当前的堆栈环境,它们的调用关系是这样的:
程序首先需要调用 setjmp 宏函数来初始化 jmp\_buf 结构体变量 env,并将当前的堆栈环境信息存入 env 中,为以后调用 longjmp 函数恢复原来环境信息时使用。如果是直接调用 setjmp,那么它的返回值为 0;如果由于调用 longjmp 函数而调用 setjmp,那么它的返回值为非 0。由此可见,setjmp 宏函数能返回两次:
* 第一次是初始化时,返回值为 0。
* 第二次则是在遇到 longjmp 函数调用后,longjmp 函数将使 setjmp 发生第二次返回,返回值由 longjmp 函数的第二个参数给出,返回值为非 0。
相对于 setjmp 函数用于保存程序的运行时的当前堆栈环境,longjmp 函数则用于恢复在先前程序中调用 setjmp 函数时所保存的堆栈环境。当调用 longjmp 函数时,它会根据变量 env 所保存的堆栈环境来恢复先前的环境。与此同时,longjmp 函数的 value 参数值会被 setjmp 函数返回,程序继续得以执行。如下面的示例代码所示:
~~~
#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
void F2(void)
{
printf("F2()\n");
longjmp(buf,1);
}
void F1(void)
{
F2();
printf("F1()\n");
}
int main(void)
{
int jmpret = setjmp(buf);
if(!jmpret)
{
F1();
}
else
{
printf("继续执行main\n");
}
return 0;
}
~~~
运行结果为:
F2()
继续执行main
从上面的代码中可以看出,对 goto 语句而言,setjmp 宏与 longjmp 函数不仅能够实现跨函数的全局跳转,而且其错误处理方式也比 goto 语句优雅得多。但是,在使用 setjmp 宏与 longjmp 函数进行错误处理时,必须注意如下两点。
1. setjmp 宏与 longjmp 函数组合使用时,它们必须有严格的先后执行顺序。
也就是说,必须先调用 setjmp 来初始化 jmp\_buf 结构体变量 env 之后,才能够调用 longjmp 函数来恢复到先前被保存的堆栈环境(即程序执行点)。如果在 setjmp 调用之前执行 longjmp 函数,那么将导致程序的执行流变得不可预测,很容易导致程序崩溃而退出。
2. longjmp函数必须在setjmp的作用域之内。
在一个函数中使用 setjmp 来初始化一个全局变量(jmp\_buf buf)buf 之后,只要这个函数没有被返回,那么在其他任何地方都可以通过 longjmp 调用来跳转到 setjmp 的下一条语句执行。也就是说,setjmp 将发生调用处的局部堆栈环境保存在一个 jmp\_buf 结构体变量 env 中,只要主调函数中对应的内存未曾释放,在调用 longjmp 的时候就可以根据已保存的 jmp\_buf 参数恢复到 setjmp 的地方执行。
其实,从上面的程序示例代码中不难看出,如果说 goto 语句使程序变得难以阅读,那么 setjmp 宏与 longjmp 函数组合这种非局部跳转会让整个代码的糟糕程度增加一个数量级,因为它能在程序中的任意多个函数间传递控制。因此,应当谨慎使用它们,尽可能避免在实际编码中使用 setjmp 宏与 longjmp 函数组合。尽管如此,在编写信号处理器时,它们偶尔还是会派上用场的。
最后,需要注意的是,无论使用什么样的错误处理方式,都不要忘记发现程序中错误的最好方法其实是执行程序,对代码进行逐条跟踪,这样可以观察数据在函数中的流动,同时检查出类似于上溢和下溢错误、数据转换错误、NULL 指针错误、错误的内存单元、用 = 代替 ==、运算优先级错误、逻辑运算等错误。
- c语言
- 基础知识
- 变量和常量
- 宏定义和预处理
- 随机数
- register变量
- errno全局变量
- 静态变量
- 类型
- 数组
- 类型转换
- vs中c4996错误
- 数据类型和长度
- 二进制数,八进制数和十六进制数
- 位域
- typedef定义类型
- 函数和编译
- 函数调用惯例
- 函数进栈和出栈
- 函数
- 编译
- sizeof
- main函数接收参数
- 宏函数
- 目标文件和可执行文件有什么
- 强符号和弱符号
- 什么是链接
- 符号
- 强引用和弱引用
- 字符串处理函数
- sscanf
- 查找子字符串
- 字符串指针
- qt
- MFC
- 指针
- 简介
- 指针详解
- 案例
- 指针数组
- 偏移量
- 间接赋值
- 易错点
- 二级指针
- 结构体指针
- 字节对齐
- 函数指针
- 指针例子
- main接收用户输入
- 内存布局
- 内存分区
- 空间开辟和释放
- 堆空间操作字符串
- 内存处理函数
- 内存分页
- 内存模型
- 栈
- 栈溢出攻击
- 内存泄露
- 大小端存储法
- 寄存器
- 结构体
- 共用体
- 枚举
- 文件操作
- 文件到底是什么
- 文件打开和关闭
- 文件的顺序读写
- 文件的随机读写
- 文件复制
- FILE和缓冲区
- 文件大小
- 插入,删除,更改文件内容
- typeid
- 内部链接和外部链接
- 动态库
- 调试器
- 调试的概念
- vs调试
- 多文件编程
- extern关键字
- 头文件规范
- 标准库以及标准头文件
- 头文件只包含一次
- static
- 多线程
- 简介
- 创建线程threads.h
- 创建线程pthread
- gdb
- 简介
- mac使用gdb
- setjump和longjump
- 零拷贝
- gc
- 调试器原理
- c++
- c++简介
- c++对c的扩展
- ::作用域运算符
- 名字控制
- cpp对c的增强
- const
- 变量定义数组
- 尽量以const替换#define
- 引用
- 内联函数
- 函数默认参数
- 函数占位参数
- 函数重载
- extern "C"
- 类和对象
- 类封装
- 构造和析构
- 深浅拷贝
- explicit关键字
- 动态对象创建
- 静态成员
- 对象模型
- this
- 友元
- 单例
- 继承
- 多态
- 运算符重载
- 赋值重载
- 指针运算符(*,->)重载
- 前置和后置++
- 左移<<运算符重载
- 函数调用符重载
- 总结
- bool重载
- 模板
- 简介
- 普通函数和模板函数调用
- 模板的局限性
- 类模板
- 复数的模板类
- 类模板作为参数
- 类模板继承
- 类模板类内和类外实现
- 类模板和友元函数
- 类模板实现数组
- 类型转换
- 异常
- 异常基本语法
- 异常的接口声明
- 异常的栈解旋
- 异常的多态
- 标准异常库
- 自定义异常
- io
- 流的概念和类库结构
- 标准io流
- 标准输入流
- 标准输出流
- 文件读写
- STL
- 简介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair对组
- 深浅拷贝问题
- 使用时机
- 常用算法
- 函数对象
- 谓词
- 内建函数对象
- 函数对象适配器
- 空间适配器
- 常用遍历算法
- 查找算法
- 排序算法
- 拷贝和替换算法
- 算术生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各种插件
- utf8编码
- 制作安装项目
- 编译模式
- 内存对齐
- 快捷键
- 自动补全
- 查看c++类内存布局
- FFmpeg
- ffmpeg架构
- 命令的基本格式
- 分解与复用
- 处理原始数据
- 录屏和音
- 滤镜
- 水印
- 音视频的拼接与裁剪
- 视频图片转换
- 直播
- ffplay
- 常见问题
- 多媒体文件处理
- ffmpeg代码结构
- 日志系统
- 处理流数据
- linux
- 系统调用
- 常用IO函数
- 文件操作函数
- 文件描述符复制
- 目录相关操作
- 时间相关函数
- 进程
- valgrind
- 进程通信
- 信号
- 信号产生函数
- 信号集
- 信号捕捉
- SIGCHLD信号
- 不可重入函数和可重入函数
- 进程组
- 会话
- 守护进程
- 线程
- 线程属性
- 互斥锁
- 读写锁
- 条件变量
- 信号量
- 网络
- 分层模型
- 协议格式
- TCP协议
- socket
- socket概念
- 网络字节序
- ip地址转换函数
- sockaddr数据结构
- 网络套接字函数
- socket模型创建流程图
- socket函数
- bind函数
- listen函数
- accept函数
- connect函数
- C/S模型-TCP
- 出错处理封装函数
- 多进程并发服务器
- 多线程并发服务器
- 多路I/O复用服务器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反应堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函数
- libevent
- libevent简介