0

I would like to understand how setjmp / longjmp works, so I created an example program, where routineA prints even, routineB prints odd numbers and they jump to each other with longjmp:

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

#define COUNTER_BEGIN 0
#define COUNTER_END   6

void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB );
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB );

int main() {
    const char message[] = "main      [ &envA=0x^%016lx &envB=0x^%016lx ]  -- %s\n";
    jmp_buf envA;
    jmp_buf envB;

    fprintf( stdout, message, &envA, &envB,
             "Started; Before calling routineA" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "After routineA returned; Exiting" );

    return 0;
}

/* Prints even numbers from COUNTER_BEGIN to COUNTER_END */
void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* message = "routineA: [ i=%d, sjr=%d ]  -- %s\n";
    static int i;
    static int sjr;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;

    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, i, sjr, "After saving statics; Before starting the for loop" );
    for( i = COUNTER_BEGIN; i < COUNTER_END; i++ ) {
        if( i % 2 == COUNTER_BEGIN + 0 ) {
            fprintf( stdout, message, i, sjr, "Inside for and if; Before setjmp" );
            sjr = setjmp( *s_pEnvA );           /* Added */
            if( sjr == 0 ) {                    /* Added */
            /* if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ) { */
                fprintf( stdout, message, i, sjr, "Got to this line directly - go to routineB somehow" );
                if( i == 0 ) {
                    fprintf( stdout, message, i, sjr, "This is the first iteration - call routineB as function" );
                    routineB( s_pEnvA, s_pEnvB );
                } else {
                    fprintf( stdout, message, i, sjr, "This is not the first iteration - longjmp to routineB" );
                    longjmp( *s_pEnvB, 12 );
                }
            } else {
                fprintf( stdout, message, i, sjr, "Got to this line via longjmp (in this program it must be from routineB) - continue" );
                ; /* Just continue */
            }
        }
    }
    fprintf( stdout, message, i, sjr, "After the for loop, Before leaving the function" );
}

/* Prints odd numbers from COUNTER_BEGIN to COUNTER_END */
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* message = "routineB: [ i=%d, sjr=%d ]  -- %s\n";
    static int i;
    static int sjr;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;

    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, i, sjr, "After saving statics; Before starting the for loop" );
    for( i = COUNTER_BEGIN; i < COUNTER_END; i++ ) {
        if( i % 2 == 1 ) {
            fprintf( stdout, message, i, sjr, "Inside for and if; Before setjmp" );
            sjr = setjmp( *s_pEnvB );           /* Added */
            if( sjr == 0 ) {                    /* Added */
            /* if( ( sjr = setjmp( *s_pEnvB ) ) == 0 ) { */
                fprintf( stdout, message, i, sjr, "Got to this line directly - longjmp to routineA" );
                longjmp( *s_pEnvA, 21 );
            } else {
                fprintf( stdout, message, i, sjr, "Got to this line via longjmp (in this program it must be from routineA) - continue" );
                ; /* Just continue */
            }
        }
    }
    fprintf( stdout, message, i, sjr, "After the for loop, Before leaving the function" );
}

I compiled it with gcc and received the following output (line numbers inserted for later reference):

01 main      [ &envA=0x^00007ffce81b0280 &envB=0x^00007ffce81b01b0 ]  -- Started; Before calling routineA
02 routineA: [ i=0, sjr=0 ]  -- After saving statics; Before starting the for loop
03 routineA: [ i=0, sjr=0 ]  -- Inside for and if; Before setjmp
04 routineA: [ i=0, sjr=0 ]  -- Got to this line directly - go to routineB somehow
05 routineA: [ i=0, sjr=0 ]  -- This is the first iteration - call routineB as function
06 routineB: [ i=0, sjr=0 ]  -- After saving statics; Before starting the for loop
07 routineB: [ i=1, sjr=0 ]  -- Inside for and if; Before setjmp
08 routineB: [ i=1, sjr=0 ]  -- Got to this line directly - longjmp to routineA
09 routineA: [ i=0, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
10 routineA: [ i=2, sjr=21 ]  -- Inside for and if; Before setjmp
11 routineA: [ i=2, sjr=0 ]  -- Got to this line directly - go to routineB somehow
12 routineA: [ i=2, sjr=0 ]  -- This is not the first iteration - longjmp to routineB
13 routineA: [ i=2, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
14 routineA: [ i=4, sjr=21 ]  -- Inside for and if; Before setjmp
15 routineA: [ i=4, sjr=0 ]  -- Got to this line directly - go to routineB somehow
16 routineA: [ i=4, sjr=0 ]  -- This is not the first iteration - longjmp to routineB
17 routineA: [ i=4, sjr=21 ]  -- Got to this line via longjmp (in this program it must be from routineB) - continue
18 routineA: [ i=6, sjr=21 ]  -- After the for loop, Before leaving the function
19 main      [ &envA=0x^00007ffce81b0280 &envB=0x^00007ffce81b01b0 ]  -- After routineA returned; Exiting

The output is OK until line 12: This is not the first iteration - longjmp to routineB, however, after this it should go to routineB, and print Got to this line via longjmp (in this program it must be from routineA) - continue but it stays in routineA, and says Got to this line via longjmp (in this program it must be from routineB) - continue.

Is there an error in my code or do I misunderstand something about setjmp / longjmp?

Update

Revised the code according to Eric Postpischil's answer's first part: I think now it suits the last bullet point, as an assignment statement is an expression statement. I received the same result (except for different pointer values).

Alright, I replaced sjr = setjmp( *s_pEnvA ); (and the corresponding part of routineB) with

sjr = -1;
switch( setjmp( *s_pEnvA ) ) {
case  0: sjr =  0; break;
case  1: sjr =  1; break;
case 12: sjr = 12; break;
case 21: sjr = 21; break;
default: sjr = -2; break;
}

I think this is completely in line with the bullet point "the entire controlling expression of a selection or iteration statement".

The result is still the same.

Final update

Eric and Nate are right, this is undefined behavior. But since I was wondering why the code in this answer works, I transformed my code gradually to that one, and watched at which point it breaks. I ended up with this:

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

#if defined( SORTOFWORKS )
#define CHECKPOINTFORMAT "routineX:\n  -- %s\n\n"
#elif defined( DOESNOTWORK )
#define CHECKPOINTFORMAT message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr
#else
#error Version (SORTOFWORKS or DOESNOTWORK) is not defined
#endif

#define STRINGIFY( x ) STRINGIFY_IMPLEMENTATION( x )
#define                STRINGIFY_IMPLEMENTATION( x ) #x

#define COUNTER_BEGIN 0
#define COUNTER_END   6

void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB );
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB );

int main() {
    const char message[] = "main      [ &envA=0x^%016lx &envB=0x^%016lx ]\n  -- %s\n\n";
    jmp_buf envA;
    jmp_buf envB;

    fprintf( stdout, message, &envA, &envB,
             "Started; Before calling routineA the first time" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "Before calling routineA the second time" );
    routineA( &envA, &envB );
    fprintf( stdout, message, &envA, &envB,
             "Exiting" );

    return 0;
}

/* Prints even numbers from COUNTER_BEGIN to COUNTER_END */
void routineA( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* const message = "routineA: [ pEnvA=0x^%016lx, pEnvB=0x^%016lx, s_pEnvA=0x^%016lx, s_pEnvB=0x^%016lx, i=%d, sjr=%d ]\n"
                                "  -- %s\n\n";
    static int i;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;
    static int sjr;

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Called; Before saving statics" );
    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After saving statics; Before starting the counting" );

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A0 at line " STRINGIFY( __LINE__ ) );

    i = COUNTER_BEGIN;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is the first iteration - call routineB as function" );
        routineB( s_pEnvA, s_pEnvB );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A1 at line " STRINGIFY( __LINE__ ) );

    i += 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is not the first iteration - longjmp to routineB" );
        longjmp( *s_pEnvB, 12 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A2 at line " STRINGIFY( __LINE__ ) );

    i += 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvA ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - This is not the first iteration - longjmp to routineB" );
        longjmp( *s_pEnvB, 12 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineB) - continue" );
        ; /* Just continue */
    }

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint A3 at line " STRINGIFY( __LINE__ ) );

    /* Should be able to do this but it causes segfault: longjmp( *s_pEnvB, 12 ); */

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After the counting, Before leaving the function" );
}

/* Prints odd numbers from COUNTER_BEGIN to COUNTER_END */
void routineB( jmp_buf* pEnvA, jmp_buf* pEnvB ) {
    const char* const message = "routineB: [ pEnvA=0x^%016lx, pEnvB=0x^%016lx, s_pEnvA=0x^%016lx, s_pEnvB=0x^%016lx, i=%d, sjr=%d ]\n"
                                "  -- %s\n\n";
    static int i;
    static jmp_buf *s_pEnvA;
    static jmp_buf *s_pEnvB;
    static int sjr;

    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Called; Before saving statics" );
    s_pEnvA = pEnvA;
    s_pEnvB = pEnvB;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After saving statics; Before starting the for loop" );

    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B0 at line " STRINGIFY( __LINE__ ) );

    i = 1 + COUNTER_BEGIN;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B1 at line " STRINGIFY( __LINE__ ) ); /* SORTOFWORKS: printed, DOESNOTWORK: missing */

    i += 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, CHECKPOINTFORMAT,
             "Checkpoint B2 at line " STRINGIFY( __LINE__ ) ); /* SORTOFWORKS: printed, DOESNOTWORK: missing */

    i += 2;
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "Part of counting; Before setjmp" );
    sjr = -1;
    switch( setjmp( *s_pEnvB ) ) {
    case  0: sjr =  0; break;
    case  1: sjr =  1; break;
    case 12: sjr = 12; break;
    case 21: sjr = 21; break;
    default: sjr = -2; break;
    }
    if( sjr == 0 ) {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line directly - longjmp to routineA" );
        longjmp( *s_pEnvA, 21 );
    } else {
        fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
                 "Got to this line via longjmp (in this program it must be from routineA) - continue" );
        ; /* Just continue */
    }
    
    fprintf( stdout, message, pEnvA, pEnvB, s_pEnvA, s_pEnvB, i, sjr,
             "After the for loop, Before leaving the function" );
}

If you compile the above with gcc -DSORTOFWORKS -o ./jump_sortofworks ./jump.c, then it prints the lines marked with the comment /* SORTOFWORKS: printed, DOESNOTWORK: missing */ but if you compile it with gcc -DDOESNOTWORK -o ./jump_doesnotwork ./jump.c, then it doesn't (Compiler: GNU C11 (Debian 6.3.0-18+deb9u1) version 6.3.0 20170516 (x86_64-linux-gnu)).

I think the fact that such a small change (different string format to print) breaks it, clearly demonstrates that setjmp / longjmp cannot be used for this purpose.

z32a7ul
  • 3,695
  • 3
  • 21
  • 45
  • Do not print pointers with `%lx`. Convert them to `void *` and print them with `%p`. – Eric Postpischil Dec 12 '21 at 14:26
  • 1
    I think Eric kind of buried the lede. Your code is fundamentally not what `longjmp` does. It's not for arbitrarily jumping around between functions; it is for jumping up the call stack only, essentially forcing several layers of functions to return at once. You might use it if a function far down the stack encounters an error that can only be handled much further up, e.g. by aborting a computation and continuing the program's main loop. If you're familiar with `throw/catch` in C++ or other languages, think of `longjmp/setjmp` as a primitive version of that. – Nate Eldredge Dec 12 '21 at 17:12
  • An assignment expression with a semicolon is an expression statement, but the `setjmp` invocation in it is not the entire expression statement as the C standard calls for. It is just a subexpression within the statement. – Eric Postpischil Dec 12 '21 at 17:26
  • @EricPostpischil: Does this mean, I cannot save setjmp's return value to a variable? Why should I supply a value to longjmp if I can't use it? – z32a7ul Dec 12 '21 at 17:48
  • @z32a7ul: You can use a `setjmp` in the controlling expression of a `switch` and pick out the individual values with `case` labels. – Eric Postpischil Dec 12 '21 at 18:23
  • That's what I just did. – z32a7ul Dec 12 '21 at 18:24

1 Answers1

5

First, the behavior of your calls to setjmp is not defined by the C standard because they violate the constraints in C 2018 7.13.1.1 4 and 5:

An invocation of the setjmp macro shall appear only in one of the following contexts:

— the entire controlling expression of a selection or iteration statement;

— one operand of a relational or equality operator with the other operand an integer constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement;

— the operand of a unary ! operator with the resulting expression being the entire controlling expression of a selection or iteration statement; or

— the entire expression of an expression statement (possibly cast to void).

If the invocation appears in any other context, the behavior is undefined.

For example, in if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ), the setjmp invocation is not the entire controlling expression, it is not an operand of a relational or equality operator (<, <=, >, >=, ==, or !=), it is not the operand of !, and it is not the entire expression of an expression statement.

Second, longjmp can only jump up the call stack, to functions that are still executing. Once a call to a function stops executing (as when it returns), you cannot jump back into that call. Your code saves the context in routineB, then jumps to routineA (which ends execution of routineB), then attempts to jump to the saved context. But C 2018 7.13.2.1 2, about longjmp says:

… if the function containing the invocation of the setjmp macro has terminated execution in the interim,… the behavior is undefined.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I don't see why the second part is a problem. At output line 12 both functions (routineA and routineB) are still running, and the for loop has multiple iterations to do, right? – z32a7ul Dec 12 '21 at 17:08
  • @z32a7ul: When `routineB` jumps to `routineA` with `longjmp`, it ends the execution of `routineB`. – Eric Postpischil Dec 12 '21 at 17:22
  • There is another question on SO, and as I see it does something similar to my code: it jumps between two functions (although without loops). What makes the difference? Or is that one wrong, as well? – z32a7ul Dec 12 '21 at 17:38
  • 1
    @ z32a7ul: Which question? – Eric Postpischil Dec 12 '21 at 17:40
  • Sorry, I pressed enter too early. Here is the link: https://stackoverflow.com/questions/14685406/practical-usage-of-setjmp-and-longjmp-in-c/14685524 – z32a7ul Dec 12 '21 at 17:45
  • 1
    @z32a7ul: The C standard says what the C standard says. That other question contains code by some person. That person and their code are not the C standard. The code might have “worked” for them due to some particular characteristics of their C implementation and how they used it. In addition to the text of the standard I quoted, note 253, referred to from that text, says “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 this case is specifically covered. – Eric Postpischil Dec 12 '21 at 18:22