Search code examples
csetjmp

Why does setjmp/longjmp


I want to use setjmp/longjmp to reuse some code inside the main function (NOTE: this is only an exercise and not something I ever seriously plan on doing in the real world). The following code is what I've came up with:

#include <stdio.h>
#include <setjmp.h>
jmp_buf jmp_body, jmp_ret;
    
void func(void)
{
    if (setjmp(jmp_ret) == 0)
        longjmp(jmp_body, 1);
}

int main()
{
    int x = 0;
    if (setjmp(jmp_body) == 1) {
        printf("Body %d\n", ++x);
        longjmp(jmp_ret, 1);
    }
    func();
    func();
    func();
    return 0;
}

The way I expected this code to work is the following:

  1. The main() function is going to remember where the 'body' part is and skip it using if (setjmp(jmp_body) == 1).
  2. The func() call is going to temporarily jump to the body using longjmp(jmp_body) after remembering where the body is supposed to return using if (setjmp(jmp_ret) == 0)
  3. The body is going to execute and jump back to the func() call using longjmp(jmp_ret, 1)
  4. The func() is just going to return to main() as expected.

Therefore, what I expected the code to print is the following:

Body 1
Body 2
Body 3

Instead, it loops forever continually executing the body which indicates to me the func() call isn't returning where it's supposed to and instead might be returning above itself executing itself over and over again.

In comparison, the following code prints just what I expected:

#include <stdio.h>
#include <setjmp.h>
jmp_buf jmp_body, jmp_ret;

int main()
{
    int x = 0;
    if (setjmp(jmp_body) == 1) {
        printf("Body %d\n", ++x);
        longjmp(jmp_ret, 1);
    }
    if (setjmp(jmp_ret) == 0)
        longjmp(jmp_body, 1);
    if (setjmp(jmp_ret) == 0)
        longjmp(jmp_body, 1);
    if (setjmp(jmp_ret) == 0)
        longjmp(jmp_body, 1);
    return 0;
}

What is it about putting if (setjmp(jmp_ret) == 0) longjmp(jmp_body, 1) inside a function call that makes the original approach invalid?


Solution

  • TL/DR - you can't jump back into a function you jumped out of.

    7.13.2.1 The longjmp function
    ...
    2     The longjmp function restores the environment saved by the most recent invocation of the setjmp macro in the same invocation of the program with the corresponding jmp_buf argument. If there has been no such invocation, or if the invocation was from another thread of execution, or if the function containing the invocation of the setjmp macro has terminated execution248) in the interim, or if the invocation of the setjmp macro was within the scope of an identifier with variably modified type and execution has left that scope in the interim, the behavior is undefined.
    248) 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.
    C 2011 Online Draft

    When you execute longjmp(jump_body, 1); in func, you invalidate jump_ret.

    longjmp isn't bidirectional - it unwinds the stack as though any of the function calls between the setjmp and longjmp never happened.