Any good idioms for error handling in straight C programs?

后端 未结 12 1541
梦如初夏
梦如初夏 2020-12-23 14:19

Getting back in to some C work.

Many of my functions look like this:

int err = do_something(arg1, arg2, arg3, &result);

With th

12条回答
  •  情深已故
    2020-12-23 14:55

    Others have suggested good ideas. Here're the idioms I've seen

    int err;
    ...
    err = foo(...);
    if (err)
        return err;
    ...
    

    You could macro this out to something like

    #define dERR int err=0
    #define CALL err = 
    #define CHECK do { if (err) return err } while(0)
    ...
    void my_func(void) {
       dERR;
       ...
       CALL foo(...);
       CHECK;
    

    or, if you're feeling really motivated, fiddle with CALL and CHECK so they can be used like

    CALL foo(...) CHECK;
    

    or

    CALL( foo(...) );
    

    --

    Often, functions which need to do cleanup on exit (e.g. free memory) are written like this:

    int do_something_complicated(...) {
        ...
    
        err = first_thing();
        if (err)
           goto err_out;
    
        buffer = malloc(...);
        if (buffer == NULL)
            goto err_out
    
        err = another_complicated(...);
        if (err)
            goto err_out_free;
    
        ...
    
       err_out_free:
        free(buffer);
       err_out:
        return err; /* err might be zero */
    }
    

    You could use that pattern, or try to simplify it with macros.

    --

    Finally, if you're feeling /really/ motivated, you can use setjmp/longjmp.

    int main(int argc, char *argv[]) {
        jmp_buf on_error;
        int err;
        if (err = setjmp(on_error)) {
            /* error occurred, error code in err */
            return 1;
        } else {
            actual_code(..., on_error);
            return 0;
        }
    }
    void actual_code(..., jmp_buf on_error) {
        ...
        if (err)
            longjmp(on_error, err);
    }
    

    Essentially, a declaration of a new jmp_buf and a setjmp function as setting up a try block. The case where setjmp returns non-zero is your catch, and calling longjmp is your throw. I wrote this with passing the jmp_buf around in case you want nested handlers (e.g. if you need to free stuff before signaling an error); if you don't need that, feel free to declare err and the jmp_buf as globals.

    Alternately, you could use macros to simply the argument passing around. I'd suggest the way Perl's implementation does it:

    #define pERR jmp_buf _err_handler
    #define aERR _err_handler
    #define HANDLE_ERRORS do { jmp_buf _err_handler; int err = setjmp(_err_handler);
    #define END_HANDLE while(0)
    #define TRY if (! err)
    #define CATCH else
    #define THROW(e) longjmp(_err_handler, e)
    
    void always_fails(pERR, int other_arg) {
        THROW(42);
    }
    void does_some_stuff(pERR) {
        normal_call(aERR);
        HANDLE_ERRORS
          TRY {
            always_fails(aERR, 23);
          } CATCH {
            /* err is 42 */
          }
        END_HANDLE;
    }
    int main(int argc, char *argv[]) {
        HANDLE_ERRORS
          TRY {
            does_some_stuff(aERR);
            return 0;
          } CATCH {
            return err;
          }
        DONE_ERRORS;
    }
    

    --

    Phew. I'm done. (Crazy examples untested. Some details might be off.)

提交回复
热议问题