【问题标题】:setjmp / longjmp does not jump where I think it shouldsetjmp / longjmp 没有跳到我认为应该跳的地方
【发布时间】:2021-12-12 14:02:05
【问题描述】:

我想了解 setjmp / longjmp 是如何工作的,所以我创建了一个示例程序,其中routineA 打印偶数,routineB 打印奇数,它们通过 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" );
}

我用 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

在第 12 行之前输出正常:This is not the first iteration - longjmp to routineB,但是,在此之后它应该转到routineB,并打印Got to this line via longjmp (in this program it must be from routineA) - continue,但它留在routineA,并显示Got to this line via longjmp (in this program it must be from routineB) - continue

我的代码有错误还是我对 setjmp / longjmp 有误解?

更新

根据 Eric Postpischil 的回答的第一部分修改了代码:我认为现在它适合最后一个要点,因为赋值语句是表达式语句。我收到了相同的结果(除了不同的指针值)。

好的,我将sjr = setjmp( *s_pEnvA );(以及routineB的对应部分)替换为

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

我认为这完全符合要点“选择或迭代语句的整个控制表达式”。

结果还是一样。

最终更新

Eric 和 Nate 是对的,这是未定义的行为。但是因为我想知道为什么this answer 中的代码有效,所以我逐渐将我的代码转换为那个代码,并观察它在什么时候中断。我最终得到了这个:

#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" );
}

如果你用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) 版本 6.3.0 20170516 (x86_64-linux-gnu))。

我认为这么小的变化(打印不同的字符串格式)会破坏它,清楚地表明 setjmp / longjmp 不能用于此目的。

【问题讨论】:

  • 不要打印带有%lx 的指针。将它们转换为void * 并使用%p 打印。
  • 我认为 Eric 有点埋没了。您的代码基本上不是longjmp 所做的。不是在函数之间随意跳转;它仅用于跳转调用堆栈,本质上是强制多层函数一次返回。如果堆栈深处的函数遇到只能更进一步处理的错误,您可能会使用它,例如通过中止计算并继续程序的主循环。如果您熟悉 C++ 或其他语言中的 throw/catch,请将 longjmp/setjmp 视为它的原始版本。
  • 带分号的赋值表达式是一个表达式语句,但其中的 setjmp 调用并不是 C 标准所要求的整个表达式语句。它只是语句中的一个子表达式。
  • @EricPostpischil:这是否意味着,我无法将 setjmp 的返回值保存到变量中?如果我不能使用它,为什么要向 longjmp 提供一个值?
  • @z32a7ul:您可以在switch 的控制表达式中使用setjmp,并选择带有case 标签的各个值。

标签: c setjmp


【解决方案1】:

首先,C 标准未定义您对 setjmp 的调用行为,因为它们违反了 C 2018 7.13.1.1 4 和 5 中的约束:

setjmp 宏的调用应仅出现在以下上下文之一中:

——选择或迭代语句的整个控制表达式;

——关系或相等运算符的一个操作数与另一个操作数为整数常量表达式,结果表达式是选择或迭代语句的整个控制表达式;

— 一元 ! 运算符的操作数,其结果表达式是选择或迭代语句的整个控制表达式;或

——一个表达式语句的整个表达式(可能转换为void)。

如果调用出现在任何其他上下文中,则行为未定义。

例如,在if( ( sjr = setjmp( *s_pEnvA ) ) == 0 ) 中,setjmp 调用不是整个控制表达式,它不是关系或相等运算符的操作数(&lt;&lt;=&gt;&gt;= , ==, or !=),它不是!的操作数,也不是一个表达式语句的整个表达式。

其次,longjmp 只能向上跳转调用堆栈,跳转到仍在执行的函数。一旦对函数的调用停止执行(当它返回时),您就不能跳回该调用。您的代码将上下文保存在routineB 中,然后跳转到routineA(结束routineB 的执行),然后尝试跳转到保存的上下文。但是 C 2018 7.13.2.1 2,关于longjmp 说:

...如果包含setjmp 宏调用的函数在此期间终止了执行,...行为未定义。

【讨论】:

  • 我不明白为什么第二部分有问题。在输出第 12 行,两个函数(routineA 和routineB)仍在运行,for 循环有多次迭代要做,对吧?
  • @z32a7ul:当routineBlongjmp跳转到routineA时,结束routineB的执行。
  • 关于 SO 还有一个问题,正如我所见,它的作用与我的代码类似:它在两个函数之间跳转(尽管没有循环)。有什么区别?还是那一个也错了?
  • @z32a7ul:哪个问题?
  • @z32a7ul:C 标准说明了 C 标准所说的内容。另一个问题包含某人的代码。那个人和他们的代码不是 C 标准。由于他们的 C 实现的一些特殊特征以及他们如何使用它,这些代码可能对他们“有效”。除了我引用的标准文本之外,从该文本引用的注释 253 说:“例如,通过执行 return 语句或因为另一个 longjmp 调用导致转移到 setjmp 调用嵌套调用集中较早的一个函数。”所以这个案例是专门介绍的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
  • 1970-01-01
  • 1970-01-01
  • 2020-08-21
  • 2011-12-21
相关资源
最近更新 更多