Gestion des erreurs
Introduction
Si nous vivions dans un monde parfait sans erreur, ce chapitre n’existerait pas. Mais ce monde n’est pas le nôtre et les erreurs surviennent. Il faut donc les corriger, et avant cela, les détecter. Mais dans la mesure du possible, les prévenir est encore mieux. Mais comment les anticiper ? Comment réagir ensuite ? De nombreuses erreurs sont récurrentes d’un programme à l’autre et avant de donner quelques recettes, nous allons passer ces erreurs en revue.
1. Fautes de frappe
La plupart des fautes de frappe ne résistent pas à l’étape de pré-traitement du code par le préprocesseur qui ne comprend pas le mot contenant la faute de frappe et le signale.
Certaines fautes de frappe sont syntaxiquement correctes mais ne font pas ce que nous leur demandons. Parmi celles-ci, l’écriture du test (variable = 0) au lieu de (variable == 0). Le premier remet la variable à zéro et renvoie ce contenu qui est 0, donc le test est toujours faux. Pour éviter ce genre de fautes de frappe, nous pouvons mettre la constante avant l’opérateur et la variable après. Cela donne (0 == variable) et en cas de faute de frappe, le préprocesseur ne comprend pas (0 = variable). La confusion entre le ET logique (&) et le ET de condition (&&) est une autre faute de frappe courante. Il est difficile de s’en prémunir.
L’utilisation d’une constante globale contenant une valeur à un endroit et une autre à un autre endroit peut venir d’une faute de frappe comme d’une erreur d’inattention, voire d’une modification du code à un endroit, mais non répercutée à un autre. Nous opterons toujours pour l’utilisation des macros à la place de constantes dès que la valeur de la constante est susceptible d’être modifiée quelque part. Nous utiliserons par exemple une macro DEFAULT_BUFFER_LEN pour définir de manière constante la taille d’un espace tampon. Par contre...
Récupérer le code d’erreur
Problème
Une erreur est survenue. Vous souhaitez obtenir le code de l’erreur.
Solution
Utilisez la variable errno qui contient ce code.
Discussion
Lorsqu’une erreur survient dans un appel système, un code d’erreur est placé dans la variable errno. Elle est déclarée dans le fichier d’en-têtes errno.h.
Dans certains cas, le programmeur considérera qu’une erreur est normale alors que dans d’autres, elles ne le sont pas et appellent à un traitement particulier. C’est le cas, par exemple, lorsque vous créez un répertoire. Une méthode consiste à tester s’il n’existe pas déjà, avec la fonction stat(). Si le code retour est -1, vous pouvez vérifier que errno est bien fixé à ENOENT, ce qui signifie que le fichier n’existe pas. Une telle erreur est en réalité le cas le plus favorable pour continuer. Inversement, un retour sans erreur de stat() implique que le fichier existe ; il faut alors tester si c’est un répertoire puis opter pour le comportement adéquat en fonction du résultat. Une autre méthode consiste à créer directement le répertoire avec mkdir(). Si le code retour est nul, tout s’est bien passé. S’il est égal à -1, il faut vérifier la variable errno...
Récupérer le descriptif de l’erreur
Problème
Une erreur est survenue. Vous souhaitez savoir à quoi elle correspond.
Solution
Dans le programme, un descriptif rapide de l’erreur peut être obtenu avec strerror(). Pour plus d’informations, consultez la page de manuel de la fonction ayant entraîné l’erreur.
Discussion
Pour obtenir un descriptif rapide de l’erreur, vous pouvez utiliser strerror() avec errno pour argument. Ainsi, après une erreur, vous pourrez écrire ceci :
fprintf (stderr, "Erreur (%d) : %s\n", errno, strerror (errno));
Nous pouvons remarquer que la fonction perror() fonctionne comme si nous exécutions fprintf(stderr, "%s : %s\n", message, strerror(errno)) où le message est l’argument à fournir à perror().
Plutôt que d’écrire la ligne précédente, vous pouvez utiliser une macro, comme ceci, ou avec un nom plus court :
#define PRINT_ERROR_DESCRIPTION fprintf(stderr, \
"%s:%d Erreur (%d) : %s\n", \
__FILE__, __LINE__, \
errno, strerror(errno))
Par ailleurs, les pages de manuel contiennent souvent des explications plus détaillées...
Garder une trace de l’exécution d’un programme
Problème
Vous souhaitez tracer votre programme de manière passive pour effectuer une analyse des points qui vous intéressent lors de l’exécution. En d’autres termes, au lieu d’intervenir avec des outils tels que strace, truss ou tusc, vous attendez du programme qu’il soit capable de créer des traces de lui-même.
Solution
Utilisez une fonction de journalisation.
Discussion
Si vous souhaitez obtenir des traces de l’exécution de votre programme, vous pouvez lui faire consigner son activité en utilisant une fonction de journalisation. La recette "Créer une fonction de journalisation" vous montre comment créer la vôtre et la recette "Utiliser syslog" explique comment utiliser le journal système avec syslog(). Vous pouvez également utiliser une fonction toute faite, comme l’une de celles fournies dans la bibliothèque glib.
Avec glib, pour écrire une chaîne de caractères dans le journal, nous utilisons g_log(). Le premier argument est le domaine, en général NULL pour les applications. Pour les bibliothèques, cette chaîne de caractères présente l’intérêt, lorsqu’elle est définie, de distinguer les messages issus de celles-ci de ceux issus du programme qui les charge. Le second argument...
Créer une fonction de journalisation
Problème
Vous souhaitez créer votre propre fonction de journalisation pour stocker de manière personnalisée les messages issus de votre programme. Comme printf(), vous souhaitez que ce soit une fonction à nombre variable d’arguments.
Solution
Utilisez la bibliothèque glib et la fonction g_log_set_handler(), comme dans la recette précédente, ou créez une fonction à nombre variable d’arguments sur le modèle de printf().
Discussion
Pour l’utilisation de g_log_set_handler(), voyez la recette précédente. Nous allons créer ici notre propre fonction qui placera, dans un fichier /var/log/test.log, l’heure à laquelle la fonction a été appelée, ainsi que le nom du fichier et le numéro de ligne d’où la fonction a été appelée. Nous ajouterons un niveau d’erreur et un message. Le message sera spécifié de la même manière que pour printf(), à savoir un format suivi d’un nombre variable d’arguments. Voici cette fonction :
#define LOG_MESSAGE_LENGTH 256
char *log_file_name = NULL;
int
ma_log (char *file, int line, char *log_level, char *fmt, ...)
{
va_list args;
char message[LOG_MESSAGE_LENGTH];
char log_date[] = "YYYY/MM/DD...
Utiliser syslog
Problème
Vous souhaitez utiliser le journal système pour stocker les messages issus de votre programme.
Solution
Utilisez syslog() comme fprintf() avec comme seule différence le premier argument qui indique le niveau d’erreur et le domaine (facility).
Discussion
Pour utiliser syslog(), il est inutile d’ouvrir un fichier ou de préparer quoi que ce soit. Cela rend cette fonction très simple d’utilisation. Son premier argument est défini par un OU logique (symbole |) d’un niveau d’erreur et d’un domaine parmi ceux-ci :
/* Niveau d'erreur */
LOG_EMERG /* Niveau de panique, habituellement envoyé à
* tous les utilisateurs. */
LOG_ALERT /* Niveau d'alerte nécessitant habituellement
* une correction immédiate. */
LOG_CRIT /* Niveau critique. */
LOG_ERR /* Messages d'erreur. */
LOG_WARNING /* Messages d'avertissement. */
LOG_NOTICE /* Messages de points remarquables. */
LOG_INFO /* Messages d'information. */
LOG_DEBUG /* Messages de débogage. */
/* Domaines */
LOG_AUTH /* Authentification : login(1), su(1), getty(8), etc. */
LOG_AUTHPRIV /* Comme LOG_AUTH, mais à destination d'un fichier
* avec des permissions restreintes. */
LOG_CONSOLE /* Message à destination de /dev/console, écrits
* par le pilote...