开发者

Exception Handling in C - What is the use of setjmp() returning 0?

开发者 https://www.devze.com 2022-12-10 15:01 出处:网络
I have a few questions relating to setjmp/longjmp usage - What is the use of setjmp(jmp___buf stackVariables) returning 0.It is a default, which we cannot influence.

I have a few questions relating to setjmp/longjmp usage -

  1. What is the use of setjmp(jmp___buf stackVariables) returning 0. It is a default, which we cannot influence.

  2. Is the only significance of setjmp(stackVariables) is to push the stack in stackVariables. And basically 0 tells us if the stack was pushed on stack_variables successfully.

  3. Their is one occasion when the value is non-zero (any non-zero) when you return from a longjmp. What is returning from a lomgjmp, when do you return from 开发者_C百科longjmp, when your exception is handled. This is setup is really confusing.

  4. Can some please relate it to try/throw and catch. And would be really great, if some good examples of setjmp/longjmp could be provided.

  5. Is longJmp like throw, and it is called just after the place where exception can be raised.

Thanks.


The C99 spec gives:

If the return is from a direct invocation, the setjmp macro returns the value zero. If the return is from a call to the longjmp function, the setjmp macro returns a nonzero value.

So the answer to 1 is that a zero indicates you have called setjmp the first time, and non-zero indicates it is returning from a longjmp.

  1. It pushes the current program state. After a longjmp, the state is restored, control returns to the point it was called, and the return value is non-zero.

  2. There are no exceptions in C. It's sort-of similar to fork returning different values depending whether you're in the original process, or a second process which has inherited the environment, if you're familiar with that.

  3. try/catch in C++ will call destructors on all automatic objects between the throw and the catch. setjmp/longjmp will not call destructors, as they don't exist in C. So you are on your own as far as calling free on anything you've malloced in the mean time.

With that proviso, this:

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

is roughly equivalent to

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

In the C case, you have to do explicit memory management (though normally you'd free it in the function which malloc'd it before calling longjmp as it makes life simpler)


setjmp is used to place a marker to where the call of longjump should return, it returns 0 if it is called directly, it returns 1 if it's called because a longjmp to that setjmp is invoked.

You have to think about setjmp like something that can be normally called and does not do anything (returning 0) in normal operation while returns 1 and it's indirectly called (and returns from there) when a long jump is called. I know what you mean about confusing because it's actually confusing..

This is the example given by wikipedia:

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

Are you able to understand it? When program is launched setjmp is executed in main and returns 0 (because it is directly called), so first is called, that calls second and then it arrives longjmp that switches context going back to where setjmp was used, but this time, since it goes back from a jump and it is indirectly called the function returns 1.

The useful thing of setjmp/longjmp approach is that you can handle error situations without caring to keep a flag between function calls (especially when you have many, think about a recursive procedure for typechecking in a compiler). If something goes wrong in typechecking deep in call stack normally you have to return a flag and keep returning it to warn the caller that typechecking failed. With longjmp you just go out and handle error without caring about passing flags back. The only problem is that this forces a context switch that doesn't care about standard deallocation of stack/heap memory so you should handle it by yourself.


The first part is almost simple: When you do a longjmp, you end up exactly after the setjmp. If the return value is 0, it means you just did the setjmp; if it is nonzero, you know you got there from a longjmp from elsewhere. That information is often useful in controlling what your code does after that.

setjmp/longjmp are the old ancestors of throw/catch. setjmp/longjmp are defined in C, whereas throw/catch is the more "modern" mechanism for doing error recovery in more object-oriented languages like C++.

calling longjmp says: "I think there's something wrong here, help, get me out of here - I'm too confused to clean up after myself and return through a bunch of function calls and if's; just get me right back to where the world was OK again, right after the last setjmp."

throw says pretty much the same thing, except it's more clearly and cleanly supported in syntax. Also, while longjmp can take you to practically ANY place in the program (wherever you did the setjmp), throw ends up directly upward in the call hierarchy of where the throw is.


Just to add to the answer (and remark) by Pete Kirkham: since C standard does not allow storing the return value of setjmp, perhaps Pete's example could be changed to use switch instead. It still demonstrates how to distinct between different return values but does not violate the standard.

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

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号