0

I would like to do some error handling in C. I like C# exceptions so decided to do something like that. The goals are:

  • One line error handling in caller functions like throw exception in C#
  • Breaks the code sequence in caller function where the error happened
  • To know the file/line where the error happened
  • Verbal description about the error

What do you think is there any problem with this approach?

The caller function:

EError activate_add(uint32_t key)
{
    if (activate_bufferSize+1>=activate_bufferMax) THROW_ERROR("activate list full");
    activate_buffer[activate_bufferSize].Id = activate_bufferSize+1;
    activate_buffer[activate_bufferSize].StoredKey = key;
    activate_bufferSize++;
    TRY(activate_saveToEEProm());
    NO_ERROR();
}

My solution is the following:

#ifndef ERROR_H_
#define ERROR_H_


typedef enum
{
    Message_None,
    Message_Error,
    Message_Warning,
    Message_Info
}EMessage_type;

typedef struct
{
    EMessage_type message_type;
    char* message;
    const char* file;
    uint32_t line;
}EErrorStruct,*EError;

#define THROW_ERROR(message, ...) { EError __err=GET_MESSAGE(Message_Error,(char*)__FILE__,__LINE__,message,##__VA_ARGS__); SEND_MESSAGE(__err); return __err;}
#define TRY(__x) { EError __err = __x; if (__err->message_type==Message_Error) { return __err;}}

#define TRYHAL(__x) { HAL_StatusTypeDef __res = __x; if (__res != HAL_OK) { THROW_ERROR("HAL problem: %u",__res);}}
#define TRYFAT(__x) { FRESULT __res = __x; if (__res != FR_OK) { THROW_ERROR("FAT problem: %u",__res);}}

#define NO_ERROR() { return GET_MESSAGE(Message_None,NULL,0,NULL,0);}
#define SEND_ERROR(message , ...) { SEND_MESSAGE(GET_MESSAGE(Message_Error,(char*)__FILE__,__LINE__,message,##__VA_ARGS__)); }
#define SEND_WARN(message , ...) { SEND_MESSAGE(GET_MESSAGE(Message_Warning,(char*)__FILE__,__LINE__,message,##__VA_ARGS__)); }
#define SEND_INFO(message , ...) { SEND_MESSAGE(GET_MESSAGE(Message_Info,(char*)__FILE__,__LINE__,message,##__VA_ARGS__)); }

EError GET_MESSAGE(EMessage_type messagetype,const char* file, uint32_t line,const char *format, ... );
void SEND_MESSAGE(EError err);
uint8_t ISFAILED(EError err);


#endif /* ERROR_H_ */

The source file

#include <error.h>
#include "stdio.h"
#include "stdarg.h"

static EErrorStruct error;
static const EError m = &error;
static char buffer[300];

EError GET_MESSAGE(EMessage_type messagetype,const char* file, uint32_t line,const char *format, ... )
{

    va_list args;
    va_start(args,format);
    vsprintf(buffer, format, args);
    va_end(args);

    m->message = buffer;
    m->message_type = messagetype;
    m->file = file;
    m->line = line;

    return m;
}



void SEND_MESSAGE(EError err)
{
    switch (err->message_type) {
        case Message_Error:
            printf("ERRO: %s \r\n\t\t\t\t\t\t\t\t\t\t at %s line: %u\r\n",err->message,err->file,(unsigned int)err->line);
            break;
        case Message_Warning:
            printf("WARN: %s \r\n\t\t\t\t\t\t\t\t\t\t at %s line: %u\r\n",err->message,err->file,(unsigned int)err->line);
            break;
        case Message_Info:
            printf("INFO: %s \r\n\t\t\t\t\t\t\t\t\t\t at %s line: %u\r\n",err->message,err->file,(unsigned int)err->line);
            break;
        default:
            break;
    }

}
uint8_t ISFAILED(EError err)
{
    if (err->message_type == Message_Error) return 1;


return 0;
}
David Molnar
  • 419
  • 1
  • 6
  • 14
  • 1
    More luck on [codereview.stackexchange.com/](https://codereview.stackexchange.com/) – LPs May 12 '17 at 11:50
  • This is quite intrusive on the function which you want to have report errors, since your approach specifies the return type (as a pointer), requires the caller to check that pointer. It also will not work in multithreaded code (C11 and later) since it uses unprotected statics. If you really want to use something like exceptions to report errors, probably better to use C++, since the solution will be simpler and less intrusive on user code. – Peter May 12 '17 at 11:51
  • I am a bit skeptical that this will actually work in practice. I don't see how you could really emulate the functionality of `try` without using `setjmp/longjmp` (which you don't seem to use). In any event, if you could get it to work then it wouldn't be idiomatic C. It might be better to either use C idioms for error handling (validate input, return error codes) or follow @Peter's idea and use C++. – John Coleman May 12 '17 at 11:59
  • Thank you for your comments. @JohnColeman: true that there is no solution for catch. But the error can bubble up on call stack until it is handled. Multithread also missing. – David Molnar May 15 '17 at 21:29

1 Answers1

3

The following are all widely considered as bad practice:

  • Function-like macros.
  • Variadic macros.
  • Variadic functions.
  • Inventing your own language features through macros.
  • Using non-standard language features such as ##__VA_ARGS__.

There are many reasons why these are bad practice: non-existent type safety, very hard to read, hard to debug/maintain, portability and so on.

Thus combining all of the above into a single program is a very bad idea.


Error handing in C is handled through a de facto industry standard, which basically goes like this:

  • Error codes are defined through enums.
  • The return value of any API function is reserved for the error code.
  • Upon error the function returns an error code.
  • Compilers are set to a warning level where you get a warning if you ignore the result of a function.
  • Source code documentation states what will/will not happen with other parameters upon success and error.

This is what other C programmers expect and immediately understand. They do not understand or expect some home-brewed exception handling system.

In addition, there is a lot of criticism against exception handling in languages that do support it. See Why is exception handling bad?

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I'm sure there are several industry standards for C programming, but I don't know which one is _de facto_. – Ian Abbott May 12 '17 at 14:26
  • @IanAbbott This is how all professional APIs and software stacks are written, pretty much. – Lundin May 13 '17 at 10:37
  • It doesn't seem all that common in the open source world. At least I can't think of any off the top of my head that work like that! – Ian Abbott May 15 '17 at 17:14
  • Very useful explanation ty. Function like macros with return value gives me compiler warnings they are nasty. Anyway I have to combine the __FILE__ __LINE__ constants it is very useful at debugging. I think I will use the enum error code style which is used by a lot of library. – David Molnar May 15 '17 at 21:46