« Programmation C/Erreurs » : différence entre les versions
m Mise en page et corrections |
|||
Ligne 1 : | Ligne 1 : | ||
{{Programmation C}} |
{{Programmation C}} |
||
Le langage C fournit un en-tête spécialisé pour la gestion des erreurs : |
Le langage C fournit un en-tête spécialisé pour la gestion des erreurs : <code>errno.h</code>. Cet en-tête déclare notamment une variable globale <code>errno</code>, 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 == |
== Utilisation == |
||
Pour inclure l'en-tête de son fichier source, il faut ajouter la ligne : |
Pour inclure l'en-tête de son fichier source, il faut ajouter la ligne : |
||
#include <errno.h> |
#include <errno.h> |
||
On peut alors utiliser la variable |
On peut alors utiliser la variable <code>errno</code>, de type <code>int</code>, pour traiter les erreurs<ref name="typeErrno"><code>errno</code> peut être définie par une macro ou un identificateur. N'essayez donc pas de récupérer son adresse.</ref>. |
||
Lorsqu'on veut utiliser |
Lorsqu'on veut utiliser <code>errno</code> 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. Cepandant, 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 le pointeur générique <code>NULL</code>, et la variable <code>errno</code> décrit plus en détail la nature de l'erreur. |
||
Une fois qu'on est sûr que la variable <code>errno</code> contient une valeur pertinente, il est indispensable de présenter un message tout aussi significatif à l'utilisateur. Rien n'est plus frustrant qu'un message aussi |
Une fois qu'on est sûr que la variable <code>errno</code> contient une valeur pertinente, il est indispensable de présenter un message tout aussi significatif à l'utilisateur. Rien n'est plus frustrant qu'un message aussi abscons que : "<code>fopen("/tmp/tmpab560f9d4", "w") : erreur 28</code>". |
||
Énumérer toutes les valeurs possibles d'<code>errno</code> peut être relativement pénible, surtout si on doit le faire dans chaque application. Heureusement qu'une fonction toute prête existe : |
Énumérer toutes les valeurs possibles d'<code>errno</code> peut être relativement pénible, surtout si on doit le faire dans chaque application. Heureusement qu'une fonction toute prête existe : |
||
Ligne 17 : | Ligne 17 : | ||
char * strerror(int code); |
char * strerror(int code); |
||
Cette fonction permet de connaître la signification textuelle d'une valeur de <code>errno</code>. À noter que le code de retour est une chaine statique, dont il sage de présupposer la durée de vie la plus courte possible. N' |
Cette fonction permet de connaître la signification textuelle d'une valeur de <code>errno</code>. À 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 |
Un autre intérêt de passer par cette fonction est qu'elle est en général localisée, ce qui est toujours ça de moins à traduire, surtout lorsqu'on voit la pléthore d'erreurs possibles que peuvent générer ne seraient-ce que les fonctions d'entrées/sorties. |
||
== Exemple == |
== Exemple == |
||
Pour illustrer cela, voici un exemple, qui tente de convertir une chaine de caractères en un nombre. Le nombre donné, 2<sup>64</sup>, peut ne pas être dans le domaine de valeur du type |
Pour illustrer cela, voici un exemple, qui tente de convertir une chaine de caractères en un nombre. Le nombre donné, 2<sup>64</sup>, peut ne pas être dans le domaine de valeur du type <code>unsigned long</code> (qui peut se limiter à 2<sup>32</sup> - 1), suivant l'architecture. Dans ce cas, on utilise <code>errno</code> pour afficher un message d'erreur approprié à l'utilisateur. |
||
<pre> |
<pre> |
||
Ligne 35 : | Ligne 35 : | ||
errno = 0; |
errno = 0; |
||
unsigned long res = strtoul(nombre, NULL, 10); |
unsigned long res = strtoul(nombre, NULL, 10); |
||
if (res == ULONG_MAX) |
if (res == ULONG_MAX && errno != 0) |
||
{ |
{ |
||
/* il y a eu une erreur ! */ |
/* il y a eu une erreur ! */ |
||
Ligne 42 : | Ligne 42 : | ||
else |
else |
||
{ |
{ |
||
printf("La conversion a été effectuée, et la valeur est: % |
printf("La conversion a été effectuée, et la valeur est: %lu\n", res); |
||
} |
} |
||
} |
} |
||
Ligne 55 : | Ligne 55 : | ||
return 0; |
return 0; |
||
}</pre> |
}</pre> |
||
Si |
Si le type <code>unsigned long</code> est codé sur 32 bits, on peut obtenir: |
||
La conversion a été effectéee, et la valeur est: 256 |
La conversion a été effectéee, et la valeur est: 256 |
||
Impossible de convertir le nombre '18446744073709551616': la valeur est en dehors de l'intervalle du type spécifié. |
Impossible de convertir le nombre '18446744073709551616': la valeur est en dehors de l'intervalle du type spécifié. |
||
Il est nécessaire de placer |
Il est nécessaire de placer <code>errno</code> à 0 ''avant'' d'utiliser <code>strtoul</code>, 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. C'est une des lourdeurs du modèle de gestion d'erreur du C, même si une telle paranoïa n'est pas toujours nécessaire. |
||
Version du 21 juin 2006 à 14:14
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. Cepandant, 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 le pointeur générique NULL
, et la variable errno
décrit plus en détail la nature de l'erreur.
Une fois qu'on est sûr que la variable errno
contient une valeur pertinente, il est indispensable de présenter un message tout aussi significatif à l'utilisateur. Rien n'est plus frustrant qu'un message aussi abscons que : "fopen("/tmp/tmpab560f9d4", "w") : erreur 28
".
Énumérer toutes les valeurs possibles d'errno
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 qu'elle est en général localisée, ce qui est toujours ça de moins à traduire, surtout lorsqu'on voit la pléthore d'erreurs possibles que peuvent générer ne seraient-ce que les fonctions d'entrées/sorties.
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 */ void teste(const char *nombre) { errno = 0; unsigned long res = strtoul(nombre, NULL, 10); if (res == ULONG_MAX && errno != 0) { /* il y a eu une erreur ! */ printf("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 */ teste("256"); /* 2 puissance 64 : peut echouer suivant la machine */ teste("18446744073709551616"); return 0; }
Si le type unsigned long
est codé sur 32 bits, on peut obtenir:
La conversion a été effectéee, et la valeur est: 256 Impossible de convertir le nombre '18446744073709551616': la valeur est en dehors de l'intervalle du type spécifié.
Il est nécessaire de placer errno
à 0 avant d'utiliser strtoul
, 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. C'est une des lourdeurs du modèle de gestion d'erreur du C, même si une telle paranoïa n'est pas toujours nécessaire.
Notes
- ↑
errno
peut être définie par une macro ou un identificateur. N'essayez donc pas de récupérer son adresse.