Try catch statements in C

前端 未结 13 1411
眼角桃花
眼角桃花 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:31

    C itself doesn't support exceptions but you can simulate them to a degree with setjmp and longjmp calls.

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

    This website has a nice tutorial on how to simulate exceptions with setjmp and longjmp

    • http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
    0 讨论(0)
  • 2020-11-27 10:32

    This is another way to do error handling in C which is more performant than using setjmp/longjmp. Unfortunately, it will not work with MSVC but if using only GCC/Clang is an option, then you might consider it. Specifically, it uses the "label as value" extension, which allows you to take the address of a label, store it in a value and and jump to it unconditionally. I'll present it using an example:

    GameEngine *CreateGameEngine(GameEngineParams const *params)
    {
        /* Declare an error handler variable. This will hold the address
           to jump to if an error occurs to cleanup pending resources.
           Initialize it to the err label which simply returns an
           error value (NULL in this example). The && operator resolves to
           the address of the label err */
        void *eh = &&err;
    
        /* Try the allocation */
        GameEngine *engine = malloc(sizeof *engine);
        if (!engine)
            goto *eh; /* this is essentially your "throw" */
    
        /* Now make sure that if we throw from this point on, the memory
           gets deallocated. As a convention you could name the label "undo_"
           followed by the operation to rollback. */
        eh = &&undo_malloc;
    
        /* Now carry on with the initialization. */
        engine->window = OpenWindow(...);
        if (!engine->window)
            goto *eh;   /* The neat trick about using approach is that you don't
                           need to remember what "undo" label to go to in code.
                           Simply go to *eh. */
    
        eh = &&undo_window_open;
    
        /* etc */
    
        /* Everything went well, just return the device. */
        return device;
    
        /* After the return, insert your cleanup code in reverse order. */
    undo_window_open: CloseWindow(engine->window);
    undo_malloc: free(engine);
    err: return NULL;
    }
    

    If you so wish, you could refactor common code in defines, effectively implementing your own error-handling system.

    /* Put at the beginning of a function that may fail. */
    #define declthrows void *_eh = &&err
    
    /* Cleans up resources and returns error result. */
    #define throw goto *_eh
    
    /* Sets a new undo checkpoint. */
    #define undo(label) _eh = &&undo_##label
    
    /* Throws if [condition] evaluates to false. */
    #define check(condition) if (!(condition)) throw
    
    /* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
    #define checkpoint(label, condition) { check(condition); undo(label); }
    

    Then the example becomes

    GameEngine *CreateGameEngine(GameEngineParams const *params)
    {
        declthrows;
    
        /* Try the allocation */
        GameEngine *engine = malloc(sizeof *engine);
        checkpoint(malloc, engine);
    
        /* Now carry on with the initialization. */
        engine->window = OpenWindow(...);
        checkpoint(window_open, engine->window);
    
        /* etc */
    
        /* Everything went well, just return the device. */
        return device;
    
        /* After the return, insert your cleanup code in reverse order. */
    undo_window_open: CloseWindow(engine->window);
    undo_malloc: free(engine);
    err: return NULL;
    }
    
    0 讨论(0)
  • 2020-11-27 10:34

    In C99, you can use setjmp/longjmp for non-local control flow.

    Within a single scope, the generic, structured coding pattern for C in the presence of multiple resource allocations and multiple exits uses goto, like in this example. This is similar to how C++ implements destructor calls of automatic objects under the hood, and if you stick to this diligently, it should allow you for a certain degree of cleanness even in complex functions.

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

    A quick google search yields kludgey solutions such as this that use setjmp/longjmp as others have mentioned. Nothing as straightforward and elegant as C++/Java's try/catch. I'm rather partial to Ada's exception handling myself.

    Check everything with if statements :)

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

    Warning: the following is not very nice but it does the job.

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct {
        unsigned int  id;
        char         *name;
        char         *msg;
    } error;
    
    #define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " ‘%s_error’ " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__)
    #define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__)
    #define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg)
    
    #define _errordef(n, _id) \
    error* new_##n##_error_msg(char* msg) { \
        error* self = malloc(sizeof(error)); \
        self->id = _id; \
        self->name = #n; \
        self->msg = msg; \
        return self; \
    } \
    error* new_##n##_error() { return new_##n##_error_msg(""); }
    
    #define errordef(n) _errordef(n, __COUNTER__ +1)
    
    #define try(try_block, err, err_name, catch_block) { \
        error * err_name = NULL; \
        error ** __err = & err_name; \
        void __try_fn() try_block \
        __try_fn(); \
        void __catch_fn() { \
            if (err_name == NULL) return; \
            unsigned int __##err_name##_id = new_##err##_error()->id; \
            if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \
                printuncaughterr(); \
            else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \
                catch_block \
        } \
        __catch_fn(); \
    }
    
    #define throw(e) { *__err = e; return; }
    
    _errordef(any, 0)
    

    Usage:

    errordef(my_err1)
    errordef(my_err2)
    
    try ({
        printf("Helloo\n");
        throw(new_my_err1_error_msg("hiiiii!"));
        printf("This will not be printed!\n");
    }, /*catch*/ any, e, {
        printf("My lovely error: %s %s\n", e->name, e->msg);
    })
    
    printf("\n");
    
    try ({
        printf("Helloo\n");
        throw(new_my_err2_error_msg("my msg!"));
        printf("This will not be printed!\n");
    }, /*catch*/ my_err2, e, {
        printerr("%s", e->msg);
    })
    
    printf("\n");
    
    try ({
        printf("Helloo\n");
        throw(new_my_err1_error());
        printf("This will not be printed!\n");
    }, /*catch*/ my_err2, e, {
        printf("Catch %s if you can!\n", e->name);
    })
    

    Output:

    Helloo
    My lovely error: my_err1 hiiiii!
    
    Helloo
    /home/naheel/Desktop/aa.c:28: error: ‘my_err2_error’ my msg!
    
    Helloo
    /home/naheel/Desktop/aa.c:38: uncaught error: ‘my_err1_error’ 
    

    Keep on mind that this is using nested functions and __COUNTER__. You'll be on the safe side if you're using gcc.

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

    Redis use goto to simulate try/catch, IMHO it is very clean and elegant:

    /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
    int rdbSave(char *filename) {
        char tmpfile[256];
        FILE *fp;
        rio rdb;
        int error = 0;
    
        snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
        fp = fopen(tmpfile,"w");
        if (!fp) {
            redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
                strerror(errno));
            return REDIS_ERR;
        }
    
        rioInitWithFile(&rdb,fp);
        if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
            errno = error;
            goto werr;
        }
    
        /* Make sure data will not remain on the OS's output buffers */
        if (fflush(fp) == EOF) goto werr;
        if (fsync(fileno(fp)) == -1) goto werr;
        if (fclose(fp) == EOF) goto werr;
    
        /* Use RENAME to make sure the DB file is changed atomically only
         * if the generate DB file is ok. */
        if (rename(tmpfile,filename) == -1) {
            redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
            unlink(tmpfile);
            return REDIS_ERR;
        }
        redisLog(REDIS_NOTICE,"DB saved on disk");
        server.dirty = 0;
        server.lastsave = time(NULL);
        server.lastbgsave_status = REDIS_OK;
        return REDIS_OK;
    
    werr:
        fclose(fp);
        unlink(tmpfile);
        redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
        return REDIS_ERR;
    }
    
    0 讨论(0)
提交回复
热议问题