我想了解 setjmp / longjmp 的工作原理,因此我创建了一个示例程序,其中例程 A 打印偶数,例程 B 打印奇数,并且它们使用 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" );
}
Run Code Online (Sandbox Code Playgroud)
我用 gcc 编译它并收到以下输出(插入行号以供以后参考):
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
Run Code Online (Sandbox Code Playgroud)
输出在第 12 行之前都是正常的:This is not the first iteration - longjmp to routineB但是,在此之后它应该转到例程 B 并打印,Got to this line via longjmp (in this program it must be from routineA) - continue但它保留在例程 A 中,并显示Got to this line via longjmp (in this program it must be from routineB) - continue。
我的代码中是否有错误或者我是否误解了 setjmp / longjmp 的某些内容?
更新
根据 Eric Postpischil 的答案的第一部分修改了代码:我认为现在它适合最后一个要点,因为赋值语句是表达式语句。我收到了相同的结果(除了不同的指针值)。
好吧,我将sjr = setjmp( *s_pEnvA );(以及例程B的相应部分)替换为
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;
}
Run Code Online (Sandbox Code Playgroud)
我认为这完全符合“选择或迭代语句的整个控制表达式”的要点。
结果还是一样。
最终更新
埃里克和内特是对的,这是未定义的行为。但由于我想知道为什么这个答案中的代码有效,所以我逐渐将我的代码转换为那个代码,并观察它在哪一点崩溃。我最终得到了这个:
#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" );
}
Run Code Online (Sandbox Code Playgroud)
如果您使用 编译上面的内容gcc -DSORTOFWORKS -o ./jump_sortofworks ./jump.c,那么它会打印标有注释的行/* SORTOFWORKS: printed, DOESNOTWORK: missing */,但如果您使用 编译它gcc -DDOESNOTWORK -o ./jump_doesnotwork ./jump.c,则不会打印(编译器:GNU C11 (Debian 6.3.0-18+deb9u1) version 6.3.0 20170516 (x86_64 -linux-gnu))。
我认为这样一个小变化(打印的不同字符串格式)打破了它,清楚地表明 setjmp / longjmp 不能用于此目的。
首先,您的调用行为setjmp不是由 C 标准定义的,因为它们违反了 C 2018 7.13.1.1 4 和 5 中的约束:
\n\n宏的调用
\nsetjmp只能出现在以下上下文之一中:\xe2\x80\x94 选择或迭代语句的整个控制表达式;
\n\xe2\x80\x94 关系运算符或相等运算符的一个操作数,另一个操作数是整型常量表达式,结果表达式是选择或迭代语句的整个控制表达式;
\n\xe2\x80\x94 一元运算符的操作数
\n!,其结果表达式是选择或迭代语句的整个控制表达式;或者\xe2\x80\x94 表达式语句的整个表达式(可能转换为
\nvoid)。如果调用出现在任何其他上下文中,则行为未定义。
\n
例如,在 中if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ),setjmp调用不是整个控制表达式,它不是关系运算符或相等运算符(<、<=、>、>=、==或!=)的操作数,它不是 的操作数!,也不是表达式语句。
其次,longjmp只能向上跳转调用堆栈,跳转到仍在执行的函数。一旦对函数的调用停止执行(当它返回时),您就无法跳回该调用。您的代码将上下文保存在 中routineB,然后跳转到routineA(结束执行routineB),然后尝试跳转到保存的上下文。但是 C 2018 7.13.2.1 2,关于longjmp说:
\n\n\xe2\x80\xa6 如果包含宏调用的函数
\nsetjmp在此期间终止执行,\xe2\x80\xa6 行为未定义。