Home > database >  How to efficiently display errors in C?
How to efficiently display errors in C?

Time:01-29

I tried a new way of protecting my code through some binary flags.
I think binary flags are very handy in such situations because it is easy to check for conditions, for example the FAILURE flag below that tells if a failure occured or not is very easy to code.
Issue is it is easy to get tangled up with 1-digit difference flags.

# define EXIT_FLAG              0b10000000000000000000000000000000

# define FAILURE                0b00000000000000000001111111111111
# define FAILED_FORK            0b00000000000000000000000000000001
# define FAILED_WAITPID         0b00000000000000000000000000000010
# define FAILED_SEM_OPEN        0b00000000000000000000000000000100
# define FAILED_SEM_CLOSE       0b00000000000000000000000000001000
# define FAILED_SEM_POST        0b00000000000000000000000000010000
# define FAILED_SEM_WAIT        0b00000000000000000000000000100000
# define FAILED_SEM_UNLINK      0b00000000000000000000000001000000
# define FAILED_CREAT_TH        0b00000000000000000000000010000000
# define FAILED_JOIN_TH         0b00000000000000000000000100000000
# define FAILED_KILL            0b00000000000000000000001000000000
# define FAILED_GET_TIME        0b00000000000000000000010000000000
# define FAILED_USLEEP          0b00000000000000000000100000000000
# define FAILED_WRITE           0b00000000000000000001000000000000

# define USERGUIDE              0b00000000000000000010000000000000
# define USERGUIDE_MSG          "usage:\n\t./philo {nb_philos} {die_tm} \
                                {eat_tm} {sleep_tm} (max_eat)\n \
                                \tinputs in ms is capped to 60,000 ms\n"

int ft_putstr_fd(char *s, int fd)
{
    if (s)
    {
        while (*s)
        {
            if (write(fd, s  , 1) == -1)
            {
                write(fd, "Failed write\n", 13);
                return (0);
            }
        }
    }
    return (1);
}

int ft_putstr_error(char *s)
{
    return (ft_putstr_fd(s, STDERR_FILENO));
}

void    *ft_puterror(int flag, void *args)
{
    if (flag & FAILED_FORK)
        ft_putstr_error("Failed fork: ");
    else if (flag & FAILED_WAITPID)
        ft_putstr_error("Failed waitpid: ");
    else if (flag & FAILED_SEM_OPEN)
        ft_putstr_error("Failed sem_open: ");
    else if (flag & FAILED_SEM_CLOSE)
        ft_putstr_error("Failed sem_close: ");
    else if (flag & FAILED_SEM_POST)
        ft_putstr_error("Failed sem_post: ");
    else if (flag & FAILED_SEM_WAIT)
        ft_putstr_error("Failed sem_wait: ");
    else if (flag & FAILED_SEM_UNLINK)
        ft_putstr_error("Failed sem_unlink: ");
    else if (flag & FAILED_CREAT_TH)
        ft_putstr_error("Failed create thread: ");
    else if (flag & FAILED_JOIN_TH)
        ft_putstr_error("Failed joining thread: ");
    else if (flag & FAILED_KILL)
        ft_putstr_error("Failed kill: ");
    else if (flag & FAILED_GET_TIME)
        ft_putstr_error("Failed get_time: ");
    else if (flag & FAILED_USLEEP)
        ft_putstr_error("Failed usleep: ");
    else if (flag & FAILED_WRITE)
        ft_putstr_error("Failed write: ");
    if (flag & FAILURE)
    {
        ft_putstr_error((char *)args);
        ft_putstr_error("\n");
    }
    if (flag & USERGUIDE)
        ft_putstr_error(USERGUIDE_MSG);
    return (NULL);
}

Would you recommend to use this method to handle such errors or is there a nicer way, like a best practice ?

CodePudding user response:

It is much less error-prone if you use a sequence of numbers for the failure codes. Whenever you need a bitmask, simply shift one bit N times to the left, e.g.

//specify failure code type
typedef uint64_t failure_t;

//create a bitmask of the specified failure code
#define FAILURE_MASK(FCODE) (UINT64_C(1) << (FCODE))

//list of failure constants
#define NO_FAILURE          UINT64_C(0) //add a no failure flag
#define FAILED_FORK         UINT64_C(1)
#define FAILED_WAITPID      UINT64_C(2)
#define FAILED_SEM_OPEN     UINT64_C(3)
#define FAILED_SEM_CLOSE    UINT64_C(4)
#define FAILED_SEM_POST     UINT64_C(5)
#define FAILED_SEM_WAIT     UINT64_C(6)
#define FAILED_SEM_UNLINK   UINT64_C(7)
#define FAILED_CREAT_TH     UINT64_C(8)
#define FAILED_JOIN_TH      UINT64_C(9)
#define FAILED_KILL         UINT64_C(10)
#define FAILED_GET_TIME     UINT64_C(11)
#define FAILED_USLEEP       UINT64_C(12)
#define FAILED_WRITE        UINT64_C(13)
//... extend as you see fit
#define LAST_FAILURE        FAILED_WRITE //useful for iteration

//list of failure strings
static const char *failure_strings[] = {
    "No failure",
    "Failed fork",
    "...",
    "Failed write"
};

//extern function to retrieve the failure string
const char* get_failure_string(failure_t failure)
{
    return failure_strings[failure];
}

//test if the failure set contains the specified failure
#define is_failure_set(FSET, FCODE) FSET & FAILURE_MASK(FCODE)

//set the failure code in the failure set
#define set_failure(FSET, FCODE) FSET |= FAILURE_MASK(FCODE)

/**/

void *ft_puterror(failure_t failure_set, void *args)
{
    if (failure_set == NO_FAILURE) //no failure set, nothing to do ...
        return NULL;               //unless no failure should be reported too

    //for each failure that is set in failure_set
    for (failure_t n=1; n <= LAST_FAILURE;   n, failure_set >>= 1) {
        if (failure_set & 1) { //failure n is set, report it
            ft_putstr_error(get_failure_string(n));
            ft_putstr_error((char *)args);
            ft_putstr_error("\n");
        }
    }

    //...

    return NULL;
}

Adding a new failure code requires only 3 steps:

  • add a new const, e.g. #define FAILED_NEW 14
  • set the constant LAST_FAILURE to the appropriate value, e.g. #define LAST_FAILURE FAILED_NEW
  • add a new string to the failure_strings table, e.g. "Failed new"
  • Related