Try catch statements in C

前端 未结 13 1521
眼角桃花
眼角桃花 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: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 
    
    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 
    #include 
    #include 
    
    #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.

提交回复
热议问题