Is there a better way to do C style error handling?

前端 未结 10 1909
时光说笑
时光说笑 2021-02-20 06:43

I\'m trying to learn C by writing a simple parser / compiler. So far its been a very enlightening experience, however coming from a strong background in C# I\'m having some pro

相关标签:
10条回答
  • 2021-02-20 07:32

    The short answer is: let your functions return an error code that cannot possibly be a valid value - and always check the return value. For functions returning pointers, this is NULL. For functions returning a non-negative int, it's a negative value, commonly -1, and so on...

    If every possible return value is also a valid value, use call-by-reference:

    int my_atoi(const char *str, int *val)
    {
            // convert str to int
            // store the result in *val
            // return 0 on success, -1 (or any other value except 0) otherwise
    }
    


    Checking the return value of every function might seem tedious, but that's the way errors are handled in C. Consider the function nc_dial(). All it does is checking its arguments for validity and making a network connection by calling getaddrinfo(), socket(), setsockopt(), bind()/listen() or connect(), finally freeing unused resources and updating metadata. This could be done in approximately 15 lines. However, the function has nearly 100 lines due to error checking. But that's the way it is in C. Once you get used to it, you can easily mask the error checking in your head.

    Furthermore, there's nothing wrong with multiple if (Action() == 0) return -1;. To the contrary: it is usually a sign of a cautious programmer. It's good to be cautious.

    And as a final comment: don't use macros for anything but defining values if you can't justify their use while someone is pointing with a gun at your head. More specifically, never use control flow statements in macros: it confuses the shit out of the poor guy who has to maintain your code 5 years after you left the company. There's nothing wrong with if (foo) return -1;. It's simple, clean and obvious to the point that you can't do any better.

    Once you drop your tendency to hide control flow in macros, there's really no reason to feel like you're missing something.

    0 讨论(0)
  • 2021-02-20 07:34

    One technique for cleanup is to use an while loop that will never actually iterate. It gives you goto without using goto.

    #define NOT_ERROR(x) if ((x) < 0) break;
    #define NOT_NULL(x) if ((x) == NULL) break;
    
    // Initialise things that may need to be cleaned up here.
    char* somePtr = NULL;
    
    do
    {
        NOT_NULL(somePtr = malloc(1024));
        NOT_ERROR(something(somePtr));
        NOT_ERROR(somethingElse(somePtr));
        // etc
    
        // if you get here everything's ok.
        return somePtr;
    }
    while (0);
    
    // Something went wrong so clean-up.
    free(somePtr);
    return NULL;
    

    You lose a level of indentation though.

    Edit: I'd like to add that I've nothing against goto, it's just that for the use-case of the questioner he doesn't really need it. There are cases where using goto beats the pants off any other method, but this isn't one of them.

    0 讨论(0)
  • 2021-02-20 07:35

    You're probably not going to like to hear this, but the C way to do exceptions is via the goto statement. This is one of the reasons it is in the language.

    The other reason is that goto is the natural expression of the implementation of a state machine. What common programming task is best represented by a state machine? A lexical analyzer. Look at the output from lex sometime. Gotos.

    So it sounds to me like now is the time for you to get chummy with that parriah of language syntax elements, the goto.

    0 讨论(0)
  • 2021-02-20 07:41

    It never occurred to me to use goto or do { } while(0) for error handling in this way - its pretty neat, however after thinking about it I realised that in many cases I can do the same thing by splitting the function out into two:

    int Foo(void)
    {
        // Initialise things that may need to be cleaned up here.
        char* somePtr = malloc(1024);
        if (somePtr = NULL)
        {
            return NULL;
        }
    
        if (FooInner(somePtr) < 0)
        {
            // Something went wrong so clean-up.
            free(somePtr);
            return NULL;
        }
    
        return somePtr;
    }
    
    int FooInner(char* somePtr)
    {
        if (something(somePtr) < 0) return -1;
        if (somethingElse(somePtr) < 0) return -1;
        // etc
    
        // if you get here everything's ok.
        return 0;
    }
    

    This does now mean that you get an extra function, but my preference is for many short functions anyway.

    After Philips advice I've also decided to avoid using control flow macros as well - its clear enough what is going on as long as you put them on one line.

    At the very least Its reassuring to know that I'm not just missing something - everyone else has this problem too! :-)

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