【发布时间】:2020-01-11 14:59:56
【问题描述】:
代码
#include <csetjmp>
template <typename Callable>
void create_checkpoint(std::jmp_buf buf, Callable&& callable)
{
if (setjmp(buf) != 0)
{
callable();
}
}
#include <iostream>
struct announcer {
int id;
announcer(int id):
id{id}
{
std::cout << "created announcer with id " << id << '\n';
}
~announcer() {
std::cout << "destructing announcer with id " << id << '\n';
}
};
void oopsie(std::jmp_buf buf, bool shouldJump)
{
if (shouldJump)
{
// std::cout << "performing jump...\n";
std::longjmp(buf, 1);
}
}
void test1()
{
std::jmp_buf buf;
announcer a1{1};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, true);
}
void test2()
{
std::jmp_buf buf;
announcer a1{1};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, false);
announcer a2{2};
create_checkpoint(buf, []() {throw std::exception();});
oopsie(buf, true);
}
int main()
{
try
{
test1();
}
catch (...)
{}
try
{
test2();
}
catch (...)
{}
}
上下文
我必须调用一些通过longjmp 报告错误的C 库。为了提供强大的异常保证,我想创建一个与std::lock_guard 非常相似的函数,例如我只写create_checkpoint(buf, handler) 并继续调用C 库函数,直到我分配更多资源(如果我理解正确,则不会调用在setjmp 行下创建的对象的析构函数)。
问题
为什么在这种情况下会调用未定义的行为,我该如何解决?
我如何发现这是未定义的行为?
Printing message to std::cout 之前的std::longjmp 与not printing 产生非常不同的结果,即使该行与控制流几乎没有关系。
我现在明白了什么?
我知道std::longjmp本质上是恢复寄存器并跳转到setjmp宏保存的指令指针。另外,functions are not optimized away,至少在编译期间,有一条指令可以调用longjmp。
将create_checkpoint 转换为宏seems to solve the issue。但我想知道有没有更好的方法来做到这一点?
【问题讨论】:
-
不鼓励在现代 C++ 中使用任何
setjmp/longjmp。事实上,我认为任何使用都表明所讨论的代码应该永远合并到主代码库中。 -
longjmp绕过 C++ 堆栈展开,因此不调用析构函数。您可以通过不使用longjmp来修复它,或者仔细捕捉每个靠近调用它的代码部分并将其转换为异常或适当地处理它。另请注意,setjmp/longjmp不会在setjmp位置恢复非易失性变量,因此它们不值得在处理程序中正确考虑。 -
我应该提一下,我维护了一个非常大的程序,它广泛使用了
setjmp/longjmp。我强烈反对在 C++ 程序中使用它们,尽我所能。它们相当于goto的核。 -
@Eljay,不幸的是,在其中一种情况下,这似乎是不可避免的(libjpeg)。我们不知道整个调用树是否被正确覆盖,所以我认为有一个保护函数会很好。宏似乎可以解决问题,但我不确定它是否是正确的前进方向。
-
我认为这是个好问题。这是 C++ 继承自 C 的东西,但如果在 C++ 中使用会引起很多麻烦。
标签: c++ undefined-behavior setjmp