C语言中的异常处理 - setjmp()返回0有什么用?

7

我有几个与setjmp/longjmp用法相关的问题:

  1. setjmp(jmp___buf stackVariables)返回0的用途是什么?这是一个默认值,我们无法控制。

  2. setjmp(stackVariables)的唯一意义是将堆栈推入stackVariables中。基本上,0告诉我们堆栈是否成功推入stack_variables。

  3. 只有在从longjmp返回时,值为非零(任何非零)的情况。从longjmp返回意味着什么,何时从longjmp返回,当处理异常时从longjmp返回。这个设置真的很令人困惑。

  4. 请有人将其与try/throw和catch联系起来。如果提供一些setjmp/longjmp的好例子,那就太好了。

  5. longJmp像throw一样,在可能引发异常的地方之后调用。

谢谢。

4个回答

12

C99规范给出:

如果是从直接调用返回,setjmp宏返回值为零。如果是从调用longjmp函数返回,则setjmp宏返回非零值。

所以问题1的答案是,0表示您第一次调用了setjmp,而非零表示它正在从longjmp返回。

  1. 它推送当前程序状态。在longjmp之后,状态被恢复,控制返回到调用点,并且返回值为非零。

  2. C中没有异常。如果您熟悉此类情况,这有点类似于fork根据您是在原始进程中还是在继承环境的第二个进程中返回不同的值。

  3. C ++中的try / catch将调用在throw和catch之间所有自动对象的析构函数。setjmp / longjmp不会调用析构函数,因为它们在C中不存在。因此,在对您已经在此期间malloc的任何内容调用free时,你需要自己注意。

具有这种说明的内容:

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

void foo ( char** data ) ;
void handle ( char* data ) ;
jmp_buf env;

int main ()
{
    char* data = 0;

    int res = setjmp ( env ); 
    // stored for demo purposes. 
    // in portable code do not store 
    // the result, but test it directly.

    printf ( "setjmp returned %d\n", res );

    if ( res == 0 )
        foo ( &data );
    else
        handle ( data );

    return 0;
}


void foo ( char** data )
{
    *data = malloc ( 32 );

    printf ( "in foo\n" );

    strcpy ( *data, "Hello World" );

    printf ( "data = %s\n", *data );

    longjmp ( env, 42 );
}

void handle ( char* data )
{
    printf ( "in handler\n" );

    if ( data ) {
        free ( data );
        printf ( "data freed\n" );
    }
}

大致等同于

#include <iostream>

void foo ( ) ;
void handle ( ) ;

int main ()
{
    try {
        foo ();
    } catch (int x) {
        std::cout << "caught " << x << "\n";
        handle ();
    }

    return 0;
}

void foo ( )
{
    printf ( "in foo\n" );

    std::string data = "Hello World";

    std::cout << "data = " << data << "\n";

    throw 42;
}

void handle ( )
{
    std::cout << "in handler\n";
}

在C语言中,你需要进行显式内存管理(尽管通常情况下,你应该在调用longjmp之前的malloc函数所在的函数中释放它,这样会使生活更加简单)


longJmp像throw一样,它在异常可能被抛出的位置后面被调用。 还有为什么您要使用数字42,您可以使用1完成相同的工作,或者是42任何非零正整数。 - Vivek Sharma
2
关键点在于setjmp()函数可能会返回一次,也可能会返回多次。它将从“直接调用”中返回一次(这将返回0);任何后续的返回都是longjmp()的结果,并将返回一个非零值。 - Jonathan Leffler
3
无法将setjmp()的结果可移植地存储到变量中。C标准规定:“setjmp宏的调用只能出现在以下情况之一中:选择或迭代语句的整个控制表达式;与整数常量表达式为另一个操作数的关系或相等运算符的一个操作数,其结果表达式为选择或迭代语句的整个控制表达式;一元!运算符的操作数,其结果表达式为[...];或者是表达式语句的整个表达式[...]。” - Jonathan Leffler
@Jonathan Leffler 您说得对,但如果不这样做,在演示代码中很难展示返回值。 - Pete Kirkham

7

setjmp用于在调用longjump时标记返回位置,如果直接调用它,则返回0,如果因为调用了longjmp到该setjmp,则返回1。

你可以把setjmp看作是一个普通的函数,在正常操作中不执行任何操作(返回0),但当调用long jump时,它会被间接调用并返回(从那里返回)1。我知道你对此感到困惑,因为它确实很令人困惑。

这是维基百科给出的示例:

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

static jmp_buf buf;

void second(void)
{
    printf("second\n");         // prints
    longjmp(buf,1);             // jumps back to where setjmp was called - making setjmp now return 1
}

void first(void)
{
    second();
    printf("first\n");          // does not print
}

int main()
{   
    if ( ! setjmp(buf) )
    {
        first();                // when executed, setjmp returns 0
    } 
    else
    {                    // when longjmp jumps back, setjmp returns 1
        printf("main");         // prints
    }

    return 0;
}

你能理解这段话吗?当程序启动时,setjmp在主函数中被执行并返回0(因为它是直接调用的),所以会调用first,然后调用second,最后到达longjmp,它会切换上下文回到使用setjmp的地方。但是这一次,由于它是从跳转中返回的,并且是间接调用的,因此该函数返回1。

setjmp/longjmp方法的有用之处在于可以处理错误情况,而无需在函数调用之间保留标志(特别是当您有许多调用时,例如编译器中的递归类型检查过程)。如果在调用堆栈深处的类型检查中出现问题,通常情况下必须返回一个标志并不断返回以警告调用者类型检查失败。使用longjmp,您只需退出并处理错误,无需关心传递标志。唯一的问题是这会强制进行上下文切换,不关心栈/堆内存的标准释放,因此您应该自己处理。


1
你指出的问题相当难以处理,因为你必须知道在 longjmp 之前分配了什么,并且如何释放它。此外,如果我没记错的话,发出 setjmp 的函数中的任何非易失性局部变量都可能会被破坏。 - David G

6
第一部分几乎很简单:当您执行longjmp时,您会在setjmp之后恰好结束。如果返回值为0,则表示刚刚执行了setjmp;如果它是非零的,则知道您从其他地方的longjmp到达那里。这些信息通常对控制代码在此之后要做什么非常有用。
setjmp/longjmp是throw/catch的古老祖先。setjmp/longjmp在C中定义,而throw/catch是更“现代”的机制,用于在更面向对象的语言(如C ++)中进行错误恢复。
调用longjmp表示:“我认为这里出了问题,请帮助我,让我离开 - 我太困惑了,无法清理自己并通过一堆函数调用和if返回;只需将我直接返回到上次setjmp之后的世界还是好的位置。”
throw说的话基本相同,只是语法更加明确和清晰支持。此外,尽管longjmp可以将您带到程序中的几乎任何地方(无论您在哪里执行setjmp),但throw最终会直接向上在抛出位置的调用层次结构中结束。

1

补充一下Pete Kirkham的回答和备注:由于C标准不允许存储setjmp的返回值,因此Pete的示例可能需要改为使用switch。它仍然演示了如何区分不同的返回值,但不违反标准。

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

void foo(char** data) ;
void handle(char* data) ;
jmp_buf env;

int main(void)
{
  char* data = 0;
  switch(setjmp(env))
  {
    case 0:
    {
      printf("setjmp returned 0\n");
      foo(&data);
      break;
    }
    case 42:
    {
      printf("setjmp returned 42\n");
      handle ( data );
      break;
    }
    default:
    {
      printf("setjmp returned something else?\n");
    }
  }
  return 0;
}

void foo(char** data)
{
  *data = malloc(32);
  printf("in foo\n");
  strcpy(*data, "Hello World");
  printf("data = %s\n", *data);
  longjmp(env, 42);
}

void handle(char* data)
{
  printf("in handler\n");
  if(data)
  {
    free(data);
    printf("data freed\n");
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接