Practical usage of setjmp and longjmp in C

前端 未结 8 1674
忘了有多久
忘了有多久 2020-11-28 01:48

Can anyone explain me where exactly setjmp() and longjmp() functions can be used practically in embedded programming? I know that these are for err

相关标签:
8条回答
  • 2020-11-28 02:05

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

    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.

    0 讨论(0)
  • 2020-11-28 02:09

    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."

    0 讨论(0)
  • 2020-11-28 02:13

    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.

    0 讨论(0)
  • 2020-11-28 02:20

    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.

    0 讨论(0)
  • 2020-11-28 02:21

    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.

    0 讨论(0)
  • 2020-11-28 02:22

    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.

    0 讨论(0)
提交回复
热议问题