使用库函数setjmp和longjmp可执行非局部跳转(local goto)。

术语"非局部(nonlocal)"是指跳转目标为当前执行函数之外的某个位置。

C语言里面有个"臭名昭著"的goto,每次介绍的时候都不忘了带一句,不要使用goto。

C语言的goto存在一个限制,即不能从当前函数跳转到另一函数。然而,偶尔还是需要这一功能的。考虑错误处理中经常出现的如下场景:在一个深度嵌套的函数调用中发生了错误,需要放弃当前任务,从多层函数调用中返回,并在较高层级的函数中继续执行(也许甚至是在main中)。

要做到这一点,可以让每个函数都返回一个状态值,由函数的调用者检查并做相应的处理。这一方法完全有效,而且,在许多情况下,是处理这类场景的理想方法。

然而,有时候如果能从嵌套函数调用中跳出,返回该函数的调用者之一(当前调用者或者调用者的调用者,等等),编码会更为简单。

setjmp和longjmp就提供了这一功能。

setjmp调用为后续由longjmp调用执行的跳转确立了跳转目标。该目标正是程序发起setjmp调用的位置。

从编程角度来看,调用longjmp函数之后,看起来就和从第二次调用setjmp返回时完全一样。

通过查看setjmp返回的整数值,可以区分setjmp调用是初始返回还是第二次返回。

初始调用的返回值是0,后续伪返回的返回值为longjmp调用中val参数所指定的任意值。通过对val参数使用不同值,能够区分出程序中跳转至同一目标的不同起跳位置。(妈的,还有起跳位置。。。)

如果指定longjmp函数的val参数值为0,而longjmp函数对此又不做检查,就会导致模拟setjmp时返回值为0,如同初次调用setjmp函数返回时一样。处于这一原因,如果指定val参数值为0,则longjmp调用实际会将其替换为1。

这两个函数的入参evn为成功实现跳转提供了黏合剂。

setjmp函数把当前进程环境中的各种信息保存到env参数中。

调用longjmp时必须指定相同的env变量,以此来执行伪返回。

由于setjmp和longjmp的调用分别位于不同函数(否则使用简单的goto即可),所以应该将env参数定义为全局变量,或者将env作为函数入参来传递,后一种做法较为少见。

调用setjmp时,env除了存储当前进程的其他信息外,还保存了程序计数寄存器(指向当前正在执行的机器语言指令)和栈指针寄存器(标记栈顶)的副本。这些信息能够使后续的longjmp调用完成两个关键步骤的操作。

将发起longjmp调用的函数与之前调用setjmp的函数之间的函数栈帧从栈上剥离。有时又将此过程称为"解开栈"(unwinding the stack),这是通过将栈指针寄存器重置为env参数内的保存值来实现的。

重置程序计数寄存器,使程序得以从初始的setjmp调用位置继续执行。同样,此功能是通过env参数中的保存值(程序计数寄存器)来实现的。

对setjmp使用的限制

SUSv3和C99规定,setjmp的调用只能在如下语境中使用。

构成选择或迭代语句中(if、switch、while等)的整个控制表达式。

作为一元操作符!(not)的操作对象,其最终表达式构成了选择或迭代语句的整个控制表达式。

作为比较操作(==、!=、<等)的一部分,另一操作对象必须是一个整数常量表达式,且其最终表达式构成选择或迭代语句的整个控制表达式。

作为独立的函数调用,且没有嵌入到更大的表达式之中。

注意:C语言赋值语句不在上述列表之列,一下语句是不符合标准的:

s = setjmp(env);

之所以规定这些限制,是因为作为常规函数的setjmp实现无法保证拥有足够的信息来保证所有寄存器值和封闭表达式中用到的临时栈位置,以便于在longjmp调用此类信息能够得以正确恢复。因此,仅允许在足够简单且无需临时存储的表达式调用setjmp。

滥用longjmp

如果env缓冲区定义为全局变量,对所有函数可见(这也是通常的用法),那么就可以执行如下操作序列。

1. 调用函数x,用setjmp调用在全局变量env中建立一个跳转目标。
2. 从函数x中返回
3. 调用函数y,使用env变量调用longjmp函数

这是一个严重的错误,因为longjmp调用不能跳转到一个已经返回的函数中。思考一下,在这种情况下,longjmp函数会对栈打什么主意——尝试将栈解开,恢复到一个不存在的栈帧位置,这无疑将引起混乱。如果幸运的话,程序将一死了之。然而,取决于栈的状态,也可能会引起调用与返回间的死循环,而程序好像真地从一个当前并未执行的函数中返回。(在多线程程序中有与之类似的滥用,在线程某甲中调用setjmp函数,却在线程某乙中调用longjmp。)

优化编译器的问题

优化编译器会重组程序的指令执行顺序,并在CPU寄存器中,而非RAM中存储某些变量。这种优化一般依赖于反映了程序词法结构的运行时(run-time)控制流。由于setjmp和longjmp的跳转需要在运行时才能得以确立和执行,并未在词法结构中有所反映,故而编译器在进行优化时也无法将其考虑在内。此外,某些应用程序二进制接口(ABI)实现的语义要求longjmp函数恢复先前setjmp调用所保存的CPU寄存器副本。这意味着longjmp操作会致使经过优化的变量被赋以错误值。一下就是一例。

尽可能避免使用setjmp和longjmp

如果说goto语句会使程序难以阅读,那么非局部跳转会让事情的糟糕程度增加一个数量级,因为它能在程序中任意两个函数间传递控制。因此,应当慎用setjmp函数和longjmp函数。在设计和编码时花点心思来避免使用这两个函数,这通常是值得的。程序更具有可读性,可能会更具有可执行性。话虽如此,但在编写信号处理器时,这些函数偶尔还会派上用场——讨论信号时将重新讨论这些函数的变体。

Unix系统编程()执行非局部跳转:setjmp和longjmp的更多相关文章

  1. 【转】浅析C语言的非局部跳转:setjmp和longjmp

    转自 http://www.cnblogs.com/lienhua34/archive/2012/04/22/2464859.html C语言中有一个goto语句,其可以结合标号实现函数内部的任意跳转 ...

  2. 《Linux/Unix系统编程手册》读书笔记3

    <Linux/Unix系统编程手册>读书笔记 目录 第6章 这章讲进程.虚拟内存和环境变量等. 进程是一个可执行程序的实例.一个程序可以创建很多进程. 进程是由内核定义的抽象实体,内核为此 ...

  3. 《Linux/UNIX系统编程手册》读书笔记

    2018-1-30 一.UNIX.C语言以及Linux的历史回顾 1. UNIX简史.C语言的诞生 1969年,贝尔实验室的Ken Thompson首次实现了UNIX系统. 1973年,C语言步入成熟 ...

  4. 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll

    关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...

  5. 《Linux/Unix系统编程手册》读书笔记8 (文件I/O缓冲)

    <Linux/Unix系统编程手册>读书笔记 目录 第13章 这章主要将了关于文件I/O的缓冲. 系统I/O调用(即内核)和C语言标准库I/O函数(即stdio函数)在对磁盘进行操作的时候 ...

  6. 《Linux/Unix系统编程手册》读书笔记7 (/proc文件的简介和运用)

    <Linux/Unix系统编程手册>读书笔记 目录 第11章 这章主要讲了关于Linux和UNIX的系统资源的限制. 关于限制都存在一个最小值,这些最小值为<limits.h> ...

  7. 《Linux/Unix系统编程手册》读书笔记6

    <Linux/Unix系统编程手册>读书笔记 目录 第9章 这章主要讲了一堆关于进程的ID.实际用户(组)ID.有效用户(组)ID.保存设置用户(组)ID.文件系统用户(组)ID.和辅助组 ...

  8. 《Linux/Unix系统编程手册》读书笔记1

    <Linux/Unix系统编程手册>读书笔记 目录 最近这一个月在看<Linux/Unix系统编程手册>,在学习关于Linux的系统编程.之前学习Linux的时候就打算写关于L ...

  9. 《Linux/Unix系统编程手册》读书笔记2

    <Linux/Unix系统编程手册>读书笔记 目录 第5章: 主要介绍了文件I/O更深入的一些内容. 原子操作,将一个系统调用所要完成的所有动作作为一个不可中断的操作,一次性执行:这样可以 ...

随机推荐

  1. 【云计算】Docker容器不能修改hosts文件怎么解决?

    参考资料: http://bbs.csdn.net/topics/390871429 http://tieba.baidu.com/p/4295556808 http://stackoverflow. ...

  2. C# 使用int.TryParse,Convert.ToInt32,(int)将浮点类型转换整数时的区别

    int.TryParse,Convert.ToInt32,(int) 这几种类型在将浮点类型转换整数时是有差别 Convert.ToInt32则会进行四舍五入 int.TryParse只能转换整数,即 ...

  3. 使用python读取word

    使用python读取word 官网:https://python-docx.readthedocs.io/en/latest/ 示例:https://blog.csdn.net/u010911997/ ...

  4. Hadoop,MapReduce,HDFS面试题

    今天发这个的目的是为了给自己扫开迷茫,告诉自己该进阶了,下面内容不一定官方和正确.全然个人理解,欢迎大家留言讨论 1.什么是hadoop 答:是google的核心算法MapReduce的一个开源实现. ...

  5. Discuz常见小问题-如何设置为人工审核

    全局-注册与访问控制-人工审核    

  6. 错误:因为相同类型的其他实体已具有相同的主键值。在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Modified" 解决方法

    在更新一个实体类的时候可能会有预先有一次查询或者其它操作,我们这样用目的是为了与提交的数据做一个比较之类的东西,如果先查询再对此类进行SaveChanges就会出错. 我们只要用AsNoTrackin ...

  7. UIScrollView 循环滚动,代码超简单

    如今非常多应用里面多多少少都用到了循环滚动,要么是图片.要么是view,或者是其它,我总结一下,写了个demo分享给大家. 先看代码之后在讲原理: 1.创建一个空的项目(这个我就不多说了). 2.加入 ...

  8. eclipse maven Cannot change version of project facet Dynamic web module to 3.0

      eclipse maven Cannot change version of project facet Dynamic web module to 3.0 (eclipse 修改maven项目的 ...

  9. Kubuntu 初始配置

    1.中文配置 系统设置中地区选择中国.语系使用简体中文(一開始可能仅仅有英文选项,似乎是更新后能够选择其它语言了) 安装中文字体: sudo apt-get install ttf-wqy-micro ...

  10. nginx根据token做频率限制

    在 nginx.conf 文件添加配置 limit_conn_log_level error; limit_conn_status ; limit_conn_zone $cookie_gray_DF_ ...