谁能解释我在嵌入式编程中实际可以使用的确切位置setjmp()
和longjmp()
功能吗?我知道这些用于错误处理。但我想知道一些用例。
longjmp()
摆脱信号处理程序的困扰,尤其是像这样的东西BUS ERROR
。该信号通常无法重启。嵌入式应用程序可能希望处理这种情况,以确保安全性和鲁棒性。
谁能解释我在嵌入式编程中实际可以使用的确切位置setjmp()
和longjmp()
功能吗?我知道这些用于错误处理。但我想知道一些用例。
longjmp()
摆脱信号处理程序的困扰,尤其是像这样的东西BUS ERROR
。该信号通常无法重启。嵌入式应用程序可能希望处理这种情况,以确保安全性和鲁棒性。
Answers:
错误处理
假设嵌套在许多其他函数中的某个函数中存在一个深入的错误,并且错误处理仅在顶级函数中才有意义。
如果介于两者之间的所有函数必须正常返回并评估返回值或全局错误变量,以确定进一步的处理没有意义甚至是不好的,那将是非常繁琐和尴尬的。
在这种情况下,setjmp / longjmp有意义。这些情况类似于其他语言(C ++,Java)中的例外有意义的情况。
协程
除了错误处理外,我还可以想到另一种需要在C中使用setjmp / longjmp的情况:
当您需要实现协程时就是这种情况。
这是一个演示示例。我希望它能够满足Sivaprasad Palas对某些示例代码的请求,并回答TheBlastOne的问题setjmp / longjmp如何支持例程的实现(就我所知,它不基于任何非标准或新的行为)。
编辑:
可能是降低调用堆栈实际上是未定义的行为(请参阅MikeMB的评论;尽管我还没有机会进行验证)。longjmp
#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;
}
下图显示了执行流程:
警告说明
使用setjmp / longjmp时,请注意它们会影响通常不考虑的局部变量的有效性。
cf. 我对这个话题的问题。
routineA
与routineB
使用相同的堆栈,它仅适用于非常原始的协同程序。如果在第一个调用之后routineA
调用了一个深层嵌套routineC
,routineB
并且routineC
它routineB
作为协程运行,那么routineB
甚至可能破坏的返回堆栈(不仅是局部变量)routineC
。因此,如果不分配专用堆栈(通过alloca()
调用rountineB
?之后),则该示例用作配方时,将遇到严重麻烦。
从理论上讲,您可以将它们用于错误处理,这样您就可以跳出深度嵌套的调用链,而无需处理链中每个函数的错误。
像每一个聪明的理论一样,当遇到现实时,这也就瓦解了。您的中间函数将分配内存,获取锁,打开文件并执行各种需要清除的不同操作。因此,在实践中setjmp
/ longjmp
通常不是一个好主意,除非在非常有限的情况下您可以完全控制环境(某些嵌入式平台)。
以我的经验,在大多数情况下,只要您认为使用setjmp
/ longjmp
都能奏效,您的程序就足够清晰和简单,以至于调用链中的每个中间函数调用都可以进行错误处理,或者太杂乱且无法修复,以致您无法在需要exit
时进行处理遇到错误。
libjpeg
。与C ++中一样,大多数C例程集合都以a struct *
作为集合来对某些事物进行操作。可以将中间功能的内存分配存储为结构,而不必将它们作为本地存储。这允许longjmp()
处理程序释放内存。而且,它没有那么多爆炸性的异常表,以至于所有C ++编译器仍然在事实发生20年后生成。
Like every clever theory this falls apart when meeting reality.
确实,临时分配之类的方法很longjmp()
棘手,因为您随后必须setjmp()
在调用堆栈中多次(对于每个需要在退出之前执行某种清除操作的函数一次,然后需要“重新引发异常”)通过longjmp()
查看它最初收到的上下文)。如果在之后修改了这些资源,情况将变得更加糟糕setjmp()
,因为您必须声明它们volatile
以防止longjmp()
破坏它们。
的组合setjmp
,并longjmp
为“超级力量goto
”。使用时要格外小心。然而,正如其他人所解释的,longjmp
是非常有用的,得到一个讨厌的错误情形的,当你想get me back to the beginning
快速,而不必淌回一个错误消息18层的功能。
但是,就像goto
,但更糟的是,您必须非常小心如何使用它。A longjmp
将使您回到代码的开头。它不会影响到setjmp
和返回setjmp
起点之间可能已更改的所有其他状态。因此,当您回到setjmp
调用位置时,分配,锁,半初始化的数据结构等仍会被分配,锁定和半初始化。这意味着,您必须真正关心进行此操作的地方,可以longjmp
无问题地拨打电话。当然,如果下一步是“重启” [也许是在存储有关错误的消息之后],然后在嵌入式系统中发现硬件处于不良状态(例如)。
我也曾经setjmp
/ longjmp
曾经提供过非常基本的线程机制。但这是非常特殊的情况-绝对不是“标准”线程的工作方式。
编辑:当然,您可以将代码添加到“清理处理”中,就像C ++将异常点存储在已编译的代码中一样,然后知道产生异常的内容和需要清除的内容。这将涉及某种函数指针表并存储“如果我们从此处跳出,请使用此参数调用此函数”。像这样:
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 ++一样的完整异常处理”。但这很混乱,并且依赖于编写得当的代码。
setjmp
以保护每次初始化来实现干净的异常处理,例如C ++……并且值得一提的是,将其用于线程化是非标准的。
setjmp
并且longjmp
可以在单元测试是非常有用的。
假设我们要测试以下模块:
#include <stdlib.h>
int my_div(int x, int y)
{
if (y==0) exit(2);
return x/y;
}
通常,如果要测试的函数调用另一个函数,则可以声明一个存根函数供其调用,该存根函数将模仿实际函数对某些流进行测试的行为。但是,在这种情况下,该函数调用exit
不返回。存根需要以某种方式模仿此行为。 setjmp
并longjmp
可以为您做到这一点。
要测试此功能,我们可以创建以下测试程序:
#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);
}
在此示例中,您setjmp
在输入要测试的函数之前使用,然后在存根中exit
调用longjmp
直接返回到测试用例。
还要注意,重定义exit
有一个特殊变量,它会检查您是否确实要退出程序并进行调用_exit
。如果您不这样做,您的测试程序可能不会干净退出。
我写了一个类似Java的异常处理机制使用C中的setjmp()
,longjmp()
和系统功能。它不仅可以捕获自定义异常,还可以捕获类似的信号SIGSEGV
。它具有异常处理块的无限嵌套功能,可跨函数调用工作,并支持两种最常见的线程实现。它允许您定义具有链接时继承特征的异常类的树层次结构,以及catch
语句遍历该树以查看其是否需要捕获或传递。
这是使用此代码的代码示例:
try
{
*((int *)0) = 0; /* may not be portable */
}
catch (SegmentationFault, e)
{
long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' };
((void(*)())f)(); /* may not be portable */
}
finally
{
return(1 / strcmp("", ""));
}
这是包含文件的一部分,其中包含很多逻辑:
#ifndef _EXCEPT_H
#define _EXCEPT_H
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include "Lifo.h"
#include "List.h"
#define SETJMP(env) sigsetjmp(env, 1)
#define LONGJMP(env, val) siglongjmp(env, val)
#define JMP_BUF sigjmp_buf
typedef void (* Handler)(int);
typedef struct _Class *ClassRef; /* exception class reference */
struct _Class
{
int notRethrown; /* always 1 (used by throw()) */
ClassRef parent; /* parent class */
char * name; /* this class name string */
int signalNumber; /* optional signal number */
};
typedef struct _Class Class[1]; /* exception class */
typedef enum _Scope /* exception handling scope */
{
OUTSIDE = -1, /* outside any 'try' */
INTERNAL, /* exception handling internal */
TRY, /* in 'try' (across routine calls) */
CATCH, /* in 'catch' (idem.) */
FINALLY /* in 'finally' (idem.) */
} Scope;
typedef enum _State /* exception handling state */
{
EMPTY, /* no exception occurred */
PENDING, /* exception occurred but not caught */
CAUGHT /* occurred exception caught */
} State;
typedef struct _Except /* exception handle */
{
int notRethrown; /* always 0 (used by throw()) */
State state; /* current state of this handle */
JMP_BUF throwBuf; /* start-'catching' destination */
JMP_BUF finalBuf; /* perform-'finally' destination */
ClassRef class; /* occurred exception class */
void * pData; /* exception associated (user) data */
char * file; /* exception file name */
int line; /* exception line number */
int ready; /* macro code control flow flag */
Scope scope; /* exception handling scope */
int first; /* flag if first try in function */
List * checkList; /* list used by 'catch' checking */
char* tryFile; /* source file name of 'try' */
int tryLine; /* source line number of 'try' */
ClassRef (*getClass)(void); /* method returning class reference */
char * (*getMessage)(void); /* method getting description */
void * (*getData)(void); /* method getting application data */
void (*printTryTrace)(FILE*);/* method printing nested trace */
} Except;
typedef struct _Context /* exception context per thread */
{
Except * pEx; /* current exception handle */
Lifo * exStack; /* exception handle stack */
char message[1024]; /* used by ExceptGetMessage() */
Handler sigAbrtHandler; /* default SIGABRT handler */
Handler sigFpeHandler; /* default SIGFPE handler */
Handler sigIllHandler; /* default SIGILL handler */
Handler sigSegvHandler; /* default SIGSEGV handler */
Handler sigBusHandler; /* default SIGBUS handler */
} Context;
extern Context * pC;
extern Class Throwable;
#define except_class_declare(child, parent) extern Class child
#define except_class_define(child, parent) Class child = { 1, parent, #child }
except_class_declare(Exception, Throwable);
except_class_declare(OutOfMemoryError, Exception);
except_class_declare(FailedAssertion, Exception);
except_class_declare(RuntimeException, Exception);
except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */
except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */
except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */
except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */
except_class_declare(BusError, RuntimeException); /* SIGBUS */
#ifdef DEBUG
#define CHECKED \
static int checked
#define CHECK_BEGIN(pC, pChecked, file, line) \
ExceptCheckBegin(pC, pChecked, file, line)
#define CHECK(pC, pChecked, class, file, line) \
ExceptCheck(pC, pChecked, class, file, line)
#define CHECK_END \
!checked
#else /* DEBUG */
#define CHECKED
#define CHECK_BEGIN(pC, pChecked, file, line) 1
#define CHECK(pC, pChecked, class, file, line) 1
#define CHECK_END 0
#endif /* DEBUG */
#define except_thread_cleanup(id) ExceptThreadCleanup(id)
#define try \
ExceptTry(pC, __FILE__, __LINE__); \
while (1) \
{ \
Context * pTmpC = ExceptGetContext(pC); \
Context * pC = pTmpC; \
CHECKED; \
\
if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \
pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \
{ \
pC->pEx->scope = TRY; \
do \
{
#define catch(class, e) \
} \
while (0); \
} \
else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \
pC->pEx->ready && ExceptCatch(pC, class)) \
{ \
Except *e = LifoPeek(pC->exStack, 1); \
pC->pEx->scope = CATCH; \
do \
{
#define finally \
} \
while (0); \
} \
if (CHECK_END) \
continue; \
if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \
pC->pEx->ready = 1; \
else \
break; \
} \
ExceptGetContext(pC)->pEx->scope = FINALLY; \
while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \
while (ExceptGetContext(pC)->pEx->ready-- > 0)
#define throw(pExceptOrClass, pData) \
ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__)
#define return(x) \
{ \
if (ExceptGetScope(pC) != OUTSIDE) \
{ \
void * pData = malloc(sizeof(JMP_BUF)); \
ExceptGetContext(pC)->pEx->pData = pData; \
if (SETJMP(*(JMP_BUF *)pData) == 0) \
ExceptReturn(pC); \
else \
free(pData); \
} \
return x; \
}
#define pending \
(ExceptGetContext(pC)->pEx->state == PENDING)
extern Scope ExceptGetScope(Context *pC);
extern Context *ExceptGetContext(Context *pC);
extern void ExceptThreadCleanup(int threadId);
extern void ExceptTry(Context *pC, char *file, int line);
extern void ExceptThrow(Context *pC, void * pExceptOrClass,
void *pData, char *file, int line);
extern int ExceptCatch(Context *pC, ClassRef class);
extern int ExceptFinally(Context *pC);
extern void ExceptReturn(Context *pC);
extern int ExceptCheckBegin(Context *pC, int *pChecked,
char *file, int line);
extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class,
char *file, int line);
#endif /* _EXCEPT_H */
还有一个C模块,其中包含用于信号处理和某些簿记的逻辑。
我可以告诉你,实施起来非常棘手,我几乎退出了。我真的努力使它尽可能接近Java。我发现仅凭C语言走到多远就感到惊讶。
如果您有兴趣,请给我喊。
不言而喻,setjmp / longjmp的最关键用法是它起到了“非本地goto跳转”的作用。Goto命令(在少数情况下,您需要在for和while循环中使用goto over)在同一范围内使用最安全。如果使用goto在范围(或自动分配)之间跳转,则很有可能会破坏程序的堆栈。setjmp / longjmp通过将堆栈信息保存在要跳转到的位置来避免这种情况。然后,当您跳转时,它将加载此堆栈信息。没有此功能,C程序员很可能不得不转向汇编编程来解决只有setjmp / longjmp才能解决的问题。感谢上帝,它的存在。C库中的所有内容都非常重要。您会在需要时知道。