C中setjmp和longjmp的实际用法

任何人都可以解释我在哪里可以在embedded式编程中使用setjmp()longjmp()函数? 我知道这些是为了处理错误。 但是我想知道一些用例。

error handling
假设嵌套在许多其他函数中的函数内部存在一个错误,而error handling仅在顶层函数中才有意义。

如果所有函数之间的所有函数都必须正常返回并计算返回值或全局错误variables以确定进一步的处理没有意义或甚至是不好的,那将是非常单调和尴尬的。

这是setjmp / longjmp有意义的情况。 这些情况类似于其他语言(C ++,Java)中的exception情况。

协同程序
除了error handling,我还可以想到另一种情况,你需要setjmp / longjmp在C:

当你需要执行协程时就是这种情况。

这里是一个小示例。 我希望它能满足来自Sivaprasad Palas的一些示例代码的请求,并回答TheBlast的问题一个setjmp / longjmp如何支持corroutines的实现(就像我看到它不基于任何非标准或新的行为一样)。

编辑:
这可能是因为它实际上未定义的行为来做一个longjmp的调用堆栈(请参阅MikeMB的评论;虽然我还没有机会来validation)。

 #include <stdio.h> #include <setjmp.h> jmp_buf bufferA, bufferB; void routineB(); // forward declaration void routineA() { int r ; printf("(A1)\n"); r = setjmp(bufferA); if (r == 0) routineB(); printf("(A2) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20001); printf("(A3) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20002); printf("(A4) r=%d\n",r); } void routineB() { int r; printf("(B1)\n"); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10001); printf("(B2) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10002); printf("(B3) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10003); } int main(int argc, char **argv) { routineA(); return 0; } 

下图显示了执行stream程:
执行流程

警告说明
当使用setjmp / longjmp时,请注意它们对通常不考虑的局部variables的有效性有影响。
参看 我关于这个话题的问题 。

理论上你可以使用它们进行error handling,这样你就可以跳出深层嵌套的调用链,而不需要处理链中每个函数的error handling。

就像每一个聪明的理论一样,在满足现实的时候这个理论就会崩溃 你的中间函数将分配内存,抓取锁,打开文件,并做各种不同的事情,需要清理。 所以在实践中, setjmp / longjmp通常是一个坏主意,除非在非常有限的情况下,您可以完全控制您的环境(某些embedded式平台)。

根据我的经验,在大多数情况下,只要你认为使用setjmp / longjmp可以工作,你的程序就足够清晰和简单了,以致调用链中的每个中间函数调用都可以进行error handling,或者如此混乱,无法修复当你遇到错误时exit

setjmplongjmp的组合是“超强度goto ”。 与EXTREME护理一起使用。 然而,正如其他人所解释的那样, longjmp对于摆脱糟糕的错误情况是非常有用的,当你想让get me back to the beginning快速get me back to the beginning ,而不是不得不为18层function滴漏一条错误信息。

然而,就像goto ,但更糟糕的是,你必须非常小心如何使用它。 longjmp只会让你回到代码的开头。 它不会影响setjmpsetjmp开始的所有其他状态。 所以当你回到调用setjmp地方时,分配,锁,半初始化的数据结构等仍然被分配,locking和半初始化。 这意味着,你必须真正关心你这样做的地方,确实可以调用longjmp而不会导致更多的问题。 当然,如果接下来的事情是“重新启动”(在存储关于错误的消息之后),例如在发现硬件处于不良状态的embedded式系统中,那么很好。

我也见过setjmp / longjmp用来提供非常基本的线程机制。 但是,这是非常特殊的情况 – 而且绝对不是“标准”线程的工作方式。

编辑:当然可以添加代码来“处理清理”,就像C ++在编译代码中存储exception点,然后知道是什么导致exception以及需要清理什么一样。 这将涉及某种函数指针表,并存储“如果我们从这里跳下来,用这个参数调用这个函数”。 像这样的东西:

 struct { void (*destructor)(void *ptr); }; void LockForceUnlock(void *vlock) { LOCK* lock = vlock; } LOCK func_lock; void func() { ref = add_destructor(LockForceUnlock, mylock); Lock(func_lock) ... func2(); // May call longjmp. Unlock(func_lock); remove_destructor(ref); } 

有了这个系统,你可以做“像C ++一样完成exception处理”。 但是这很麻烦,而且依赖于编写的代码。

既然你提到embedded式,我认为值得注意一个非使用的情况 :当你的编码标准禁止它。 例如MISRA(MISRA-C:2004:规则20.7)和JFS (AV规则20):“不能使用setjmpmacros和longjmp函数”。

setjmp()和longjmp()对于处理在程序的低级子程序中遇到的错误和中断很有用。

setjmplongjmp在unit testing中非常有用。

假设我们要testing下面的模块:

 #include <stdlib.h> int my_div(int x, int y) { if (y==0) exit(2); return x/y; } 

通常情况下,如果要testing的函数调用另一个函数,则可以声明一个stub函数来调用它,它将模仿实际函数testing某些stream的function。 在这种情况下,函数调用不返回的exit 。 存根需要以某种方式模拟这种行为。 setjmplongjmp可以为你做到这一点。

为了testing这个function,我们可以创build下面的testing程序:

 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <setjmp.h> // redefine assert to set a boolean flag #ifdef assert #undef assert #endif #define assert(x) (rslt = rslt && (x)) // the function to test int my_div(int x, int y); // main result return code used by redefined assert static int rslt; // variables controling stub functions static int expected_code; static int should_exit; static jmp_buf jump_env; // test suite main variables static int done; static int num_tests; static int tests_passed; // utility function void TestStart(char *name) { num_tests++; rslt = 1; printf("-- Testing %s ... ",name); } // utility function void TestEnd() { if (rslt) tests_passed++; printf("%s\n", rslt ? "success" : "fail"); } // stub function void exit(int code) { if (!done) { assert(should_exit==1); assert(expected_code==code); longjmp(jump_env, 1); } else { _exit(code); } } // test case void test_normal() { int jmp_rval; int r; TestStart("test_normal"); should_exit = 0; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(12,3); } assert(jmp_rval==0); assert(r==4); TestEnd(); } // test case void test_div0() { int jmp_rval; int r; TestStart("test_div0"); should_exit = 1; expected_code = 2; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(2,0); } assert(jmp_rval==1); TestEnd(); } int main() { num_tests = 0; tests_passed = 0; done = 0; test_normal(); test_div0(); printf("Total tests passed: %d\n", tests_passed); done = 1; return !(tests_passed == num_tests); } 

在这个例子中,在进入要testing的函数之前使用setjmp ,然后在stubbed exit调用longjmp直接返回到testing用例。

还要注意,重定义的exit有一个特殊的variables,它检查你是否真的想退出程序并调用_exit来做这件事。 如果你不这样做,你的testing程序可能不会干净地退出。