« Programmation C/Erreurs » : différence entre les versions

Un livre de Wikilivres.
Contenu supprimé Contenu ajouté
ortho !
Suppression des cast en void et renommage de la fonction teste en test.
Ligne 55 : Ligne 55 :
#include <errno.h> /* errno */
#include <errno.h> /* errno */
static void teste(const char *nombre)
static void test(const char *nombre)
{
{
unsigned long res;
unsigned long res;
Ligne 73 : Ligne 73 :
{
{
/* Il y a eu une erreur ! */
/* Il y a eu une erreur ! */
(void)fprintf(stderr, "Impossible de convertir le nombre '%s': %s.\n",
fprintf(stderr, "Impossible de convertir le nombre '%s': %s.\n",
nombre, strerror(errno));
nombre, strerror(errno));
}
}
else
else
{
{
(void)printf("La conversion a été effectuée, et la valeur est: %lu\n", res);
printf("La conversion a été effectuée, et la valeur est: %lu\n", res);
}
}
}
}
Ligne 85 : Ligne 85 :
{
{
/* 2 puissance 8 : sera toujours accepte */
/* 2 puissance 8 : sera toujours accepte */
teste("256");
test("256");
/* 2^64 - 1 : peut echouer suivant la machine */
/* 2^64 - 1 : peut echouer suivant la machine */
teste("18446744073709551615");
test("18446744073709551615");
return EXIT_SUCCESS;
return EXIT_SUCCESS;
}
}

Version du 16 mars 2020 à 10:59


Le langage C fournit un en-tête spécialisé pour la gestion des erreurs : <errno.h>. Cet en-tête déclare notamment une variable globale errno, et un certain nombre de codes d'erreur, qui permettent aux fonctions de la bibliothèque standard de reporter précisément la cause d'une erreur.

Utilisation

Pour inclure l'en-tête de son fichier source, il faut ajouter la ligne :

#include <errno.h>

On peut alors utiliser la variable errno, de type int, pour traiter les erreurs[1].

Lorsqu'on veut utiliser errno pour déterminer la cause d'un échec, il faut d'abord s'assurer que la fonction a bel et bien échoué. Le C laissant une certaine liberté dans la manière de signaler un échec, il n'y a pratiquement aucun mécanisme universel qui permette de détecter une telle situation, chaque fonction étant presque un cas particulier. Cependant, une pratique relativement répandue est de retourner un code spécifique, en général en dehors de l'intervalle de ce qu'on attend. Par exemple, lorsque la fonction est censée allouer un objet et retourne un pointeur, une erreur est souvent signalée en retournant un pointeur nul, et la variable errno décrit plus en détail la nature de l'erreur.

Il est nécessaire de placer errno à 0 avant d'utiliser la fonction qui peut échouer, car les fonctions de la bibliothèque standard ne sont pas obligées de la mettre à zéro en cas de succès. Si on ne la réinitialisait pas « manuellement », on pourrait « voir » le résultat d'une erreur causée par un appel de fonction antérieur. La procédure à suivre est donc la suivante :

  • Mettre errno à 0 ;
  • Appeler la fonction (de la bibliothèque standard) que l'on souhaite ;
  • Vérifier si elle a échoué ;
    • Si c'est le cas : la valeur de errno est disponible pour traiter l'erreur ;
    • Sinon : procéder à la suite du traitement.

Affichage d'un message d'erreur

Une fois qu'on est sûr que la variable errno contient une valeur pertinente, il est indispensable de déterminer le traitement à effectuer, et le cas échéant présenter un message tout aussi significatif à l'utilisateur. Rien n'est plus frustrant pour un utilisateur qu'un message aussi abscons que : "fopen("/tmp/tmpab560f9d4", "w") : erreur 28", alors qu'un message écrit sous la forme "impossible de créer le fichier /tmp/tmpab560f9d4: plus de place disponible sur le périphérique" sera plus facilement compris par l'utilisateur.

Énumérer toutes les valeurs possibles d'errno pour leur associer un message peut être relativement pénible, surtout si on doit le faire dans chaque application. Heureusement qu'une fonction toute prête existe :

#include <string.h>
char * strerror(int code);

Cette fonction permet de connaître la signification textuelle d'une valeur de errno. À noter que le code de retour est une chaine statique, dont il est sage de présupposer la durée de vie la plus courte possible. N'essayez pas non plus de modifier le contenu de la chaîne, affichez-la directement ou copiez-la dans une zone temporaire.

Un autre intérêt de passer par cette fonction est que les messages retournés sont adaptés à la localisation du système. Ce qui est loin d'être négligeable, car cela retire au programmeur le souci de traduire les messages d'erreurs dus à la bibliothèque standard dans toutes les langues possibles (chaque implémentation pouvant avoir ses propres codes d'erreur spécifiques, la maintenance d'une telle traduction serait un cauchemar).

Une autre fonction, perror, permet d'envoyer le message correspondant à errno sur le flux des erreurs stderr.

#include <errno.h>
void perror(const char *chaine);

Le paramètre chaine, s'il est différent de NULL, est une chaine de caractère passée par le programmeur et affichée avant le message d'erreur.

Exemple

Pour illustrer cela, voici un exemple, qui tente de convertir une chaine de caractères en un nombre. Le nombre donné, 264, peut ne pas être dans le domaine de valeur du type unsigned long (qui peut se limiter à 232 - 1), suivant l'architecture. Dans ce cas, on utilise errno pour afficher un message d'erreur approprié à l'utilisateur.

#include <stdio.h>  /* puts(), printf(), NULL */
#include <stdlib.h> /* strtoul() */
#include <limits.h> /* ULONG_MAX */
#include <string.h> /* strerror() */
#include <errno.h>  /* errno */
 
static void test(const char *nombre)
{
    unsigned long res;
	
	/* On réinitialise errno */
    errno = 0;
 
    /* Appel de strtoul : conversion d'une chaine en un entier unsigned long*/
    res = strtoul(nombre, NULL, 10);
 
    /* On détecte une erreur de strtoul quand elle renvoie ULONG_MAX _et_
     * que errno est non nul. En effet, si on lui passe en entrée la
     * représentation de ULONG_MAX, la conversion se fait sans erreur
     * et errno reste à 0.
     */
    if (res == ULONG_MAX && errno != 0)
    {
        /* Il y a eu une erreur ! */
        fprintf(stderr, "Impossible de convertir le nombre '%s': %s.\n",
			nombre, strerror(errno));
    }
    else
    {
       printf("La conversion a été effectuée, et la valeur est: %lu\n", res);
    }
}
 
int main(void)
{
    /* 2 puissance 8 : sera toujours accepte */
    test("256");
 
    /* 2^64 - 1 : peut echouer suivant la machine */
    test("18446744073709551615");
    return EXIT_SUCCESS;
}

Si le type unsigned long est codé sur 32 bits, on peut obtenir:

La conversion a été effectuée, et la valeur est: 256
Impossible de convertir le nombre '18446744073709551615': la valeur est en dehors de l'intervalle du type spécifié.

Notons bien qu'il est nécessaire de placer errno à 0 avant d'utiliser strtoul, car elle pourrait contenir une valeur non nulle, même au lancement du programme.


Notes

  1. errno peut être définie par une macro ou un identificateur. N'essayez donc pas de récupérer son adresse.