12|标准库:非本地跳转与可变参数是怎样实现的?
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
C语言中的非本地跳转和可变参数函数是本文的重点内容。非本地跳转通过setjmp和longjmp函数实现,允许程序在执行过程中跳转到不同函数的特定位置,实现了跨函数的程序执行流程跳转。而可变参数函数通过stdarg.h头文件提供的宏定义,使得函数能够接收任意数量的参数,增强了函数的灵活性。文章详细介绍了非本地跳转的概念、setjmp和longjmp函数的使用方式,以及它们的运作原理。同时,通过示例代码和汇编代码的分析,展示了非本地跳转的实际执行方式和机器代码层面的执行顺序。此外,文章还介绍了自定义实现setjmp和longjmp函数的方法,并提供了相应的汇编代码和C代码示例。另外,文章还详细讲解了可变参数函数的基本使用方法,通过一个计算传入参数之和的示例函数展示了可变参数函数的定义和使用。整体而言,本文深入浅出地介绍了C语言中非本地跳转与可变参数函数的基本用法和实现原理,为读者提供了深入了解这两个重要特性的机会。文章还提出了一个思考题,引发读者思考如何使用非本地跳转来实现try...catch语句,并鼓励读者分享实践经验。
《深入 C 语言和程序运行原理》,新⼈⾸单¥59
全部留言(12)
- 最新
- 精选
- 白凤凰setjmp和longjmp的应用场景是什么呢?实际开发过程中什么时候可以用这两个函数,是为了解决什么问题呢?
作者回复: 除了我们在文章中提到的可以用来实现 try...catch,协程以外,也可以用在信号处理程序中。比如参考这边的例子:https://www.gnu.org/software/libc/manual/html_node/Longjmp-in-Handler.html。除此之外,我好像没有再见到过其他用例。其他同学如果有另外的使用场景也可以补充哈。
2022-01-1023 - liu_liu对 setjmp 和 longjmp 的理解: setjmp 将寄存器的值保存到全局变量中,同时保存 rsp 和返回地址的值,设置 eax = 0。 在上面的例子中,返回地址就是 cmp eax, 73 这句指令的地址。 longjmp 的作用: 1. 将保存的值从全局变量中全部恢复,其中最重要的是返回地址和 rsp。因为之后的 jmp 指令会跳转到返回地址去继续执行,也就是重新回到 cmp eax, 73。 2. 根据第二个参数更新 eax 的值。 那么当回到返回地址继续执行时,数据也都准备好,这样就达到了目的。 ------------------------- 另外有个问题,对于 rsp 的保存,不知理解的对不对? lea rdx, [rsp+0x8] mov QWORD PTR [rdi+0x30], rdx 在 call _setjmp 时,会将返回地址压栈,此时 rsp -= 8。所以需要加上 8 得到之前的 rsp,也就是 rdx = rsp + 8。
作者回复: 没错的哈。
2022-01-072 - 红军请问一下setjump和longjump为什么要保存&恢复ebx呢?其他的ecx、edx等寄存器为啥可以不用保存&恢复呢
作者回复: 因为 rbx 属于 callee-saved 寄存器,需要由被调用方保存状态,并在结束前重置。
2022-09-16归属地:上海 - TableBear很好奇老师截图里面的工具是什么开发工具?有小伙伴知道吗?
作者回复: 是这个:https://godbolt.org/
2022-08-05归属地:广东 - shk1230那么if (setimp(jb) < 'J' ),不就是恒等式?setjmp(jb)返回0时
作者回复: setjmp 初次调用会返回 0,而后续 longjmp 在每次返回到 setjmp 时都会携带更新后的值。
2022-02-262 - ZR2021对了,还有个感觉比较怪异的点,就是setjmp 函数的参数是直接传递的值,而非指针,然后setjmp 函数里还真修改了传入的值到jb 变量中! 按照我之前的理解,值传递的时候调用函数先将变量传给rdi 寄存器,然后被调函数里将rdi 里的内容拷贝至本函数的传参变量所在的栈,再使用这个变量的值。 但是setjmp函数貌似没有新开辟传参变量的栈,而是直接找到调用函数传参的地址,然后对其进行修改,这样,调用函数里的变量就得到了修改,不知道我理解的对不对。还有,这种通过值传递的而不是指针,但最后将传入的变量内容修改的形式,哪些地方用到的比较多呢?
作者回复: setjmp 调用时传递的 jb 是一个指针哈。jb 对应的类型 jmp_buf 是这样定义的:typedef long jmp_buf[8];
2022-01-102 - ZR2021老师,可变参数里面va_start初始化ap的时候,那个count参数需不需要的?理论上也被存到RSA里面了吧,看起来好像有点多余,但是看到很多可变参数都会用到这个参数,很是奇怪
作者回复: va_start 在调用时的第二个参数,需要传入函数参数列表中显式定义的最后一个带名字的参数,这个是必须要传的。
2022-01-092 - Ping老师,yield函数是不是用这里的setjmp和longjmp实现的?
作者回复: setjmp 和 longjmp 是在 C 语言中实现协程这种语言特性的一种方式,但其他语言比如 Python、JavaScript 中的 yield 关键字是怎样实现的,这个就要具体情况具体分析了。当然,在 C 中也可以实现 yield,比如参考这个 Post:https://stackoverflow.com/questions/17478264/implementing-yield-in-c
2022-01-09 - liu_liuhttps://gist.github.com/silan-liu/47cf4d65e5f49b84c7f499a7d4fd24f2 尝试写了一个版本,不知思路对不对?
作者回复: 思路是对的,不过可以用宏封装一下,让使用方式更自然一些。可以参考这篇文章:http://groups.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
2022-01-07 - liu_liucmp esi, 0x1 adc eax, esi 如果 longjmp 传入的第二个参数为 0,那么此时 esi = 0。 cmp 之后的结果,会让进位 CF = 1。 adc 表示进位加和,eax = eax + esi + CF = 1。 这样就达到了令 eax = 1。2022-01-074