当前位置: 首页 > news >正文

包头网站网站建设医院咨询男科

包头网站网站建设,医院咨询男科,海南住房城乡建设网站,店铺设计图片素材一、前言二、函数语法介绍与 goto 语句比较与 fork 函数比较与 Python 语言中的 yield/resume 比较三、利用 setjmp/longjmp 实现异常捕获四、利用 setjmp/longjmp 实现协程五、总结一、前言 在 C 标准库中#xff0c;有两个威力很猛的函数#xff1a;setjmp 和 longjmp… 一、前言二、函数语法介绍与 goto 语句比较与 fork 函数比较与 Python 语言中的 yield/resume 比较三、利用 setjmp/longjmp 实现异常捕获四、利用 setjmp/longjmp 实现协程五、总结一、前言 在 C 标准库中有两个威力很猛的函数setjmp 和 longjmp不知道各位小伙伴在代码中是否使用过我问了身体的几位同事一部分人不认识这两个函数有一部分人知道这个函数但从来没有使用过。从知识点范围来看这两个函数的功能比较单纯一个简单的示例代码就能说清楚了。但是我们需要从这个知识点进行发散、思考在不同的维度上把这个知识点与这个编程语言中其它类似的知识进行联想、对比与其他编程语言中类似的概念进行比较然后再思考这个知识点可以使用在哪些场合别人是怎么来使用它的。今天我们就来掰扯掰扯这两个函数。虽然在一般的程序中使用不上但是在今后的某个场合当你需要处理一些比较奇特的程序流程时也许它们可以给你带来意想不到的效果。例如我们会把 setjmp/longjmp 与 goto 语句进行功能上的比较与 fork 函数从返回值上进行类比与 Python/Lua 语言中的协程进行使用场景上的比较。二、函数语法介绍 1. 最简示例先不讲道理直接看一下这个最简单的示例代码看不懂也没关系混个脸熟:int main(){ // 一个缓冲区用来暂存环境变量 jmp_buf buf; printf(line1 \n); // 保存此刻的上下文信息 int ret setjmp(buf); printf(ret %d \n, ret); // 检查返回值类型 if (0 ret) { // 返回值0说明是正常的函数调用返回 printf(line2 \n); // 主动跳转到 setjmp 那条语句处 longjmp(buf, 1); } else { // 返回值非0说明是从远程跳转过来的 printf(line3 \n); } printf(line4 \n); return 0;}执行结果执行顺序如下(如果不明白就不要深究看完下面的解释再回过头来看)2. 函数说明首先来看下这个 2 个函数的签名int setjmp(jmp_buf env);void longjmp(jmp_buf env, int value);它们都在头文件 setjmp.h 中进行声明维基百科的解释如下setjmp: Sets up the local jmp_buf buffer and initializes it for the jump. This routine saves the programs calling environment in the environment buffer specified by the env argument for later use by longjmp. If the return is from a direct invocation, setjmp returns 0. If the return is from a call to longjmp, setjmp returns a nonzero value。longjmpRestores the context of the environment buffer env that was saved by invocation of the setjmp routine in the same invocation of the program. Invoking longjmp from a nested signal handler is undefined. The value specified by value is passed from longjmp to setjmp. After longjmp is completed, program execution continues as if the corresponding invocation of setjmp had just returned. If the value passed to longjmp is 0, setjmp will behave as if it had returned 1; otherwise, it will behave as if it had returned value。下面我再用自己的理解把上面这段英文解释一下setjmp 函数功能把执行这个函数时的各种上下文信息保存起来主要就是一些寄存器的值参数用来保存上下文信息的缓冲区相当于把当前的上下文信息拍一个快照保存起来返回值有 2 种返回值如果是直接调用 setjmp 函数时返回值是 0如果是调用 longjmp 函数跳转过来时返回值是非 0;  这里可以与创建进程的函数 fork 进行一下类比。longjmp 函数功能跳转到参数 env 缓冲区中保存的上下文(快照)中去执行参数env 参数指定跳转到哪个上下文中(快照)去执行 value 用来给 setjmp 函数提供返回判断信息也就是说调用 longjmp 函数时这个参数 value 将会作为 setjmp 函数的返回值;返回值没有返回值。因为在调用这个函数时就直接跳转到其他地方的代码去执行了不会再回来了。小结这 2 个函数是配合使用的用来实现程序的跳转。3. setjmp保存上下文信息我们知道C 代码在编译成二进制文件之后在执行时被加载到内存中CPU 按照顺序到代码段取出每一条指令来执行。在 CPU 中有很多个寄存器用来保存当前的执行环境比如代码段寄存器CS、指令偏移量寄存器IP当然了还有其他很多其它寄存器我们把这个执行环境称作上下文。CPU 在获取下一条执行指令时通过 CS 和 IP 这 2 个寄存器就能获取到需要执行的指令如下图补充一下知识点上图中把代码段寄存器 CS 当做一个基地址来看待了也就是说CS 指向代码段在内存中的开始地址IP 寄存器代表下一个要执行的指令地址距离这个基地址的偏移量。因此每次取指令时只需要把这 2 个寄存器中的值相加就得到了指令的地址其实在 x86 平台上代码段寄存器 CS 并不是一个基地址而是一个选择子。在操作系统的某个地方有一个表格这个表格里存储了代码段真正的开始地址而 CS 寄存器中 只是存储了一个索引值这个索引值指向这个表格中的某个表项这里涉及到虚拟内存的相关知识了IP 寄存器在获取一条指令之后自动往下移动到下一个指令的开始位置至于移动多少个字节那就要看当前取出的这条指令占用了多少个字节。CPU 是一个大傻瓜它没有任何的想法我们让它干什么它就干什么。比如取指令我们只要设置 CS 和 IP 寄存器CPU 就用这 2 个寄存器里的值去获取指令。如果把这 2 个寄存器设置为一个错误的值CPU 也会傻不拉几的去取指令只不过在执行时就会崩溃。我们可以简单的把这些寄存器信息理解为上下文信息CPU 就根据这些上下文信息来执行。因此C 语言为我们准备了 setjmp 这个库函数来把当前的上下文信息保存起来暂时存储到一个缓冲区中。保存的目的是什么为了在以后可以恢复到当前这个地方继续执行。还有一个更简单的例子服务器中的快照。快照的作用是什么当服务器出现错误时可以恢复到某个快照4. longjmp: 实现跳转说到跳转脑袋中立刻跳出的概念就是 goto 语句我发现很多教程都对 goto 语句很有意见认为在代码中应该尽量不要使用它。这样的观点出发点是好的如果 goto 使用太多会影响对代码执行顺序的理解。但是如果看一下 Linux 内核的代码可以发现很多的 goto 语句。还是那句话在代码维护和执行效率上要寻找一个平衡点。跳转改变了程序的执行序列goto 语句只能在函数内部进行跳转如果是跨函数它就无能为力了。因此C 语言中为我们提供了 longjmp 函数来实现远程跳转从它的名字就可以额看出来也就是说可以跨函数跳转。从 CPU 的角度看所谓的跳转就是把上下文中的各种寄存器设置为某个时刻的快照很显然上面的 setjmp 函数中已经把那个时刻的上下文信息(快照)存储到一个临时缓冲区中了如果要跳转到那个地方去接着执行直接告诉 CPU 就行了。怎么告诉 CPU 呢就是把临时缓冲区中的这些寄存器信息覆盖掉 CPU 中使用的那些寄存器即可。5. setjmp返回类型和返回值在某些需要多进程的程序中我们经常使用 fork 函数来从当前的进程中孵化一个新的进程这个新进程从 fork 这个函数的下一条语句开始执行。对于主进程来说调用 fork 函数之后返回也是继续执行下一条语句那么如何来区分是主进程还是新进程呢 fork 函数提供了一个返回值给我们来进行区分fork 函数返回 0代表这是新进程fork 函数返回非 0代表是原来的主进程返回数值是新进程的进程号。类似的setjmp 函数也有不同的返回类型。也许用返回类型来表述不太准确可以这样理解从 setjmp 函数返回一共有 2 个场景主动调用 setjmp 时返回 0主动调用的目的是为了保存上下文建立快照。通过 longjmp 跳转过来时返回非 0此时的返回值是由 longjmp 的第二个参数来指定的。根据以上这 2 种不同的值我们就可以进行不同的分支处理了。当通过 longjmp 跳转返回的时候可以根据实际场景返回不同的非 0 值。有过 Python、Lua 等脚本语言编程经验的小伙伴是不是想到了 yield/resume 函数它们在参数、返回值上的外在表现是一样的小结到这里基本上把 setjmp/longjmp 这 2 个函数的使用方法讲完了不知道我描述的是否足够清楚。此时再看一下文章开头的示例代码应该一目了然了。三、利用 setjmp/longjmp 实现异常捕获 既然 C 函数库给我们提供了这个工具那就肯定存在一定的使用场景。异常捕获在一些高级语言中(Java/C)直接在语法层面进行了支持一般就是 try-catch 语句但是在 C 语言中需要自己去实现。我们来演示一个最简单的异常捕获模型代码一共 56 行#include unistd.h#include stdio.h#include stdlib.h#include setjmp.h typedef int BOOL;#define TRUE 1#define FALSE 0 // 枚举错误代码typedef enum _ErrorCode_ { ERR_OK 100, // 没有错误 ERR_DIV_BY_ZERO -1 // 除数为 0} ErrorCode; // 保存上下文的缓冲区jmp_buf gExcptBuf; // 可能发生异常的函数typedef int (*pf)(int, int);int my_div(int a, int b){ if (0 b) { // 发生异常跳转到函数执行之前的位置 // 第2个参数是异常代码 longjmp(gExcptBuf, ERR_DIV_BY_ZERO); } // 没有异常返回正确结果 return a / b;} // 在这个函数中执行可能会出现异常的函数int try(pf func, int a, int b){ // 保存上下文如果发生异常将会跳入这里 int ret setjmp(gExcptBuf); if (0 ret) { // 调用可能发生异常的哈数 func(a, b); // 没有发生异常 return ERR_OK; } else { // 发生了异常ret 中是异常代码 return ret; }} int main(){ int ret try(my_div, 8, 0); // 会发生异常 // int ret try(my_div, 8, 2); // 不会发生异常 if (ERR_OK ret) { printf(try ok ! \n); } else { printf(try excepton. error %d \n, ret); } return 0;}代码就不需要详细说明了直接看代码中的注释即可明白。这个代码仅仅是示意性的在生产代码中肯定需要更完善的包装才能使用。有一点需要注意setjmp/longjmp 仅仅是改变了程序的执行顺序应用程序自己的一些数据如果需要回滚的话需要我们自己手动处理。四、利用 setjmp/longjmp 实现协程 1. 什么是协程在 C 程序中如果需要并发执行的序列一般都是用线程来实现的那么什么是协程呢维基百科对于协程的解释是更详细的信息在这个页面 协程网页中具体描述了协程与线程、生成器的比较各种语言中的实现机制。我们用生产者和消费者来简单体会一下协程和线程的区别2. 线程中的生产者和消费者生产者和消费者是 2 个并行执行的序列通常用 2 个线程来执行生产者在生产商品时消费者处于等待状态(阻塞)。生产完成后通过信号量通知消费者去消费商品消费者在消费商品时生产者处于等待状态(阻塞)。消费结束后通过信号量通知生产者继续生产商品。3. 协程中的生产者和消费者生产者和消费者在同一个执行序列中执行通过执行序列的跳转来交替执行生产者在生产商品之后放弃 CPU让消费者执行消费者在消费商品之后放弃 CPU让生产者执行4. C 语言中的协程实现这里给出一个最最简单的模型通过 setjmp/longjmp 来实现协程的机制主要是目的是来理解协程的执行序列没有解决参数和返回值的传递问题。typedef int BOOL;#define TRUE 1#define FALSE 0 // 用来存储主程和协程的上下文的数据结构typedef struct _Context_ { jmp_buf mainBuf; jmp_buf coBuf;} Context; // 上下文全局变量Context gCtx; // 恢复#define resume() \ if (0 setjmp(gCtx.mainBuf)) \ { \ longjmp(gCtx.coBuf, 1); \ } // 挂起#define yield() \ if (0 setjmp(gCtx.coBuf)) \ { \ longjmp(gCtx.mainBuf, 1); \ } // 在协程中执行的函数void coroutine_function(void *arg){ while (TRUE) // 死循环 { printf(\n*** coroutine: working \n); // 模拟耗时操作 for (int i 0; i 10; i) { fprintf(stderr, .); usleep(1000 * 200); } printf(\n*** coroutine: suspend \n); // 让出 CPU yield(); }} // 启动一个协程// 参数1func 在协程中执行的函数// 参数2func 需要的参数typedef void (*pf)(void *);BOOL start_coroutine(pf func, void *arg){ // 保存主程的跳转点 if (0 setjmp(gCtx.mainBuf)) { func(arg); // 调用函数 return TRUE; }return FALSE;} int main(){ // 启动一个协程 start_coroutine(coroutine_function, NULL); while (TRUE) // 死循环 { printf(\n main: working \n);// 模拟耗时操作 for (int i 0; i 10; i) { fprintf(stderr, .); usleep(1000 * 200); }printf(\n main: suspend \n); // 放弃 CPU让协程执行 resume(); }return 0;}打印信息如下 如果想深入研究 C 语言中的协程实现可以看一下达夫设备这个概念其中利用 goto 和 switch 语句来实现分支跳转其中使用的语法比较怪异、但是合法。五、总结 这篇文章的重点是介绍 setjmp/longjmp 的语法和使用场景在某些需求场景中能达到事半功倍的效果。当然你还可以发挥想象力通过执行序列的跳转来实现更加花哨的功能一切皆有可能不吹嘘不炒作不浮夸认真写好每一篇文章欢迎转发、分享给身边的技术朋友道哥在此表示衷心的感谢转发的推荐语已经帮您想好了道哥总结的这篇总结文章写得很用心对我的技术提升很有帮助。好东西要分享最后祝您面对代码永无bug面对生活春暖花开【原创声明】作者道哥(公众号: IOT物联网小镇)知乎道哥B站道哥分享掘金道哥分享CSDN道哥分享推荐阅读专辑|Linux文章汇总专辑|程序人生专辑|C语言我的知识小密圈关注公众号后台回复「1024」获取学习资料网盘链接。欢迎点赞关注转发在看您的每一次鼓励我都将铭记于心~
http://www.zqtcl.cn/news/720934/

相关文章:

  • 人和兽做的网站视频汽车建设网站开发流程
  • 长春市建设工程造价管理协会网站厦门网站建设费用
  • 广东建设信息公开网站怎样策划一个营销型网站
  • 魔兽做图下载网站如何经营一个购物网站
  • 深圳做网站哪个平台好一级消防工程师考试题型
  • 网站婚礼服务态网站建设论文网站设计有限公司是干嘛的
  • 邯郸网站建设效果好广西做网站的公司
  • 网站logo上传营销型网站制作方案
  • 小说网站静态模板站长工具seo综合查询adc
  • 北京响应式网站做logo那个网站
  • 如何申请免费网站空间刚察县wap网站建设公司
  • 哪里有网站推广软件免费推广seo策略方法
  • 阿里云备案网站 网站名称怎么写京icp备案查询
  • 网站开发岗位思维导图alexa排名
  • 自适应网站建设济南济南网站建设公司
  • 巴州网站建设库尔勒网站建设钟爱网络杭州微信网站制作
  • 52做网站南京市住房城乡建设门户网站
  • 网站开发精品课程贵阳市白云区官方网站
  • seo整站优化服务会计培训班一般收费多少
  • 批量网站访问检测怎么做好手机网站开发
  • 深圳网站建设公司哪家比较好shortcodes wordpress
  • 网站内链越多越好嘛可以做3d电影网站
  • 企业网站需求文档微商引流客源最快的方法
  • 交互式网站备案业务网站在线生成
  • 自建网站百度个人网站如何在百度上做推广
  • 如何安装wordpress模板竞价网站做seo
  • 做论坛网站如何赚钱电子商务营销推广
  • 想要自己做一个网站怎么做济宁百度网站建设
  • 海会网络建设网站wordpress刷不出图片
  • 一个人做商城网站网站推广的几个阶段