我在研究setjmp/longjmp,发现setjmp保存了指令指针、堆栈指针等寄存器。
但是我在这里没有得到的是,在调用setjmp和longjmp之间,线程本身的堆栈中的数据不能被修改吗?在这种情况下,longjmp不会像预期的那样工作。
为了清楚起见,例如,当longjmp恢复堆栈指针时,假设堆栈指针现在指向的内存中的数据与调用setjmp时的数据不同。这会发生吗?如果真的发生了我们不是有麻烦了吗
同样,语句“* 在调用setjmp()例程的例程返回后,longjmp()例程可能不会被调用。
6条答案
按热度按时间92vpleto1#
堆栈指针标记堆栈的“已使用”和“未使用”部分之间的划分。当你调用
setjmp
时,所有当前的调用帧都在“已使用”的一侧,而在setjmp
之后,但在调用setjmp
的函数返回之前发生的任何调用,其调用帧都在保存的堆栈指针的“未使用”一侧。请注意,在调用setjmp
的函数返回后调用longjmp
会调用未定义的行为,因此不需要考虑这种情况。现在,一些现有调用帧中的局部变量可能在
setjmp
之后被修改,无论是通过调用函数还是通过指针,这就是为什么在许多情况下需要使用volatile
的原因之一。p4tfgftt2#
setjmp()/longjmp()
并不意味着保存堆栈,这就是setcontext()/getcontext()
的作用。该标准规定,在调用
setjmp()
的函数中定义的在setjmp()
和longjmp()
调用之间更改的非易失性自动变量的值在longjmp()
之后未指定。出于同样的原因,对如何调用setjmp()
也有一些限制。gev0vcfq3#
C中的setjmp/longjmp(以下简称slj)功能很难看,其行为可能因实现而异。尽管如此,由于没有异常,slj有时在C中是必要的(注意,C提供的异常几乎在所有方面都上级slj,而且slj与许多C特性的交互都很糟糕)。
在使用slj时,应该记住以下内容,假设例程Parent()调用例程Setter(),后者调用setjmp(),然后调用Jumper,后者又调用longjmp()。
1.代码可以合法地退出执行setjmp而没有执行longjmp的范围;然而,一旦范围退出,先前创建的jmp_buf就必须被视为无效。编译器可能不会做任何事情来标记它,但任何使用它的尝试都可能导致不可预知的行为,可能包括跳转到任意地址。
1.无论何时控制权通过何种方式返回到Parent,Parent的局部变量都将与调用Setter时一样,除非这些变量的地址已被获取并使用此类指针进行更改;在任何情况下,setjmp/longjmp都不会以任何方式影响它们的值。如果这样的变量没有获取它们的地址,则setjmp()可以缓存这样的变量的值,并且longjmp()可以恢复它们。然而,在这种情况下,变量在缓存时和恢复时之间没有办法改变,因此该高速缓存/恢复将没有可见的效果。
尽管setjmp/longjmp()有时候很有用,但它们也可能非常危险。在大多数情况下,没有保护错误的代码导致未定义的行为,并且在许多现实世界的场景中,不正确的使用可能会导致不好的事情发生(不像某些类型的未定义的行为,其中实际结果通常可能与程序员的意图一致)。
zazmityj4#
在下面的例子中,setjmp / longjump通过指针改变了位于main中的i的值。I在for循环中从不递增。如需更多乐趣,请参阅1992年IOCCC的http://www.ioccc.org/years-spoiler.html赢家者albert.c。(这是我为数不多的几次阅读C源代码的时候...)
xvw2m8pv5#
在数据处理的情况下使用
setjmp()
和longjmp()
有点没用。所以,这就是你可能会关心的自动变量的情况。变量可以在堆栈槽或寄存器中。如果它们在堆栈槽中,则不会通过沿着堆栈弹出上下文来恢复堆栈。相反,堆栈被立即倒回并且被调用方保存的寄存器被恢复。例程使用的堆栈空间是保留的,其他例程不应使用它。因此,如果编译器知道该变量存储在堆栈(
setjmp()
)之前,那么它可以在返回时检索它。它只会混淆基于“基本块”的分析,而setjmp()/longjmp()
违背了这种分类。我想问为什么有人会在这种情况下使用
setjmp()/longjmp()
。在libjpeg和Simon Tatham的use in co-routines中可以找到一个很好的用法。也就是说,例程是相当单一的目的。它们要么为可能有许多异常条件的长时间运行的操作设置上下文,要么用作原始调度程序。将实际数据处理与它们混合是一个等待发生的错误(由于其他地方提到的所有原因)。C标准的子条款7.13.1.1详细说明了可以在何处使用
setjmp()
。它是一个异常函数,因此将其视为正常函数调用是主要问题。也许语言应该给它一个不同的语法。同样,语句“在调用setjmp()例程的例程返回后,longjmp()例程可能不会被调用。”
下面是函数
e()
中的一个例子。代码序列是e()-> f()-> k()-> f()-> e()-> crash。这有点像使用关闭的文件句柄。也就是说,jmp_buf仍然设置为有效的外观值,但它们指向不存在的堆栈。
r1zhe5dt6#
也就是说,“* 在调用
setjmp()
例程的例程返回后,longjmp()
例程可能不会被调用。这就是说,你只能
longjmp
向上调用树/调用堆栈,**到调用setjmp
**的父函数。(或者在当前函数的同一个调用中,比如美化的goto
,所以它不一定是父函数。因此,就调用结构而言,它很像
try{}catch
/throw
,其中setjmp
设置了一个捕获点,而longjmp
类似于throw
。堆栈展开和局部变量的语义有些不同,但仍然只是展开调用堆栈。这就是为什么重用堆栈空间用于其他事情不是问题的原因:在相同的生存期内,在调用
setjmp
时处于活动状态的局部变量(在自动存储中)必须仍然处于活动状态。你不能将
longjmp
返回到一个已经返回的函数中。当然可以,但这是一种未定义的行为。不像try{}catch
/throw
,因为捕获是基于作用域和函数调用的嵌套,所以如果你不在try
内,你就不能跳到catch{}
块。其他的答案进入了关于
volatile
和其他东西的更多细节;我发布这篇文章是因为其他人似乎缺乏一个简单而明确的声明,即只能跳转到一个尚未达到其生命周期终点的作用域中的setjmp
调用站点。