Try catch statements in C

前端 未结 13 1413
眼角桃花
眼角桃花 2020-11-27 10:06

I was thinking today about the try/catch blocks existent in another languages. Googled for a while this but with no result. From what I know, there is not such a thing as tr

相关标签:
13条回答
  • 2020-11-27 10:40

    While some of the other answers have covered the simple cases using setjmp and longjmp, in a real application there's two concerns that really matter.

    1. Nesting of try/catch blocks. Using a single global variable for your jmp_buf will make these not work.
    2. Threading. A single global variable for you jmp_buf will cause all kinds of pain in this situation.

    The solution to these is to maintain a thread-local stack of jmp_buf that get updated as you go. (I think this is what lua uses internally).

    So instead of this (from JaredPar's awesome answer)

    static jmp_buf s_jumpBuffer;
    
    void Example() { 
      if (setjmp(s_jumpBuffer)) {
        // The longjmp was executed and returned control here
        printf("Exception happened\n");
      } else {
        // Normal code execution starts here
        Test();
      }
    }
    
    void Test() {
      // Rough equivalent of `throw`
      longjump(s_jumpBuffer, 42);
    }
    

    You'd use something like:

    #define MAX_EXCEPTION_DEPTH 10;
    struct exception_state {
      jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
      int current_depth;
    };
    
    int try_point(struct exception_state * state) {
      if(current_depth==MAX_EXCEPTION_DEPTH) {
         abort();
      }
      int ok = setjmp(state->jumpBuffer[state->current_depth]);
      if(ok) {
        state->current_depth++;
      } else {
        //We've had an exception update the stack.
        state->current_depth--;
      }
      return ok;
    }
    
    void throw_exception(struct exception_state * state) {
      longjump(state->current_depth-1,1);
    }
    
    void catch_point(struct exception_state * state) {
        state->current_depth--;
    }
    
    void end_try_point(struct exception_state * state) {
        state->current_depth--;
    }
    
    __thread struct exception_state g_exception_state; 
    
    void Example() { 
      if (try_point(&g_exception_state)) {
        catch_point(&g_exception_state);
        printf("Exception happened\n");
      } else {
        // Normal code execution starts here
        Test();
        end_try_point(&g_exception_state);
      }
    }
    
    void Test() {
      // Rough equivalent of `throw`
      throw_exception(g_exception_state);
    }
    

    Again a more realistic version of this would include some way to store error information into the exception_state, better handling of MAX_EXCEPTION_DEPTH (maybe using realloc to grow the buffer, or something like that).

    DISCLAIMER: The above code was written without any testing whatsoever. It is purely so you get an idea of how to structure things. Different systems and different compilers will need to implement the thread local storage differently. The code probably contains both compile errors and logic errors - so while you're free to use it as you choose, TEST it before using it ;)

    0 讨论(0)
  • 2020-11-27 10:42

    Perhaps not a major language (unfortunately), but in APL, theres the ⎕EA operation (stand for Execute Alternate).

    Usage: 'Y' ⎕EA 'X' where X and Y are either code snippets supplied as strings or function names.

    If X runs into an error, Y (usually error-handling) will be executed instead.

    0 讨论(0)
  • 2020-11-27 10:43

    If you're using C with Win32, you can leverage its Structured Exception Handling (SEH) to simulate try/catch.

    If you're using C in platforms that don't support setjmp() and longjmp(), have a look at this Exception Handling of pjsip library, it does provide its own implementation

    0 讨论(0)
  • 2020-11-27 10:44

    You use goto in C for similar error handling situations.
    That is the closest equivalent of exceptions you can get in C.

    0 讨论(0)
  • 2020-11-27 10:47

    This can be done with setjmp/longjmp in C. P99 has a quite comfortable toolset for this that also is consistent with the new thread model of C11.

    0 讨论(0)
  • 2020-11-27 10:49

    In C, you can "simulate" exceptions along with automatic "object reclamation" through manual use of if + goto for explicit error handling.

    I often write C code like the following (boiled down to highlight error handling):

    #include <assert.h>
    
    typedef int errcode;
    
    errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
    {
        errcode ret = 0;
    
        if ( ( ret = foo_init( f ) ) )
            goto FAIL;
    
        if ( ( ret = goo_init( g ) ) )
            goto FAIL_F;
    
        if ( ( ret = poo_init( p ) ) )
            goto FAIL_G;
    
        if ( ( ret = loo_init( l ) ) )
            goto FAIL_P;
    
        assert( 0 == ret );
        goto END;
    
        /* error handling and return */
    
        /* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
    
    FAIL_P:
        poo_fini( p );
    
    FAIL_G:
        goo_fini( g );
    
    FAIL_F:
        foo_fini( f );
    
    FAIL:
        assert( 0 != ret );
    
    END:
        return ret;        
    }
    

    This is completely standard ANSI C, separates the error handling away from your mainline code, allows for (manual) stack unwinding of initialized objects much like C++ does, and it is completely obvious what is happening here. Because you are explicitly testing for failure at each point it does make it easier to insert specific logging or error handling at each place an error can occur.

    If you don't mind a little macro magic, then you can make this more concise while doing other things like logging errors with stack traces. For example:

    #include <assert.h>
    #include <stdio.h>
    #include <string.h>
    
    #define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
    
    typedef int errcode;
    
    errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
    {
        errcode ret = 0;
    
        TRY( ret = foo_init( f ), FAIL );
        TRY( ret = goo_init( g ), FAIL_F );
        TRY( ret = poo_init( p ), FAIL_G );
        TRY( ret = loo_init( l ), FAIL_P );
    
        assert( 0 == ret );
        goto END;
    
        /* error handling and return */
    
    FAIL_P:
        poo_fini( p );
    
    FAIL_G:
        goo_fini( g );
    
    FAIL_F:
        foo_fini( f );
    
    FAIL:
        assert( 0 != ret );
    
    END:
        return ret;        
    }
    

    Of course, this isn't as elegant as C++ exceptions + destructors. For example, nesting multiple error handling stacks within one function this way isn't very clean. Instead, you'd probably want to break those out into self contained sub functions that similarly handle errors, initialize + finalize explicitly like this.

    This also only works within a single function and won't keep jumping up the stack unless higher level callers implement similar explicit error handling logic, whereas a C++ exception will just keep jumping up the stack until it finds an appropriate handler. Nor does it allow you to throw an arbitrary type, but instead only an error code.

    Systematically coding this way (i.e. - with a single entry and single exit point) also makes it very easy to insert pre and post ("finally") logic that will execute no matter what. You just put your "finally" logic after the END label.

    0 讨论(0)
提交回复
热议问题