Licence CC BY-SA

La gestion d'erreurs (1)

Dans les chapitres précédents, nous vous avons présenté des exemples simplifiés afin de vous familiariser avec le langage. Aussi, nous avons pris soin de ne pas effectuer de vérifications quant à d’éventuelles rencontres d’erreurs.

Mais à présent, c’est fini ! Vous disposez désormais d’un bagage suffisant pour affronter la dure réalité d’un programmeur : des fois, il y a des trucs qui foirent et il est nécessaire de le prévoir. Nous allons voir comment dans ce chapitre.

Détection d'erreurs

La première chose à faire pour gérer d’éventuelles erreurs lors de l’exécution, c’est avant tout de les détecter. Par exemple, quand vous exécutez une fonction et qu’une erreur a lieu lors de son exécution, celle-ci doit vous prévenir d’une manière ou d’une autre. Et elle peut le faire de deux manières différentes.

Valeurs de retour

Nous l’avons vu dans les chapitres précédents : certaines fonctions, comme scanf(), retournent un nombre (souvent un entier) alors qu’elles ne calculent pas un résultat comme la fonction pow() par exemple. Vous savez dans le cas de scanf() que cette valeur représente le nombre de conversions réussies, cependant cela va plus loin que cela : cette valeur vous signifie si l’exécution de la fonction s’est bien déroulée.

Scanf

En fait, la fonction scanf() retourne le nombre de conversions réussies ou un nombre inférieur si elles n’ont pas toutes été réalisées ou, enfin, un nombre négatif en cas d’erreur.

Ainsi, si nous souhaitons récupérer deux entiers et être certains que scanf() les a récupérés, nous pouvons utiliser le code suivant.

#include <stdio.h>


int main(void)
{
    int x;
    int y;

    printf("Entrez deux nombres : ");

    if (scanf("%d %d", &x, &y) == 2)
        printf("Vous avez entre : %d et %d\n", x, y);

    return 0;
}
Résultat
Entrez deux nombres : 1 2
Vous avez entre : 1 et 2

Entrez deux nombres : 1 a

Comme vous pouvez le constater, le programme n’exécute pas l’affichage des nombres dans le dernier cas, car scanf() n’a pas réussi à réaliser deux conversions.

Main

Maintenant que vous savez cela, regardez bien votre fonction main().

int main(void)
{
    return 0;
}

Vous ne voyez rien qui vous interpelle ? :)

Oui, vous avez bien vu, elle retourne un entier qui, comme pour scanf(), sert à indiquer la présence d’erreur. En fait, il y a deux valeurs possibles :

  • EXIT_SUCCESS (ou zéro, cela revient au même), qui indique que tout s’est bien passé ; et
  • EXIT_FAILURE, qui indique un échec du programme.

Ces deux constantes sont définies dans l’en-tête <stdlib.h>.

Les autres fonctions

Sachez que scanf(), printf() et main() ne sont pas les seules fonctions qui retournent des entiers, en fait quasiment toutes les fonctions de la bibliothèque standard le font.

Ok, mais je fais comment pour savoir ce que retourne une fonction ?

À l’aide de la documentation. Vous disposez de la norme (enfin, du brouillon de celle-ci) qui reste la référence ultime, sinon vous pouvez également utiliser un moteur de recherche avec la requête man nom_de_fonction afin d’obtenir les informations dont vous avez besoin.

Si vous êtes anglophobe, une traduction française de diverses descriptions est disponible à cette adresse, vous les trouverez à la section trois.

Variable globale errno

Le retour des fonctions est un vecteur très pratique pour signaler une erreur. Cependant, il n’est pas toujours utilisable. En effet, nous avons vu lors du second TP la fonction mathématique pow(). Or, cette dernière utilise déjà son retour pour transmettre le résultat d’une opération. Comment faire dès lors pour signaler un problème ?

Une première idée serait d’utiliser une valeur particulière, comme zéro par exemple. Toutefois, ce n’est pas satisfaisant puisque, dans le cas de la fonction pow(), elle peut parfaitement retourner zéro lors d’un fonctionnement normal. Que faire alors ?

Dans une telle situation, il ne reste qu’une seule solution : utiliser un autre canal, en l’occurrence une variable globale. La bibliothèque standard fournit une variable globale nommée errno (elle est déclarée dans l’en-tête <errno.h>) qui permet à différentes fonctions d’indiquer une erreur en modifiant la valeur de celle-ci.

Une valeur de zéro indique qu’aucune erreur n’est survenue.

Les fonctions mathématiques recourent abondamment à cette fonction. Prenons l’exemple suivant.

#include <errno.h>
#include <math.h>
#include <stdio.h>


int main(void)
{
    errno = 0;
    double x = pow(-1, 0.5);

    if (errno == 0)
        printf("x = %f\n", x);

    return 0;
}

L’appel revient à demander le résultat de l’expression 112-1^\frac{1}{2}, autrement dit, de cette expression : 1\sqrt{-1}, ce qui est impossible dans l’ensemble des réels. Aussi, la fonction pow() modifie la variable errno pour vous signifier qu’elle n’a pas pu calculer l’expression demandée.

Une petite précision concernant ce code et la variable errno : celle-ci doit toujours être mise à zéro avant d’appeler une fonction qui est susceptible de la modifier, ceci afin de vous assurer qu’elle ne contient pas la valeur qu’une autre fonction lui a assignée auparavant. Imaginez que vous ayez précédemment appelé la fonction pow() et que cette dernière a échoué, si vous l’appelez à nouveau, la valeur de errno sera toujours celle assignée lors de l’appel précédent.

Notez que la norme ne prévoit en fait que trois valeurs d’erreurs possibles pour errno : EDOM (pour le cas où le résultat d’une fonction mathématique est impossible), ERANGE (en cas de dépassement de capacité, nous y reviendrons plus tard) et EILSEQ (pour les erreurs de conversions, nous en reparlerons plus tard également). Ces trois constantes sont définies dans l’en-tête <errno.h>. Toutefois, il est fort probable que votre système supporte et définisse davantage de valeurs.

Prévenir l'utilisateur

Savoir qu’une erreur s’est produite, c’est bien, le signaler à l’utilisateur, c’est mieux. Ne laissez pas votre utilisateur dans le vide, s’il se passe quelque chose, dites le lui.

#include <stdio.h>


int main(void)
{
    int x;
    int y;

    printf("Entrez deux nombres : ");

    if (scanf("%d %d", &x, &y) == 2)
        printf("Vous avez entré : %d et %d\n", x, y);
    else
        printf("Vous devez saisir deux nombres !\n");

    return 0;
}
Résultat
Entrez deux nombres : a b
Vous devez saisir deux nombres !

Entrez deux nombres : 1 2
Vous avez entre : 1 et 2

Simple, mais tellement plus agréable. ;)

Un exemple d'utilisation des valeurs de retour

Maintenant que vous savez tout cela, il vous est possible de modifier le code utilisant la fonction scanf() pour vérifier si celle-ci a réussi et, si ce n’est pas le cas, préciser à l’utilisateur qu’une erreur est survenue et quitter la fonction main() en retournant la valeur EXIT_FAILURE.

#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    int x;
    int y;

    printf("Entrez deux nombres : ");

    if (scanf("%d %d", &x, &y) != 2)
    {
        printf("Vous devez saisir deux nombres !\n");
        return EXIT_FAILURE;
    }

    printf("Vous avez entré : %d et %d\n", x, y);
    return 0;
}

Ceci nous permet de réduire un peu la taille de notre code en éliminant directement les cas d’erreurs.


Bien, vous voilà à présent fin prêt pour la deuxième partie du cours et ses vrais exemples. Plus de pitié donc : gare à vos fesses si vous ne vérifiez pas le comportement des fonctions que vous appelez ! :pirate:

En résumé
  1. Certaines fonctions comme scanf() ou main() utilisent leur valeur de retour pour signaler la survenance d’erreurs ;
  2. Dans le cas où la valeur de retour ne peut pas être utilisée (comme pour la fonction mathématique pow()), les fonctions de la bibliothèque standard utilisent la variable globale errno ;
  3. Tâchez de prévenir l’utilisateur à l’aide d’un message explicite dans le cas où vous détectez une erreur.