Practical use of setjmp and longjmp in C

Can someone explain to me exactly where the setjmp() and longjmp() functions can be used practically in embedded programming? I know that they are designed to handle errors. But I would like to know some use cases.

+75
c
Feb 04 '13 at 11:05
source share
7 answers

Error processing
Suppose there is an error in a function nested in many other functions, and error handling only makes sense in a top-level function.

It would be very tiring and inconvenient if all the functions between them had to return normally and evaluate the return values ​​or the global error variable to determine that further processing does not make sense or even will be bad.

This is a situation where setjmp / longjmp makes sense. These situations are similar to the situation when the meaning in other languages ​​(C ++, Java) makes sense.

Coroutines
Besides error handling, I can think of another situation when you need setjmp / longjmp in C:

This is the case when you need to implement coroutines .

Here is a small demo. I hope it satisfies the Sivaprasad Palas request for sample code and answers the question of TheBlastOne how setjmp / longjmp supports the implementation of corrections (as far as I see, it is not based on any non-standard or new behavior).

EDIT:
Maybe this is actually the undefined behavior of longjmp down the stop-cost (see MikeMB's comment, although I have not yet had the opportunity to verify this).

 #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; } 

The following figure shows the progress:
flow of execution

Warning note
When using setjmp / longjmp, you should be aware that they affect the validity of local variables, which are often ignored.
Wed my question on this .

+65
04 Feb '13 at 11:11
source share
— -

The theory is that you can use them to handle errors so you can jump out of a deeply nested call chain without having to handle error handling in every function in the chain.

Like any smart theory, it falls apart when it encounters reality. Your intermediate functions will allocate memory, locks, open files and do all kinds of things that need to be cleaned up. Therefore, in practice, setjmp / longjmp usually a bad idea, except in very limited situations, when you have full control over your environment (some built-in platforms).

In my experience, in most cases, whenever you think that using setjmp / longjmp will work, your program is understandable and simple enough that every call to an intermediate function in a call chain can handle errors, or it is so random and impossible to fix that you should exit when you encounter an error.

+16
04 Feb '13 at
source share

The combination of setjmp and longjmp is " goto super goto ". Use with EXTREME care. However, as others have explained, a longjmp very useful to get out of an unpleasant situation with an error when you want to get me back to the beginning quickly, instead of resetting the error message for 18 levels of functions.

However, like goto , but worse, you have to REALLY be careful how you use it. A longjmp will bring you back to the beginning of the code. This will not affect all other states that may change between setjmp and return to the beginning of setjmp . Thus, allocations, locks, semi-initialized data structures, etc. Still allocated, blocked, and partially initialized when you return to where setjmp was called. This means that you really need to take care of the places where you do it, which is REALLY normal to call longjmp without causing MORE problems. Of course, if the next thing you do is “reboot” [after saving the error message, maybe] - in the embedded system, where you find that the equipment is in poor condition, for example, then it’s fine.

I also saw that setjmp / longjmp used to provide very simple threading mechanisms. But this is a rather special case - and definitely not how the "standard" streams work.

Edit: You could, of course, add code to “handle the cleanup,” just like C ++ stores exception points in compiled code, and then knows what the exception is and what needs to be cleaned up. This will be due to some sort of function pointer table and saving "if we jump from below here, call this function with this argument." Something like that:

 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); } 

With this system, you can perform "full exception handling such as C ++." But it's pretty messy and relies on well-written code.

+9
Feb 04 '13 at
source share

Since you mention the built-in, I think it's worth noting a case of non-use : when your coding standard prohibits this. For example, MISRA (MISRA-C: 2004: rule 20.7) and JFS (AV rule 20): "The setjmp macro and the longjmp function should not be used."

+6
Feb 04 '13 at 16:27
source share

setjmp and longjmp can be very useful in unit testing.

Suppose we want to test the following module:

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

Usually, if a test function calls another function, you can declare a stub function to call it, which will mimic what the actual function does to test certain threads. However, in this case, the function calls exit , which does not return. The piece must somehow imitate this behavior. setjmp and longjmp can do this for you.

To test this function, we can create the following test program:

 #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); } 

In this example, you use setjmp before entering this function for testing, and then in the exit you call longjmp to go back to the test file.

Also note that the overridden exit has a special variable that checks to see if you really want to exit the program and call _exit . If you do not, your test program may not fail.

+5
Jul 24 '16 at 23:50
source share

setjmp () and longjmp () are useful for handling errors and interrupts encountered in a low-level program routine.

0
04 Feb '13 at 11:28
source share

I wrote a Java-like exception handling mechanism in C using setjmp() , longjmp() and system functions. It catches custom exceptions, but also signals such as SIGSEGV . It has an infinite nesting of exception handling blocks that works through function calls and supports the two most common thread implementations. This allows you to define a tree hierarchy of exception classes that have inheritance at build time, and the catch statement traverses this tree to see if it needs to be intercepted or passed.

Here is an example of how the code looks using this:

 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("", "")); } 

And here is the part of the include file that contains a lot of logic:

 #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 */ 

There is also module C, which contains logic for signal processing and some accounting.

It was extremely difficult to implement, I can tell you, and I almost left. I really tried to make it as close to Java as possible; It was amazing to me how far I have come only with C.

Give me a shout if you're interested.

0
Dec 23 '17 at 21:21
source share



All Articles