Practical usage of setjmp and longjmp in C
Asked Answered
N

8

148

Can anyone explain me where exactly setjmp() and longjmp() functions can be used practically in embedded programming? I know that these are for error handling. But I'd like to know some use cases.

Nimble answered 4/2, 2013 at 11:5 Comment(3)
And of course, thedailywtf.com/Articles/Longjmp--FOR-SPEED!!!.aspxTanah
Another answer than those given is here stackoverflow.com/questions/7334595/… You may use longjmp() to get out of a signal handler, especially things like a BUS ERROR. This signal can not usually restart. An embedded application may wish to handle this case for safety and robust operation.Bewley
And regarding performance differences of setjmp between BSD and Linux, see "Timing setjmp, and the Joy of Standards", which suggests using sigsetjmp.Selfeffacement
K
121

Error handling
Suppose there is an error deep down in a function nested in many other functions and error handling makes sense only in the top level function.

It would be very tedious and awkward if all the functions in between had to return normally and evaluate return values or a global error variable to determine that further processing doesn't make sense or even would be bad.

That's a situation where setjmp/longjmp makes sense. Those situations are similar to situation where exception in other langages (C++, Java) make sense.

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

It is the case when you need to implement coroutines.

Here is a little demo example. I hope it satisfies the request from Sivaprasad Palas for some example code and answers the question of TheBlastOne how setjmp/longjmp supports the implementation of corroutines (as much as I see it doesn't base on any non-standard or new behaviour).

EDIT:
It could be that it actually is undefined behaviour to do a longjmp down the callstack (see comment of MikeMB; though I have not yet had opportunity to verify that).

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

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration 

void routineA()
{
    int r ;

    printf("- 12 : (A1)\n");

    r = setjmp(bufferA);
    if (r == 0) routineB();

    printf("- 17 : (A2) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20001);

    printf("- 22 : (A3) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20002);

    printf("- 27 : (A4) r=%d\n",r);
}

void routineB()
{
    int r;

    printf("- 34 : (B1)\n");

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10001);

    printf("- 39 : (B2) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10002);

    printf("- 44 : (B3) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10003);
}


int main(int argc, char **argv) 
{
    routineA();
    return 0;
}

Output

- 12 : (A1)
- 34 : (B1)
- 17 : (A2) r=10001
- 39 : (B2) r=20001
- 22 : (A3) r=10002
- 44 : (B3) r=20002
- 27 : (A4) r=10003

Following figure shows the flow of execution:
flow of execution

Warning note
When using setjmp/longjmp be aware that they have an effect on the validity of local variables often not considered.
Cf. my question about this topic.

Kelila answered 4/2, 2013 at 11:11 Comment(19)
Since setjmp prepares, and longjmp executes the jump out of the current call scope back to the setjmp scope, how would that support the implementation of coroutines? I don´t see how one could continue the execution of the routine that longjmp´d out.Sanburn
@Sanburn See the Wikipedia article. You can continue the execution if you setjmp before you longjmp. This is nonstandard.Clardy
@Clardy this was news to me, and was nonexistent when I looked at setjmp/longjmp the last time years and years ago, so: thanks!Sanburn
@Sanburn I wrote most of the code on that page, so you're welome!Clardy
@SivaprasadPala did you take a look at the wiki article?Sanburn
@Sivaprasad Pala: see my example aboveKelila
Coroutines need to run on separate stacks, not on the same as shown in your example. As routineA and routineB use the same stack, it only works for very primitive coroutines. If routineA calls a deeply nested routineC after the first call to routineB and this routineC runs routineB as coroutine, then routineB might even destroy the return stack (not only local variables) of routineC. So without allocating an exclusive stack (through alloca() after calling rountineB?) you will get in serious trouble with this example if used as a recipe.Pasquale
@Tino: I am not sure what you mean exactly, but it seems to me that you want to do something that is quite different to what I have described. So don't be surprised if you get a result that is different. I wonder if you have too high expectations in the concept of coroutines; of course coroutines are not as powerful as multi-threading. Multi-threading would require two separate stacks.Kelila
Please mention, in your answer that jumping down the callstack (from A to B) is undefined behavior).Pauperism
@MikeMB: I think you can't say that in genereal and in particular not about this example. I think it is defined as long as the function you jump to has not been finished by it's return instruction (so its stack frame hasn't been cleaned up and it is still valid). Though I'd be willing to learn if you can give some authoritative references that back your statement.Kelila
@Curd: I don't hav a copy of the actual c standard, but e.g. in the C11 "Committee Draft — April 12, 2011" (and also in earlier standard drafts) in 7.13.2.1 §2 it states that "[...] or if the function containing the invocation of the setjmp macro has terminated execution 248) [...] the behavior is undefined. " (Emphasis mine) They are explicitly not saying returned, but terminated.Pauperism
And in the footnote 248) it reads: "For example, by executing a return statement or because another longjmp call has caused a transfer to a setjmp invocation in a function earlier in the set of nested calls." So calling a longjmp funtion out of a function to a point further up the callstack also terminates that funtion and hence jumping back into it afterwards is UB.Pauperism
@MikeMB: yup, that sounds really like it is undefined behaviour. Though I haven't had opportunity to study your quoted reference in detail and understand what it says exactly. Thanks for the info!Kelila
It is indeed undefined. You will have to make each function run on its own independent stack to switch contexts gracefullyPostremogeniture
In practical terms : once you longjmp down the stack, there is no guarantee that the context higher up the stack won't be overwritten. In general, addresses < the SP are not reliable places to store data. In an embedded system these locations can be overwritten at any moment by an ISR; in a protected mode system, you still have the possibility of function calls overwriting some unknown length of stack (including hidden calls, such as might be used to do a 'long long' multiply, if it can't be coded inline without excessive code).Houri
@Kelila "It would be very tedious and awkward if all the functions in between had to return normally and evaluate return values or a global error variable to determine that further processing doesn't make sense or even would be bad." This is what exacly Rust does for error handling, why it is bad?Lavelle
The coroutines code works only by chance, here is my demonstration that even a small change can break it: stackoverflow.com/questions/70324244/…Jacktar
While it's true that coroutines cannot be implemented with setjmp/longjmp under the guarantees given by the standard, it is also true that it can be done under some reasonable assumptions about real-life architectures: setjmp/longjmp also blindly restore the stack pointer. We can allocate a new stack, relocate the stack pointer with alloca or VLA, and then use setjmp/longjmp to switch between the two stacks.Harpoon
I think you need to manually allocate stack for each coroutine, so longjump won't jump down the stack, but would jump to parallel stackSoulless
C
30

The theory is that you can use them for error handling so that you can jump out of deeply nested call chain without needing to deal with handling errors in every function in the chain.

Like every clever theory this falls apart when meeting reality. Your intermediate functions will allocate memory, grab locks, open files and do all kinds of different things that require cleanup. So in practice setjmp/longjmp are usually a bad idea except in very limited circumstances where you have total control over your environment (some embedded platforms).

In my experience in most cases whenever you think that using setjmp/longjmp would work, your program is clear and simple enough that every intermediate function call in the call chain can do error handling, or it's so messy and impossible to fix that you should do exit when you encounter the error.

Christenechristening answered 4/2, 2013 at 11:42 Comment(13)
Please look at libjpeg. As in C++, most collections of C routines take a struct * to operate on something as a collective. Instead of storing your intermediate functions memory allocations as locals, they can be stored in the structure. This allows a longjmp() handler to free the memory. Also, this does not have so many blasted exceptions tables that all C++ compilers still generate 20 years after the fact.Bewley
Like every clever theory this falls apart when meeting reality. Indeed, temporary allocation and the like make longjmp()ing tricky, since you then have to setjmp() multiple times in the call stack (once for every function that needs to perform some sort of cleanup before it exits, which then needs to "re-raise the exception" by longjmp()ing to the context that it had initially received). It gets even worse if those resources are modified after the setjmp(), since you have to declare them as volatile to prevent the longjmp() from clobbering them.Darb
@artlessnoise those structs have to be locals at some point in the call stack! The fact is that setjmp/longjmp, while it can be used well, is one of those things like pointers and unchecked array access where if you screw it up, the problems are subtle (i.e., the OS steps in to segfault you're program only if you're lucky). The difference is that only libpng and libjpeg and a few others use it; so few examples that nobody is going to get the practice to use it well.Galliot
Plus, much of the new C++ code these days is very performance sensitive stuff (numerical simulations or neural networks), where you're relying on GCC or Clang's ability to slice and dice local, fixed-sized variables (including richly typed parametrically polymorphic structs, see deal.ii) into SIMD registers. If you try to make setjmp/longjmp to work, you suppress the performance benefits of C++ or even C.Galliot
@Galliot This is incorrect. The jmpbuf can be allocated in a structure. The structure can contain other allocated data (or even a static). Care must be taken in the use of stack variables when using setjmp/longjmp (as is returning a pointer to a local). It is only the stack which is 'unwound'. It is identical in theory to exceptions. heap and static variables will survive a longjmp(). I feel this kind of mis-information feeds into a misunderstanding of setjmp/longjmp and C++ exception handling (or I look forward to learning something new).Bewley
@artlessnoise For stuff like physical tensors or transformation matrices, you get notably better performance if you keep them local, because then the tensor components can stay in registers as long as possible, reducing round-trips to addressable RAM for the same tiny amount of data. This ambiguity as to whether it's in a register or addressable RAM, while it improves performance, is part of what makes setjmp/longjmp so iffy for local variables.Galliot
Again, if you're working primarily in terms of statically or dynamically allocated variables rather than locals, you're squandering the performance benefits of C++ or of CGalliot
@Galliot Can you please cite what you are talking about? Chapter 9 of Compiler: Principal.. (Dragon book) has information on data flow analysis. The principal issue is pointer aliasing and this has absolutely nothing to do with this question? For 'C' you can use restrict; if it was C++, I would use exceptions, but the question is tagged 'C'. It is quite possible to write production code that uses non local error handling in 'C' with setjmp/longjmp. What you think a compiler will do with C++ matrix classes has nothing to do with this question?Bewley
@Galliot A compiler is free to put local variable in registers with setjmp/longjmp. It uses the call site setjmp() where the function call ABI must be complete to ensure that the registers in the basic block are saved. It in no way says that all variables in a program using setjmp/longjmp need to always use the stack. If that is what your thought is, you are completely misguided.Bewley
I'm citing the C side of cppreference: en.cppreference.com/w/c/program/setjmp "Upon return to the scope of setjmp: all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when longjmp was executed, except for the non-volatile local variables in the function containing the invocation of setjmp, whose values are indeterminate if they have been changed since the setjmp invocation."Galliot
And no, I did not say that setjmp/longjmp requires that everything be on the stack, nor did I bring up pointer aliasing.Galliot
@artlessnoise What you said in your first comment is that setjmp/longjmp is fine if you avoid local variables. I'm saying that you want to use local variables because it improves performance.Galliot
@Galliot I mean around the call site of setjmp/longjmp, not everywhere in an entire program. And, I don't see this exact quote.Bewley
F
22

I've written a Java-like exception handling mechanism in C using setjmp(), longjmp() and system functions.

It catches custom exceptions but also signals like SIGSEGV.

It features infinite nesting of exception handling blocks, which works accross function calls, and supports the two most common threading implementations.

It allows you to define a tree hierarchy of exception classes that feature link-time inheritance, and the catch statement walks this tree to see if it needs to catch or pass on.

Here's a sample of how 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's 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's also a C module that contains the logic for signal handling and some bookkeeping.

It was extremely tricky to implement I can tell you and I almost quit. I really pushed to make it as close to Java as possible; I found it surprising how far I got with just C.

Give me a shout if you're interested.

Framing answered 23/12, 2017 at 21:21 Comment(8)
I'm surprised this is possible without actual compiler support for the custom exceptions. But what's really interesting is how signals convert to exceptions.Litha
I will ask one thing: what about exceptions that end up never getting caught? How will main() exit?Litha
someone else must have down voted this, since I'm rather interested in seeing the remainder of the code. They must have not liked the macro based approach.Litha
@PaulStelian And, here's your answer to how main() will exit on uncaught exeption. Please upvote this answer :-)Framing
I asked about the particular implementation of this answer. What you linked is what applies to a normal implementation.Litha
@PaulStelian Ah, I see what you mean now. Run-time exceptions that are not caught I believe were raised again so that the general (platform dependent) answer applies. Not caught custom exceptions were printed and ignored. See Progagation section in the README I've posted my April 1999 code to GitHub (see link in edited answer). Have a look; it was a hard nut to crack. Would be nice to hear what you think.Framing
Had a short look at the README, pretty nice one there. So basically it propagates to the outermost try block and is reported, akin to JavaScript's async functions. Nice. I will look at the source code itself later.Litha
While SIGSEGV is possible, in all likelihood it is problematic. Something most likely ran over the stack, the heap or static data. So whatever your handler is referencing needs to take care. You really need to allocated a minimal environment to carefully setup for the SIGSEGV handler, but what you have is a good start.Bewley
M
14

The combination of setjmp and longjmp is "super strength goto". Use with EXTREME care. However, as others have explained, a longjmp is very useful to get out of a nasty error situation, when you want to get me back to the beginning quickly, rather than having to trickle back an error message for 18 layers of functions.

However, just like goto, but worse, you have to be REALLY careful how you use this. A longjmp will just get you back to the beginning of the code. It won't affect all the other states that may have changed between the setjmp and getting back to where setjmp started. So allocations, locks, half-initialized data structures, etc, are still allocated, locked and half-initialized when you get back to where setjmp was called. This means, you have to really care for the places where you do this, that it's REALLY ok to call longjmp without causing MORE problems. Of course, if the next thing you do is "reboot" [after storing a message about the error, perhaps] - in an embedded system where you've discovered that the hardware is in a bad state, for example, then fine.

I have also seen setjmp/longjmp used to provide very basic threading mechanisms. But that's pretty special case - and definitely not how "standard" threads work.

Edit: One could of course add code to "deal with cleaning up", in the same way that C++ stores the exception points in the compiled code and then knows what gave an exception and what needs cleaning up. This would involve some sort of function pointer table and storing away "if we jump out from below here, call this function, with this argument". Something like this:

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 could do "complete exception handling like C++". But it's quite messy, and relies on the code being well written.

Mane answered 4/2, 2013 at 11:48 Comment(1)
+1, of course you could in theory implement clean exception handling by calling setjmp to guard every initialization, a la C++… and worth mentioning that using it for threading is nonstandard.Clardy
S
13

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

Normally, if the function to test calls another function, you can declare a stub function for it to call that will mimic what the actual function does to test certain flows. In this case however, the function calls exit which does not return. The stub needs to somehow emulate this behavior. setjmp and longjmp can do that 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 the function to test, then in the stubbed exit you call longjmp to return directly back to your test case.

Also note that the redefined exit has a special variable that it checks to see if you actually want to exit the program and calls _exit to do so. If you don't do this, your test program may not quit cleanly.

Stake answered 24/7, 2016 at 23:50 Comment(1)
@milanHrabos The done flag is set to 0 when the tests are being run. When exit(2) is called, the stub function first checks if done is 0, which it is. Then checks that the global should_exit is 1 (true) and the global expected_code is 2 (true). Then longjmp is called with a status of 1. This jumps back to test_div0 where 1 is returned from setjmp.Stake
P
11

Since you mention embedded, I think it's worth noting a non-use case: when your coding standard prohibit it. For instance MISRA (MISRA-C:2004:Rule 20.7) and JFS (AV Rule 20) : "The setjmp macro and the longjmp function shall not be used."

Precept answered 4/2, 2013 at 16:27 Comment(0)
M
8

Hands down, the most crucial use of setjmp/longjmp is that it acts a "non-local goto jump". Goto command (and there rare instances where you will need to use goto over for and while loops) is most-used-safely in the same scope. If you use goto to jump across scopes (or across auto allocation), you will most-likely corrupt your program's stack. setjmp/longjmp avoids this by saving the stack info at the location you want to jump to. Then, when you jump, it loads this stack info. Without this feature, C programmers would most likely had to turn to assembly programming to solve issues that only setjmp/longjmp could solve. Thank God it exists. Everything in the C library is extremely important. You will know when you need it.

Melentha answered 14/2, 2020 at 18:44 Comment(1)
"Everything in the C library is extremely important." There is a whole bunch of deprecated stuff and stuff that was never good, like locales.Uninterested
M
1

Apart from error handling, the other thing that you can do and was not previously mentioned is to implement tail rectursive computation in C in a smart way.

This is actually how are implemented the continuations in C without converting the input code in continuation passing style.

Mattins answered 21/10, 2020 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.