Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Non-Officiels > Programmation > C > Réaliser des saisies sécurisées grâce à fgets > Lecture du tutoriel

Réaliser des saisies sécurisées grâce à fgets

Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Avatar
Auteur : Kr00pS
Note : 18 / 20 (18 votes)
Visualisations : 23 046

Plus d'informations Plus d'informations
Vous avez fait un programme que vous jugez utile (pour quelqu'un d'autre que vous), mais vous utilisez encore scanf(), et vous savez qu'il plante souvent à cause de cela ? J'ai la solution. Nous allons d'abord étudier le comportement de cette fonction puis nous verrons la solution à ces problèmes.
Sommaire du tutoriel :
Icône du chapitre

Étude de la fonction scanf()

Dans la plupart des cours de C, scanf() est une des premières fonctions présentée aux débutants. Malgré son apparence d'utilisation "facile", elle est dur à maîtriser et demande une surcharge de code (pour résoudre tous les problèmes qu'elle cause par son utilisation).

Nous allons donc, étudier cette fonction, et voir quels problèmes se posent lors de son utilisation.

Les attributs de conversion



scanf() demande une chaîne à un utilisateur et s'occupe de le convertir grâce aux attributs de conversion qui sont spécifiés par le programmeur.

Généralement, on utilise ces différents attributs :


Il existe beaucoup de ces attributs, je fournis ceux qui sont le plus utilisés, vous pouvez trouver une liste plus complète en tapant man scanf dans votre moteur de recherche.

Les problèmes



Oui, il existe des problèmes en relation avec une mauvaise utilisation de scanf(), nous allons traiter des deux problèmes les plus courants dans l'utilisation de scanf().

Entrer des caractères inattendus dans un appel à scanf()


Nous allons prendre un exemple très simple, vous voulez que l'utilisateur entre un nombre et vous voulez le récupérer dans une variable.

Quoi de plus simple avec notre ami scanf() !
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

int main (void)
{
    int nombre;
    
    printf("Entre un nombre s'il-te-plait : \n");
    scanf("%d", &nombre);
    printf("Merci ! le nombre que vous avez tape est %d", nombre);
    
    return 0;
}

Ce qui donne après compilation et exécution :
Code : Console
Entre un nombre s'il-te-plait :

50

Merci ! Le nombre que vous avez tape est 50

Jusque-là, rien d'anormal.

Maintenant, relançons notre programme mais cette fois-ci en entrant quelques caractères :
Code : Console
Entre un nombre s'il-te-plait : dgsgcbv

[...]


On entre dans une boucle infinie, ce problème est très embêtant.

Mais il ne faut pas en vouloir à scanf, elle fait son job. Le problème, c'est qu'elle a été conçue pour avoir des saisies dîtes "formatées" (d'où le nom : scanformatted) c'est-à-dire, une saisie qui respecte caractère pour caractère ce que vous avez mis dans son appel (donc pour une saisie de chiffre, elle ne cherche pas à savoir si c'est un nombre ou pas, elle le stocke quand même dans votre int).

Entrer une chaîne de caractères



Vous souhaitez que l'utilisateur entre une chaîne dans votre programme ? Ok, on utilise scanf.
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

int main (void)
{
    char chaine[20];

    printf("Ecris une phrase s'il-te-plait : \n");
    scanf("%s", chaine);
    printf("Tu as entre : '%s'", chaine);

    return 0;
}


On teste notre programme :
Code : Console
Ecris une phrase s'il-te-plait :

Salut ça va ?

Tu as entre : 'Salut'


o_O Oui, scanf n'a pris que le premier mot de notre phrase !

Pourquoi ? C'est très simple, avec l'utilisation du format %s, scanf supprime tous les espaces qu'il juge inutiles (avant ou après le premier mot de la chaîne), le comportement est facile à deviner, scanf ne prend que le premier mot (avant le premier espace) et le stocke dans la chaîne.

Où sont les caractères restants ? Dans le flux d'entrée standard, stdin.

Et cela pose problème, effectivement, lors du prochain appel à scanf, les caractères non extraits (qui ne sont pas dans la chaîne) se trouvent dans stdin et vont directement se stocker dans la chaîne sans que l'utilisateur n'ai rien à demander.

Pour "manger" les caractères restants, on va tout simplement les lire, car ils sont non lus.

Pour cela, on va utiliser la macro très utilisée : getchar() (ou la fonction fgetc(stdin) ).

On va coder ça dans une fonction :
Code : C
1
2
3
4
5
6
7
static void purger(void)
{
    int c;

    while ((c = getchar()) != '\n' && c != EOF)
    {}
}


On va tester ça avec un petit programme :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

static void purger(void)
{
    int c;

    while ((c = getchar()) != '\n' && c != EOF)
    {}
}

int main (void)
{
    char chaine[20], chaine2[20];

    printf("Ecris une phrase s'il-te-plait : \n");
    scanf("%s", chaine);
    printf("Tu as entre : '%s'", chaine);
    purger();
    printf("\nEcris une autre phrase s'il-te-plait : \n");
    scanf("%s", chaine2);
    printf("\nTu as entre : '%s'", chaine2);
    purger();

    return 0;
}

Et l'exécution :
Code : Console
Ecris une phrase s'il-te-plait :

Salut ça va ?

Tu as entre : 'Salut'

Ecris une autre phrase s'il-te-plait :

Bien et toi ?



Tu as entre : 'Bien'

On voit que ce problème est résolu !

Vous avez vu le gros des problèmes. Vous voulez résoudre ça ? C'est parti pour la seconde partie !

Captures sécurisées grâce à fgets() et une fonction de conversion !

Nous entrons dans la partie la plus intéressante : La résolution des problèmes causés par scanf().

Mais nous allons rencontrer de nouveaux problèmes, nous allons les prévoir puis les résoudre.

Comportement



Eh oui ! Encore un peu de théorie pour bien comprendre ce que l'on fait (le C est un langage de bas niveau donc il est préférable de bien connaître le fonctionnement des fonctions standards). Cette fois, ce ne sera pas long. Vous allez voir !

Le prototype de fgets est le suivant :
Code : C
1
char *fgets (char *str, int size, FILE* stream);

fgets lit size-1 caractères dans stream et place le tout dans str (avec le caractère terminal '\0').

Le pointeur sur FILE peut être un fichier mais aussi l'entrée standard (c'est le flux stdin, il contient les caractères saisis).

La fonction extrait le '\n' (retour chariot qui est la validation de la frappe) dans le flux quand elle le peut (selon l'espace libre dans la chaîne à ce moment), si ce caractère est présent à la fin de la chaîne, on sait que la saisie est réussie.

Il ne faut pas confondre les fonctions fgets et gets. Cette dernière si on l'utilise, augmente énormément le risque de buffer overflow (entrer plus de caractères dans une chaîne que sa limite).


Problèmes



Voilà, vous avez les connaissances pour utiliser fgets() correctement. On va mettre cela en pratique :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

int main (void)
{
    char chaine[20];

    printf("Tapez une phrase : \n");
    fgets(chaine, sizeof chaine, stdin);
    printf("Vous avez tape : '%s'", chaine);

    return 0;
}


Cela donne un résultat satisfaisant :
Code : Console
Tapez une phrase :

Salut ça va ?

Vous avez tape : 'Salut ça va ?

'


Premier problème : On voit qu'il y a bien un retour à la ligne à la fin de la chaîne.

On va le localiser grâce à une fonction standard : strchr() (elle est dans l'header string.h).

On code cela :
Code : C
1
2
3
4
5
6
7
8
9
static void search(char *chaine)
{
    char *p = strchr(chaine, '\n');

    if (p)
    {
        *p = 0;
    }
}


Le code est très simple : on cherche le caractère \n et on le supprime (grâce au pointeur, on le déférence).

Il faut inclure <string.h> pour la fonction strchr() !


Un petit test :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

static void search(char *chaine)
{
    char *p = strchr(chaine, '\n');

    if (p)
    {
        *p = 0;
    }
}

int main (void)
{
    char chaine[20];

    printf("Tapez une phrase : \n");
    fgets(chaine, sizeof chaine, stdin);
    search(chaine);
    printf("Vous avez tape : '%s'", chaine);

    return 0;
}

Code : Console
Tapez une phrase :

Salut ça va ?

Vous avez tape : 'Salut ça va ?'


On voit que notre programme marche correctement.

Cependant, nous avons un deuxième problème : Si la dernière saisie ne s'est pas déroulée correctement (ou on a dépassé le nombre de caractères max spécifié en deuxième argument) alors la seconde saisie va contenir les caractères qui n'ont pas été lus et ainsi de suite, jusqu'à ce que le flux stdin devienne vide.

Nous avons déjà traité ce problème donc on reprend notre fonction :
Code : C
1
2
3
4
5
6
7
static void purger(void)
{
    int c;

    while ((c = getchar()) != '\n' && c != EOF)
    {}
}

Un petit test :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <string.h>

static void purger(void)
{
    int c;

    while ((c = getchar()) != '\n' && c != EOF)
    {}
}

static void clean (char *chaine)
{
    char *p = strchr(chaine, '\n');
    int c;

    if (p)
    {
        *p = 0;
    }

    else
    {
        purger();
    }
}

int main (void)
{
    char chaine[20], chaine2[20];

    printf("Tapez une phrase : \n");
    fgets(chaine, sizeof chaine, stdin);
    clean(chaine);
    printf("Vous avez tape : '%s'", chaine);

    printf("Tapez une phrase : \n");
    fgets(chaine2, sizeof chaine2, stdin);
    clean(chaine2);
    printf("Vous avez tape : '%s'", chaine2);

    return 0;
}


Code : Console
Tapez une phrase :

123456789123456789123456789

Vous avez tape : '1234567891234567891'

Tapez une phrase :

123456789123456789123456789

Vous avez tape : '1234567891234567891'

On voit que tout marche correctement !

Voilà, mais néanmoins, il reste encore un problème : Toutes nos saisies avec fgets sont pour le moment des chaînes, elles sont toutes stockées dans des chaînes de caractères.

Nous allons traiter la conversion de chaîne vers un entier ou un réel.

Conversion de chaîne à entier ou réel



Vous avez plusieurs solutions :


sscanf

sscanf fonctionne exactement de la même manière que scanf à l'exception qu'il utilise une chaîne pour convertir ses éléments à la place du flux stdin.

Voici un code commenté pour bien comprendre le fonctionnement :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <string.h> /* Pour la fonction clean (strchr). */

/* Pour supprimer le '\n' de fgets et nettoyer le buffer stdin (entrée
   standard). */
static void clean(const char *buffer);
static void purger(void);

int main(void)
{
    /* La chaine qui va contenir les caracteres. */
    char chaine[20];

    /* Le resultat recherche : un nombre. */
    int nombre;

    /* Un booleen pour savoir si la chaine est valide. */
    int ret;

    /* Tant que la chaine est invalide, elle ne comporte pas ce que
       l'on veut, on continue. */
    do
    {
        /* On demande a l'utilisateur d'entrer son nombre. */
        printf("Entrez votre nombre : ");
        fgets(chaine, sizeof chaine, stdin);

        /* On nettoie notre chaine et le buffer stdin. */
        clean(chaine);

        /* On convertie notre chaine en entier. */
        ret = sscanf(chaine, "%d", &nombre);
    } while (ret != 1);

    /* On voit que notre nombre est bien dans le int. */
    printf("Nombre: %d", nombre);

    return 0;
}

static void purger(void)
{
    int c;

    while ((c = getchar()) != '\n' && c != EOF)
    {}
}

static void clean (char *chaine)
{
    char *p = strchr(chaine, '\n');
    int c;

    if (p)
    {
        *p = 0;
    }

    else
    {
        purger();
    }
}


strtol

Cette fonction sert à convertir une chaîne en entier.

Code : C
1
long int strtol(const char *nptr, char **endptr, int base);

Le premier argument est la chaîne que l'on veut convertir, le deuxième n'est pas important, et le troisième est la base (généralement on utilise la base 10).

Si vous avez bien suivi, vous devriez être capable de convertir une chaîne avec cela.

strtod

Conversion en réel cette fois-ci.

Code : C
1
double strtod(const char *nptr, char **endptr);


Le deuxième argument n'est toujours pas important et le premier est la chaîne.

Maintenant, vous connaissez le nécessaire pour faire une saisie de données contrôlée.

Bonne programmation !

(Ce document est sous licence CC - Commons Deed)
Retour en haut Retour en haut


Créé : le 01/09/2006 à 15:00:23
Modifié : le 22/08/2008 à 16:08:30
Avancement : 100%
Licence : Copie non autorisée

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 382 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.266s (0.2535s)