25

I have some C code I'm working with, and I'm finding errors when the code is running but have little info about how to do a proper try/catch (as in C# or C++).

For instance in C++ I'd just do:

try{
//some stuff
}
catch(...)
{
//handle error
}

but in ANSI C I'm a bit lost. I tried some online searches but I don't see enough info about how to make it happen / figured I'd ask here in case anyone can point me in the right direction.

Here's the code I'm working with (fairly simple, recursive method) and would like to wrap with try/catch (or equivalent error-handling structure).

However my main question is simply how to do a try / catch in ANSI C...the implementation / example doesn't have to be recursive.

void getInfo( int offset, myfile::MyItem * item )
{
    ll::String myOtherInfo = item->getOtherInfo();
    if( myOtherInfo.isNull() )
        myOtherInfo = "";
    ll::String getOne = "";
    myfile::Abc * abc = item->getOrig();
    if( abc != NULL )
    {
        getOne = abc->getOne();
    }
    for( int i = 0 ; i < offset ; i++ )
    {
             printf("found: %d", i);
    }
    if( abc != NULL )
        abc->release();
    int childCount = item->getChildCount();
    offset++;
    for( int i = 0 ; i < childCount ; i++ )
        getInfo( offset, item->getChild(i) );
    item->release();
}
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
aiden
  • 259
  • 1
  • 3
  • 3
  • 2
    http://www.nicemice.net/cexcept/ something that might be useful – anijhaw Sep 21 '10 at 16:59
  • 24
    This code is not C, ansi or otherwise. C does not have the `::` scope operator. – Steve Jessop Sep 21 '10 at 16:59
  • 4
    C does not have exceptions handling mechanism. All error handling is usually done with return values and errnum variable. BTW, it would be good to get some detailed expert comments of how error handling is done properly in C :) – Kel Sep 21 '10 at 17:02
  • @Steve: Good point. My answer was specifically for C, not any form of C++, no matter how C-like. – David Thornley Sep 21 '10 at 17:10
  • 2
    The only language I know of which would compile the code posted by @aiden is C++. And if it is C++, you can just use `try`/`catch`. If you actually have to write ANSI C code, then it looks like you'll have to rewrite your code to actually *be* valid C in addition to the suggestions made in the answers. – jalf Sep 21 '10 at 19:05

8 Answers8

26

Generally, you don't.

It's possible to use setjmp and longjmp to build something fairly similar to try/catch, although there's no such thing in C as destructors or stack unwinding, so RAII is out of the question. You could even approximate RAII with a so-called "cleanup stack" (see for example Symbian/C++), although it's not a very close approximation, and it's a lot of work.

The usual way to indicate errors or failure in C is to return a value indicating success status. Callers examine the return value and act accordingly. See for example the standard C functions: printf, read, open, for ideas how to specify your functions.

When mixing C and C++ code, you must ensure that a C++ exception never reaches C code. When writing C++ functions that will be called from C, catch everything.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 19
    IME the usual way to indicate errors or failure in C is to return a value that's ignored by the caller. `:(` – sbi Sep 21 '10 at 18:22
  • 2
    @sbi: IME, some people still believe the "return a value" is the way to go in C++, Java and C#, too... `:(` – paercebal Sep 22 '10 at 11:32
17

C does not support exception handling.

There is info on one approach to this problem here. This shows the simple setjmp/longjmp approach but also provides a more sophisticated alternative, covered in depth.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
4

There is the classic unwinding gotos pattern:

FILE *if = fopen(...);
FILE *of = NULL;
if (if == NULL) return; 

of = fopen(...);
if (of == NULL) goto close_if;

/* ...code... */
if (something_is_wrong) goto close_of;

/* ... other code... */

close_of:
  fclose(of);
close_if:
  fclose(if);

return state;

Alternately you can fake it in a limited way by isolating the "try" code in another function

int try_code(type *var_we_must_write, othertype var_we_only_read /*, ... */){
  /* ...code... */
  if (!some_condition) return 1;
  /* ...code... */
  if (!another_condition) return 2;
  /* ...code... */
  if (last_way_to_fail) return 4;
  return 0;
}

void calling_routine(){
  /* ... */
  if (try_code(&x,y/*, other state */) ) {
     /* do your finally here */
  }
 /* ... */
}

but neither approach is fully equivalent. You have to manage all the resources yourself, you don't get automatic rollback until a handler is found, and so on...

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • Eek! That looks really messy to me. I prefer my method I suggested below. No need for goto! – James Sep 21 '10 at 19:30
  • `goto` is the way to go (excuse the pun) in C. Having multiple exit labels seems like overkill, though. It's cleaner (although slightly less efficient) to have a single exit label and (if necessary) to check which resources were allocated. – jamesdlin Sep 21 '10 at 19:41
  • @jamesdlin: The multiple labels buy you something specific: each one corresponds to a resource that may need to be released. You can avoid them with conditionals on the releases but in some cases (not this one) that calls for additional flags. Matter of taste as far as I can tell. – dmckee --- ex-moderator kitten Sep 21 '10 at 19:50
  • Yeah, but it has the cost of some extra complexity, and IMO it's a bit harder to maintain (because order matters). In most cases additional flags (and additional checks) shouldn't be necessary anyway; clean-up functions (not `fclose`) generally should be no-ops if called on dummy values (e.g. `NULL`). – jamesdlin Sep 21 '10 at 20:41
  • @jamesdlin: when I've done this style of cleanup (generally in assembler rather than C), I've found the goto version cleaner and simpler in cases where the potential resources are always allocated in the same order. You can easily know which one to `goto`, because at any given point in the code you understand what resources you have (I hope). – Steve Jessop Sep 22 '10 at 15:58
4

One useful coding style I like to use is the following. I don't know if it has a particular name but I came across it when I was reverse engineering some assembly code into the equivalent C code. You do lose an indentation level but it's not such a big deal to me. Watch out for reviewer who will point out the infinite loop! :)

int SomeFunction() {
    int err = SUCCESS;

    do {
        err = DoSomethingThatMayFail();
        if (err != SUCCESS) {
            printf("DoSomethingThatMayFail() failed with %d", err);
            break;
        }

        err = DoSomethingElse();
        if (err != SUCCESS) {
            printf("DoSomethingElse() failed with %d", err);
            break;
        }

        // ... call as many functions as needed.

        // If execution gets there everything succeeded!
        return SUCCESS;
    while (false);

    // Something went wrong!
    // Close handles or free memory that may have been allocated successfully.

    return err;
}
James
  • 9,064
  • 3
  • 31
  • 49
4

This is my implementation of an exception handling system in C: exceptions4c.

It's powered by macros, built on top of setjmp and longjmp and it is 100% portable ANSI C.

There you can also find a list of all the different implementations I know of.

Guillermo Calvo
  • 777
  • 6
  • 9
3

You can find a possible implementation with longjmp in this book : C Interfaces and Implementations: Techniques for Creating Reusable Software - David Hanson

And you can find the code here and here as an example of the style used in the book :

except.h

/* $Id$ */
#ifndef EXCEPT_INCLUDED
#define EXCEPT_INCLUDED
#include <setjmp.h>
#define T Except_T
typedef struct T {
    const char *reason;
} T;
typedef struct Except_Frame Except_Frame;
struct Except_Frame {
    Except_Frame *prev;
    jmp_buf env;
    const char *file;
    int line;
    const T *exception;
};
enum { Except_entered=0, Except_raised,
       Except_handled,   Except_finalized };
extern Except_Frame *Except_stack;
extern const Except_T Assert_Failed;
void Except_raise(const T *e, const char *file,int line);
#ifdef WIN32
#include <windows.h>

extern int Except_index;
extern void Except_init(void);
extern void Except_push(Except_Frame *fp);
extern void Except_pop(void);
#endif
#ifdef WIN32
/* $Id$ */
#define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
#define RERAISE Except_raise(Except_frame.exception, \
    Except_frame.file, Except_frame.line)
#define RETURN switch (Except_pop(),0) default: return
#define TRY do { \
    volatile int Except_flag; \
    Except_Frame Except_frame; \
    if (Except_index == -1) \
        Except_init(); \
    Except_push(&Except_frame);  \
    Except_flag = setjmp(Except_frame.env); \
    if (Except_flag == Except_entered) {
#define EXCEPT(e) \
        if (Except_flag == Except_entered) Except_pop(); \
    } else if (Except_frame.exception == &(e)) { \
        Except_flag = Except_handled;
#define ELSE \
        if (Except_flag == Except_entered) Except_pop(); \
    } else { \
        Except_flag = Except_handled;
#define FINALLY \
        if (Except_flag == Except_entered) Except_pop(); \
    } { \
        if (Except_flag == Except_entered) \
            Except_flag = Except_finalized;
#define END_TRY \
        if (Except_flag == Except_entered) Except_pop(); \
        } if (Except_flag == Except_raised) RERAISE; \
} while (0)
#else
#define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
#define RERAISE Except_raise(Except_frame.exception, \
    Except_frame.file, Except_frame.line)
#define RETURN switch (Except_stack = Except_stack->prev,0) default: return
#define TRY do { \
    volatile int Except_flag; \
    Except_Frame Except_frame; \
    Except_frame.prev = Except_stack; \
    Except_stack = &Except_frame;  \
    Except_flag = setjmp(Except_frame.env); \
    if (Except_flag == Except_entered) {
#define EXCEPT(e) \
        if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
    } else if (Except_frame.exception == &(e)) { \
        Except_flag = Except_handled;
#define ELSE \
        if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
    } else { \
        Except_flag = Except_handled;
#define FINALLY \
        if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
    } { \
        if (Except_flag == Except_entered) \
            Except_flag = Except_finalized;
#define END_TRY \
        if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
        } if (Except_flag == Except_raised) RERAISE; \
} while (0)
#endif
#undef T
#endif

except.c

static char rcsid[] = "$Id$" "\n$Id$";
#include <stdlib.h>
#include <stdio.h>
#include "assert.h"
#include "except.h"
#define T Except_T
Except_Frame *Except_stack = NULL;
void Except_raise(const T *e, const char *file,
    int line) {
#ifdef WIN32
    Except_Frame *p;

    if (Except_index == -1)
        Except_init();
    p = TlsGetValue(Except_index);
#else
    Except_Frame *p = Except_stack;
#endif
    assert(e);
    if (p == NULL) {
        fprintf(stderr, "Uncaught exception");
        if (e->reason)
            fprintf(stderr, " %s", e->reason);
        else
            fprintf(stderr, " at 0x%p", e);
        if (file && line > 0)
            fprintf(stderr, " raised at %s:%d\n", file, line);
        fprintf(stderr, "aborting...\n");
        fflush(stderr);
        abort();
    }
    p->exception = e;
    p->file = file;
    p->line = line;
#ifdef WIN32
    Except_pop();
#else
    Except_stack = Except_stack->prev;
#endif
    longjmp(p->env, Except_raised);
}
#ifdef WIN32
_CRTIMP void __cdecl _assert(void *, void *, unsigned);
#undef assert
#define assert(e) ((e) || (_assert(#e, __FILE__, __LINE__), 0))

int Except_index = -1;
void Except_init(void) {
    BOOL cond;

    Except_index = TlsAlloc();
    assert(Except_index != TLS_OUT_OF_INDEXES);
    cond = TlsSetValue(Except_index, NULL);
    assert(cond == TRUE);
}

void Except_push(Except_Frame *fp) {
    BOOL cond;

    fp->prev = TlsGetValue(Except_index);
    cond = TlsSetValue(Except_index, fp);
    assert(cond == TRUE);
}

void Except_pop(void) {
    BOOL cond;
    Except_Frame *tos = TlsGetValue(Except_index);

    cond = TlsSetValue(Except_index, tos->prev);
    assert(cond == TRUE);
}
#endif
Matthieu
  • 4,605
  • 4
  • 40
  • 60
1

If you want to do a multiple level jump, look up setjmp() and longjmp(). They can be used as a primitive exception throw. The setjmp() function sets up a return-to place, and returns a status value. The longjmp() function goes to the return-to place, and provides the status value. You can create a catch function by having be called after setjmp() depending on the status value.

Do not, for whatever reason, use them in C++. They do not do stack unwinding or call destructors.

David Thornley
  • 56,304
  • 9
  • 91
  • 158
0

Since C++ was originally implemented as a C pre-processor and it had Try / Catch you could redo Bjarne Stroustrup's work and write a pre-processor to do it.

Hogan
  • 69,564
  • 10
  • 76
  • 117
  • 1
    By the time exceptions were introduced to C++, the C-preprocessor implementation was a history. – Nemanja Trifunovic Sep 21 '10 at 17:31
  • @Nemanja: That is indeed true for the original cfront. [Comeau C++](http://www.comeaucomputing.com), however, still outputs C code. (Because that makes for high portability.) But I doubt that this is anymore useful than assembler output of any other compiler: it's machine-generated code, and as such not for human consumption. – sbi Sep 21 '10 at 18:24
  • 1
    I believe exceptions were the straw that broke the camel's back, where the camel in question was the cfront C++ compiler. See Stroupstrup "Design & Evolution of C++". – Jonathan Leffler Sep 21 '10 at 19:05