longjmp应该还原堆栈吗
Is longjmp supposed to restore the stack?
根据我的理解,setjmp
保存当前上下文,并且应该在调用longjmp
时恢复它。然而,下一段代码打印了15(我使用-g编译,没有进行任何优化)。我是误解了这个结构,还是遗漏了其他东西?
#include <iostream>
#include <csetjmp>
std::jmp_buf jump_buffer;
int main()
{
int a = 0;
if (setjmp(jump_buffer) == 0) {
a = 15;
std::longjmp(jump_buffer, 42);
}
std::cerr << a << std::endl;
}
免责声明:仅供好奇使用。我从来没有听说过这种结构,直到我最近读到一些关于美国国家航空航天局编码指南的论文,其中提到禁止使用这种控制结构
同时使用c和c++标记,因为代码是混合的,我认为实际的相关函数与c用户更相关,而不是c++…:/
这是预期的行为:
返回到setjmp的作用域后,所有可访问的对象,浮点状态标志和抽象的其他组件机器的值与
std::longjmp
执行,除了setjmp
中的非易失性局部变量范围,如果自CCD_ 5调用。
执行longjmp
时,a
的值是15,所以这是一个可以预期的值(通常是不确定的)。jmp_buf
仅存储执行点。不是程序中每个变量的状态。
除了setjmp作用域中的非易失性局部变量之外的,如果自setjmp调用以来更改了它们的值,则这些变量的值是不确定的部分描述非常重要,因为您看到的值属于不确定的类别。
考虑对您的程序进行一点修改:
#include <iostream>
#include <csetjmp>
std::jmp_buf jump_buffer;
void func() {
std::longjmp(jump_buffer, 42);
}
int main()
{
int a = 0;
volatile int b = 0;
if (std::setjmp(jump_buffer) == 0) {
a = 15;
b = 1;
func();
}
std::cout << a << ' ' << b << 'n';
}
当我编译并运行这个版本(使用-O
)时,我会得到0 1
作为输出,而不是15 1
(因为a
是不确定的,所以结果可能会有所不同)。
如果您希望在初始setjmp()
调用和调用longjmp()
之间发生更改的局部变量能够可靠地保持该更改,则它需要是volatile
。
我只想回答问题的另一部分,推测美国国家航空航天局为什么会禁止这些功能(基本上是将SO的相关答案联系起来)。与C代码相比,C++中不鼓励使用setjmp
和longjmp
,因为关于自动对象销毁的未定义行为,请参阅此so线程,特别是对已接受答案的注释:
通常,每当有某种方法退出C++中的作用域(return、throw或其他)时,编译器都会发出指令,为任何因离开该块而需要销毁的自动变量调用dtor。
longjmp()
只是跳转到代码中的一个新位置,因此它不会为dtor的调用提供任何机会。这个标准实际上没有那么具体——这个标准并没有说不会调用dtor——它说所有的赌注都没有了。在这种情况下,你不能依赖任何特定的行为。[…]
由于智能指针依赖于被销毁,您将得到未定义的行为。这种未定义的行为很可能包括一个没有递减的refcount。使用
longjmp()
是"安全的",只要您没有从应该调用dtor的代码中提取jmp。然而,正如David Thornley在评论中指出的那样,setjmp()
/longjmp()
即使在直接的C语言中也很难正确使用——在C++中,它们是非常危险的。尽可能避开它们。
那么是什么让setjmp()
/longjmp()
在C中变得棘手呢?看看可能的用例,我们可以看到其中之一就是协同程序的实现。答案已经在评论@StoryTeler中给出了,但你能在不同的功能中使用goto
吗?
你不能在标准C中;标签是单个函数的本地标签。
最接近的标准等价物是setjmp()和longjmp(()函数对。
但是,setjmp
和longjmp
也非常有限,您可能很快就会遇到segfault。宝藏可以再次在评论中找到:
您可以将
longjmp()
视为";延长返回";。成功的longjmp()
就像一系列连续的返回一样,展开调用堆栈,直到它到达相应的setjmp()
。一旦调用堆栈帧被展开,它们就不再有效。这与协程(例如Modula-2)或延续(例如Scheme)的实现形成对比,在这些实现中,调用堆栈在跳到其他地方后仍然有效。C和C++只支持单个线性调用堆栈,除非使用创建多个独立调用堆栈的线程。
对于setjmp
和longjmp
,"上下文"是执行上下文,而不是堆栈的实际内容(通常存储局部变量)。
使用setjmp
和longjmp
,您不能"回滚"对局部变量所做的更改。
我想您可能看到了较旧的代码库。Where异常在某些编译器中不太流行或不可用。
您不应该使用setjmp&longjmp,直到您更接近系统软件。
- 对于控制流:setjmp返回两次,longjmp从不返回退货
- 当您第一次调用setjmp时,要存储环境中,它返回零
- 然后,当您调用longjmp时,控制流通过参数中提供的值从setjmp返回
- 用例通常被称为"错误处理",并且"不要使用这些功能">
setjmp&longjmp商店&恢复CPU SFR(即上下文寄存器)。
下面是一个小的控制流程示例:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void foo()
{
longjmp(&env, 10); +---->----+
} | |
| |
int main() (entry)---+ ^ V
{ | | |
if(setjmp(&env) == 0) | (= 0) | | (= 10)
{ | ^ |
foo(); +---->----+ |
} +---->----+
else |
{ |
return 0; +--- (end)
}
}
“Setjump” and “Longjump” are defined in setjmp.h, a header file in C standard library.
setjump(jmp_buf buf) : uses buf to remember current position and returns 0.
longjump(jmp_buf buf, i) : Go back to place buf is pointing to and return i .
的简单示例
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
void second() {
printf("secondn"); // prints
longjmp(buf,1); // jumps back to where setjmp was called - making setjmp now return 1
}
void first() {
second();
printf("firstn"); // does not print
}
int main() {
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("mainn"); // prints
return 0;
}
这就是为什么setjump()为您返回0的原因,当您检查条件时,它会指定a=15,一旦过程完成,下一步它会给出42。
这些函数的主要特点是提供一种偏离标准调用和返回序列的方法。这主要用于在C.中实现异常处理。setjmp可以像try一样使用(在C++和Java等语言中)。对longjmp的调用可以像throw一样使用(请注意,longjmp()将控制权转移到setjmp(()设置的点)。
- 算法问题:查找从堆栈中弹出的所有序列
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 为什么调用堆栈数组会导致内存泄漏
- gdb错误:Backtrace已停止:上一帧与此帧相同(堆栈已损坏?)
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 堆栈和队列是否像C++中的数组一样传递?
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 从堆栈分配的原始指针构造智能指针
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 堆栈中大小变量输入错误 (C++)
- 堆栈问题(平衡表达式问题集)
- C++ 在堆栈中包含多态属性的类对象存储
- 用于解析 win64 堆栈跟踪的命令行客户端(可以访问符号服务器)
- 在 C++ 中使用链表进行堆栈
- 变量周围的堆栈'...'已损坏
- longjmp应该还原堆栈吗
- 中止的xbegin事务是否还原xbegin启动时存在的堆栈上下文