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^6lx &envB=0x^6lx ] -- %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^6lx &envB=0x^6lx ]\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^6lx, pEnvB=0x^6lx, s_pEnvA=0x^6lx, s_pEnvB=0x^6lx, 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^6lx, pEnvB=0x^6lx, s_pEnvA=0x^6lx, s_pEnvB=0x^6lx, 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.
CodePudding user response:
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.