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;
}
免责声明:仅出于好奇而尝试使用它。我从来没有听说过这个结构,直到我最近阅读了一些关于 NASA 编码指南的论文,其中提到禁止使用这种控制流结构
同时使用 c 和 c++ 标签,因为代码是混合的,我认为实际的相关功能更适合 c 重度用户而不是 c++...:/
对于 setjmp
和 longjmp
"context" 是 执行 上下文,而不是堆栈的实际内容(其中局部变量是通常存储)。
使用 setjmp
和 longjmp
您不能 "roll back" 对局部变量进行更改。
Upon return to the scope of setjmp, all accessible objects,
floating-point status flags, and other components of the abstract
machine have the same values as they had when std::longjmp
was
executed, except for the non-volatile local variables in setjmp
's
scope, whose values are indeterminate if they have been changed since
the setjmp
invocation.
执行longjmp
时a
的值为15,所以这是一个可以预期看到的值(一般是不确定的)。 jmp_buf
仅存储 执行点 。不是程序中每个变量的状态。
我认为您看到的可能是较旧的代码库。在某些编译器中异常不是很流行或不可用的地方。
在更接近系统软件之前,您不应使用 setjmp 和 longjmp。
- 至于控制流程:setjmp returns 两次,longjmp 从不
return秒。
- 第一次调用setjmp时,存储
环境,它 return 为零,
- 然后当您调用 longjmp 时,控制流从 setjmp 传递到 return,并带有参数中提供的值。
- 用例通常被称为“错误处理”,并且
“不要使用这些功能”。
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)
}
}
除了 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
.
“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("second\n"); // prints
longjmp(buf,1); // jumps back to where setjmp was called - making setjmp now return 1
}
void first() {
second();
printf("first\n"); // does not print
}
int main() {
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints
return 0;
}
这就是为什么 setjump() returns 0 的原因,当你检查条件时,它分配 a=15,一旦过程完成,下一步它将给出 42。
这些函数的主要特点是提供了一种偏离标准调用和return序列的方式。这在C中主要用于实现异常处理。setjmp可以像try一样使用(在C++和Java等语言中)。对 longjmp 的调用可以像 throw 一样使用(注意 longjmp() 将控制转移到 setjmp() 设置的点)。
我只想回答问题的另一部分,通过推测为什么 NASA 会禁止这些功能(基本上链接来自 SO 的相关答案)。由于关于 automatic object destruction, see this SO thread 的未定义行为,特别是对已接受答案的评论:
,因此在 C++ 中比在 C 代码中更不鼓励使用 setjmp
和 longjmp
Generally, whenever there's some way to exit a scope in C++ (return, throw, or whatever), the compiler will place instructions to call the dtors for any automatic variables that need to be destroyed as a result of leaving that block. longjmp()
just jumps to a new location in the code, so it will not provide any chance for the dtors to be called. The standard is actually less specific than that - the standard doesn't say that dtors won't be called - it says that all bets are off. You can't depend on any particular behavior in this case.
[...]
Since smart pointers depend on being destroyed, you will get undefined behavior. It's likely that that undefined behavior would include a refcount not getting decremented. You're 'safe' using longjmp()
as long as you don't longjmp out of code that should cause dtors to be invoked. However, as David Thornley noted in a comment, setjmp()
/longjmp()
can be tricky to use right even in straight C - in C++ they're downright dangerous. Avoid them if at all possible.
那么是什么让 setjmp()
/longjmp()
在 C 语言中变得棘手?看看可能的use cases we can see that one of them is implementation of coroutines. The answer was already given here in the comments @StoryTeler, but could you use goto
across different functions?
You can't in Standard C; labels are local to a single function.
The nearest standard equivalent is the setjmp() and longjmp() pair of functions.
但是,您在 setjmp
和 longjmp
方面的能力也很有限,您可能很快就会 运行 变成 segfault。宝物又可以在评论中找到了:
You can think of a longjmp()
as an "extended return". A successful longjmp()
works like a series of successive returns, unwinding the call stack until it reaches the corresponding setjmp()
. Once the call stack frames are unwound, they are no longer valid. This is in contrast to implementations of coroutines (eg. Modula-2) or continuations (eg. Scheme) where the call stack remains valid after jumping somewhere else. C and C++ only support a single linear call stack, unless you use threads where you create multiple independent call stacks.
据我了解,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;
}
免责声明:仅出于好奇而尝试使用它。我从来没有听说过这个结构,直到我最近阅读了一些关于 NASA 编码指南的论文,其中提到禁止使用这种控制流结构
同时使用 c 和 c++ 标签,因为代码是混合的,我认为实际的相关功能更适合 c 重度用户而不是 c++...:/
对于 setjmp
和 longjmp
"context" 是 执行 上下文,而不是堆栈的实际内容(其中局部变量是通常存储)。
使用 setjmp
和 longjmp
您不能 "roll back" 对局部变量进行更改。
Upon return to the scope of setjmp, all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when
std::longjmp
was executed, except for the non-volatile local variables insetjmp
's scope, whose values are indeterminate if they have been changed since thesetjmp
invocation.
执行longjmp
时a
的值为15,所以这是一个可以预期看到的值(一般是不确定的)。 jmp_buf
仅存储 执行点 。不是程序中每个变量的状态。
我认为您看到的可能是较旧的代码库。在某些编译器中异常不是很流行或不可用的地方。
在更接近系统软件之前,您不应使用 setjmp 和 longjmp。
- 至于控制流程:setjmp returns 两次,longjmp 从不 return秒。
- 第一次调用setjmp时,存储 环境,它 return 为零,
- 然后当您调用 longjmp 时,控制流从 setjmp 传递到 return,并带有参数中提供的值。
- 用例通常被称为“错误处理”,并且 “不要使用这些功能”。
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)
}
}
除了 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
.
“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("second\n"); // prints
longjmp(buf,1); // jumps back to where setjmp was called - making setjmp now return 1
}
void first() {
second();
printf("first\n"); // does not print
}
int main() {
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints
return 0;
}
这就是为什么 setjump() returns 0 的原因,当你检查条件时,它分配 a=15,一旦过程完成,下一步它将给出 42。
这些函数的主要特点是提供了一种偏离标准调用和return序列的方式。这在C中主要用于实现异常处理。setjmp可以像try一样使用(在C++和Java等语言中)。对 longjmp 的调用可以像 throw 一样使用(注意 longjmp() 将控制转移到 setjmp() 设置的点)。
我只想回答问题的另一部分,通过推测为什么 NASA 会禁止这些功能(基本上链接来自 SO 的相关答案)。由于关于 automatic object destruction, see this SO thread 的未定义行为,特别是对已接受答案的评论:
,因此在 C++ 中比在 C 代码中更不鼓励使用setjmp
和 longjmp
Generally, whenever there's some way to exit a scope in C++ (return, throw, or whatever), the compiler will place instructions to call the dtors for any automatic variables that need to be destroyed as a result of leaving that block.
longjmp()
just jumps to a new location in the code, so it will not provide any chance for the dtors to be called. The standard is actually less specific than that - the standard doesn't say that dtors won't be called - it says that all bets are off. You can't depend on any particular behavior in this case.[...]
Since smart pointers depend on being destroyed, you will get undefined behavior. It's likely that that undefined behavior would include a refcount not getting decremented. You're 'safe' using
longjmp()
as long as you don't longjmp out of code that should cause dtors to be invoked. However, as David Thornley noted in a comment,setjmp()
/longjmp()
can be tricky to use right even in straight C - in C++ they're downright dangerous. Avoid them if at all possible.
那么是什么让 setjmp()
/longjmp()
在 C 语言中变得棘手?看看可能的use cases we can see that one of them is implementation of coroutines. The answer was already given here in the comments @StoryTeler, but could you use goto
across different functions?
You can't in Standard C; labels are local to a single function.
The nearest standard equivalent is the setjmp() and longjmp() pair of functions.
但是,您在 setjmp
和 longjmp
方面的能力也很有限,您可能很快就会 运行 变成 segfault。宝物又可以在评论中找到了:
You can think of a
longjmp()
as an "extended return". A successfullongjmp()
works like a series of successive returns, unwinding the call stack until it reaches the correspondingsetjmp()
. Once the call stack frames are unwound, they are no longer valid. This is in contrast to implementations of coroutines (eg. Modula-2) or continuations (eg. Scheme) where the call stack remains valid after jumping somewhere else. C and C++ only support a single linear call stack, unless you use threads where you create multiple independent call stacks.