包装 setjmp 和 longjmp 有哪些奇怪之处?

4

我第一次使用setjmp和longjmp,但当我尝试包装它们时,遇到了一个问题。我将代码简化为以下示例:

#include <stdio.h>
#include <setjmp.h>

jmp_buf jb;

int mywrap_save()
{
  int i = setjmp(jb);
  return i;
}

int mywrap_call()
{
  longjmp(jb, 1);
  printf("this shouldn't appear\n");
}

void example_wrap()
{
  if (mywrap_save() == 0){
    printf("wrap: try block\n");
    mywrap_call();
  } else {
    printf("wrap: catch block\n");
  }
}

void example_non_wrap()
{
  if (setjmp(jb) == 0){
    printf("non_wrap: try block\n");
    longjmp(jb, 1);
  }  else {
    printf("non_wrap: catch block\n");
  }
}

int main()
{
  example_wrap();
  example_non_wrap();
}

起初我认为example_wrap()和example_non_wrap()的行为是相同的。然而,运行程序的结果(GCC 4.4,Linux)却不同:

wrap: try block
non_wrap: try block
non_wrap: catch block

如果我在gdb中跟踪程序,尽管mywrap_save()返回1,但在返回之后的else分支被奇怪地忽略了。有人能解释一下发生了什么吗?

1
如果你想在C语言中构建类似于异常处理的系统:最好不要这样做。虽然看起来很可爱,但是没有RAII或垃圾回收机制,你会遇到资源泄漏的问题。在C语言中,使用单入口/单出口和goto是更好的错误处理技术。 - jamesdlin
谢谢你的提醒,那不是我的意图。话虽如此,你可以在C语言中使用一个包装了malloc/free的库来进行垃圾回收。例如,可以查找Boehm GC。 - Max
即使使用了垃圾回收,仍然有比内存更多的资源(例如文件句柄、互斥锁等)。 - jamesdlin
2个回答

10
 The longjmp() routines may not be called after the routine which called
 the setjmp() routines returns.

换句话说,你正在破坏你的堆栈。

你可以查看汇编代码,看看能否拼凑出真正发生了什么。


1
所以你需要使用宏来调用 mywrap_save() - caf
太棒了,完全正确。我真笨,之前没有意识到这一点。代码没有崩溃,因此我没有发现它。我自作自受,使用setjmp和longjmp :) - Max

1

setjmp() 会保存当前的调用栈并标记一个点。当调用栈增长时,无论距离标记点有多远,你都可以使用 longjmp() 去到标记点,就像你从未离开过那个点。

在你的代码中,当从 mywrap_save() 返回时,标记点已经失效了,点周围的栈空间是脏的,因此你不能回到一个脏点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接