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

前端 未结 10 1704
时光说笑
时光说笑 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:14

    Use setjmp.

    http://en.wikipedia.org/wiki/Setjmp.h

    http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.html

    http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

    #include <setjmp.h>
    #include <stdio.h>
    
    jmp_buf x;
    
    void f()
    {
        longjmp(x,5); // throw 5;
    }
    
    int main()
    {
        // output of this program is 5.
    
        int i = 0;
    
        if ( (i = setjmp(x)) == 0 )// try{
        {
            f();
        } // } --> end of try{
        else // catch(i){
        {
            switch( i )
            {
            case  1:
            case  2:
            default: fprintf( stdout, "error code = %d\n", i); break;
            }
        } // } --> end of catch(i){
        return 0;
    }
    

    #include <stdio.h>
    #include <setjmp.h>
    
    #define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
    #define CATCH } else {
    #define ETRY } }while(0)
    #define THROW longjmp(ex_buf__, 1)
    
    int
    main(int argc, char** argv)
    {
       TRY
       {
          printf("In Try Statement\n");
          THROW;
          printf("I do not appear\n");
       }
       CATCH
       {
          printf("Got Exception!\n");
       }
       ETRY;
    
       return 0;
    }
    
    0 讨论(0)
  • 2021-02-20 07:17

    A goto statement is the easiest and potentially cleanest way to implement exception style processing. Using a macro makes it easier to read if you include the comparison logic inside the macro args. If you organize the routines to perform normal (i.e. non-error) work and only use the goto on exceptions, it is fairly clean for reading. For example:

    /* Exception macro */
    #define TRY_EXIT(Cmd)   { if (!(Cmd)) {goto EXIT;} }
    
    /* My memory allocator */
    char * MyAlloc(int bytes)
    {
        char * pMem = NULL;
    
        /* Must have a size */
        TRY_EXIT( bytes > 0 );
    
        /* Allocation must succeed */
        pMem = (char *)malloc(bytes);
        TRY_EXIT( pMem != NULL );
    
        /* Initialize memory */
        TRY_EXIT( initializeMem(pMem, bytes) != -1 );
    
        /* Success */
        return (pMem);
    
    EXIT:
    
        /* Exception: Cleanup and fail */
        if (pMem != NULL)
            free(pMem);
    
        return (NULL);
    }
    
    0 讨论(0)
  • 2021-02-20 07:19

    It's a bigger problem when you have to repeat the same finalizing code before each return from an error. In such cases it is widely accepted to use goto:

    int func ()
    {
      if (a() < 0) {
        goto failure_a;
      }
    
      if (b() < 0) {
        goto failure_b;
      }
    
      if (c() < 0) {
        goto failure_c;
      }
    
      return SUCCESS;
    
      failure_c:
      undo_b();
    
      failure_b:
      undo_a();
    
      failure_a:
      return FAILURE;
    }
    

    You can even create your own macros around this to save you some typing, something like this (I haven't tested this though):

    #define CALL(funcname, ...) \
      if (funcname(__VA_ARGS__) < 0) { \ 
        goto failure_ ## funcname; \
      }
    

    Overall, it is a much cleaner and less redundant approach than the trivial handling:

    int func ()
    {
      if (a() < 0) {
        return FAILURE;
      }
    
      if (b() < 0) {
        undo_a();
        return FAILURE;
      }
    
      if (c() < 0) {
        undo_b();
        undo_a();
        return FAILURE;
      }
    
      return SUCCESS;
    }
    

    As an additional hint, I often use chaining to reduce the number of if's in my code:

    if (a() < 0 || b() < 0 || c() < 0) {
      return FAILURE;
    }
    

    Since || is a short-circuit operator, the above would substitute three separate if's. Consider using chaining in a return statement as well:

    return (a() < 0 || b() < 0 || c() < 0) ? FAILURE : SUCCESS;
    
    0 讨论(0)
  • 2021-02-20 07:19

    What about this?

    int NS_Expression(void)
    {
        int ok = 1;
        ok = ok && NS_Term();
        ok = ok && Emit("MOVE D0, D1\n");
        ok = ok && NS_AddSub();
        return ok
    }
    
    0 讨论(0)
  • 2021-02-20 07:28

    THis must be thought on at least two levels: how your functions interact, and what you do when it breaks.

    Most large C frameworks I see always return a status and "return" values by reference (this is the case of the WinAPI and of many C Mac OS APIs). You want to return a bool?

    StatusCode FooBar(int a, int b, int c, bool* output);
    

    You want to return a pointer?

    StatusCode FooBar(int a, int b, int c, char** output);
    

    Well, you get the idea.

    On the calling function's side, the pattern I see the most often is to use a goto statement that points to a cleanup label:

        if (statusCode < 0) goto error;
    
        /* snip */
        return everythingWentWell;
    
    error:
        cleanupResources();
        return somethingWentWrong;
    
    0 讨论(0)
  • 2021-02-20 07:29

    Besides goto, standard C has another construct to handle exceptional flow control setjmp/longjmp. It has the advantage that you can break out of multiply nested control statements more easily than with break as was proposed by someone, and in addition to what goto provides has a status indication that can encode the reason for what went wrong.

    Another issue is just the syntax of your construct. It is not a good idea to use a control statement that can inadvertibly be added to. In your case

    if (bla) NOT_ERROR(X);
    else printf("wow!\n");
    

    would go fundamentally wrong. I'd use something like

    #define NOT_ERROR(X)          \
      if ((X) >= 0) { (void)0; }  \
      else return -1
    

    instead.

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