La saisie sécurisée en C++

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Les matières

  • Saisie
  • C++

Le programme

Introduction du cours

Quand on débute le C++, on découvre avec émerveillement la simplicité des fonctions d'entrée/sortie par rapport au C, plus besoin de spécifier le type de la variable passée en paramètre, plus besoin de mettre des "&" de partout.

Prenons par exemple le code suivant, que l'on rencontre très couramment :

#include <iostream> #include <cstdlib> using namespace std; int main() { unsigned short int age; cout << "Entrez votre age : "; cin >> age; char nom[15]; cout << "Entrez votre nom : "; cin >> nom; cout << endl << "Bonjour " << nom << ", vous avez " << age << " ans." << endl; return EXIT_SUCCESS; }

Les petits malins auront vite fait de remarquer qu'il y a quelques failles dans l'utilisation de ces méthodes. En effet, il suffit d'entrer tout sauf un nombre pour que le programme ne fonctionne plus du tout comme prévu.

Entrez votre age : n'importe Entrez votre nom :  Bonjour ╝[¼t@'@, vous avez 32509 ans

Et là nous nous retrouvons face à non pas un, mais deux problèmes bien ennuyeux, nous n'avons ni l'âge de l'utilisateur, ni son nom :(.

Il ne faut surtout pas utiliser ce code car en plus de ces problèmes, votre programme est susceptible d'être victime d'une attaque par "buffer overflow", problème que nous aborderons en troisième partie de ce tutoriel.

A ce stade, vous avez trois possibilités :

  1. Arrêter de programmer.

  2. Arrêter d'interagir avec l'utilisateur (ce qui limiterait un peu vos programmes).

  3. Utiliser la saisie sécurisée afin que ce genre de désagréments ne puisse pas survenir.

Ce tutoriel ne va exploiter que les objets et fonctions fournies par la librairie standard (qui sont largement suffisantes), si vous ne comprenez pas le fonctionnement d'un code, je vous invite à vous documenter sur cplusplus.com.

Fonctionnement de cin

Bien, si vous en êtes à lire ces lignes c'est que vous avez opté pour la troisième solution, alors attaquons le sujet .

Pour comprendre ce que l'on a obtenu, il faut en savoir plus sur cin .

L'état du flux

cin permet de manipuler l'entrée standard, qui est généralement (mais pas toujours) le clavier, il est ce que l'on appelle un flux d'entrée, quelque chose qui permet de manipuler les informations saisies par l'utilisateur. Lorsque l'on réalise une opération avec (comme par exemple cin >> age; ), il extrait des informations du flux d'entrée (il les sort du flux pour les mettre dans la variable en quelques sortes).
De plus, à chaque fois que l'on demande à cin d'extraire des données du flux, ce dernier enregistre des informations sur son état, à savoir le succès ou l'échec de l'opération d'extraction. Si tout s'est déroulé normalement, l'état sera bon, sinon le code nous indiquera la ou les erreurs survenues.

Voici les différentes méthodes disponibles :

  • good() : retourne true si le flux est valide, false dans le cas contraire.

  • bad() : retourne true s'il y a eu une erreur sur le flux, false dans le cas contraire.

  • fail() : retourne true s'il y a eu une erreur sur le flux ou que la dernière opération a échouée, false dans le cas contraire. Donc si bad() retourne true alors fail() retourne true (mais si bad() retourne false , fail() peut retourner true ).

  • eof() : retourne true si la fin du flux a été rencontrée lors d'une opération d'extraction (si le flux est un fichier, c'est tout a fait normal mais si le flux est l'entrée standard, c'est que l'on ne peut plus rien demander à l'utilisateur).

Ceci est très pratique car on pourra savoir ce qui s'est passé lors de la dernière opération. Mais il faut aussi savoir qu'avant toute extraction, cin fait une vérification sur son état, s'il y a une erreur (donc si good() retourne false ), alors il ne fait rien.
Dans notre exemple, comme il y a eu une erreur lorsqu'on a saisi n'importe alors qu'on nous demandait un nombre, l'état de cin est passé à « invalide » et il ne demande rien lorsque l'on voudrait avoir le nom de l'utilisateur.

Pour solutionner ce problème, il suffit d'utiliser la méthode clear() qui va effacer toutes les erreurs et donc permettre à cin de faire son boulot par la suite. Voici son prototype :

void clear(iostate state = goodbit);

clear() permet de définir l'état d'un flux.

Paramètres :

  • state : l'état à attribuer au flux, par défaut goodbit , soit aucune erreur.

On peut donc l'utiliser dans notre programme ce qui permet de continuer à demander la saisie à l'utilisateur même après une erreur.
Le code devient donc :

#include <iostream> #include <cstdlib> using namespace std; int main() { unsigned short int age; cout << "Entrez votre age : "; cin >> age; cin.clear(); char nom[15]; cout << "Entrez votre nom : "; cin >> nom; cin.clear(); cout << endl << "Bonjour " << nom << ", vous avez " << age << " ans." << endl; return EXIT_SUCCESS; }

Ce code produit la sortie suivante :

Entrez votre âge : n'importe Entrez votre nom :  Bonjour n'importe, vous avez 32509 ans.

Encore une fois nous n'avons pas réussi à récupérer le nom de l'utilisateur, mais nous avons tout de même quelque chose de stocké dans la variable nom , ce que nous avons tapé lorsque le programme nous a demandé notre âge.
Et pour comprendre ce qu'il s'est passé, il nous faut en savoir encore plus sur le fonctionnement de cin .

Une affaire de buffer

Contrairement à ce que l'on pourrait penser, lorsque l'on demande une information via l'objet cin , on ne demande pas à l'utilisateur de saisir quelque chose au clavier. cin regarde dans une zone en mémoire, où est stockée tout ce que l'utilisateur a entré pour notre programme, appelée buffer, qui représente le flux.
Lorsque cin veut extraire une information, il regarde dans le buffer, si celui-ci n'est pas vide, il tente d'extraire les données, sinon il demande à l'utilisateur d'entrer quelque chose au clavier, qui va donc se retrouver dans le buffer, puis il tente d'extraire des données ensuite. Si l'extraction réussit, les informations extraites sont retirées du buffer et la variable est modifiée, sinon, les informations restent dans le buffer et la variable reste inchangée.

D'accord mais qu'est-ce que ça peut bien nous faire de savoir comment ça marche ?

Et bien maintenant que l'on sait tout ça, il faut aller faire un tour par... la documentation :D .

Il est clairement indiqué dans cette dernière qu'après une opération d'extraction (réussie ou non), cin ne supprime pas ce qui reste dans le buffer. Donc le contenu non supprimé sera alors utilisé lors de la prochaine demande d'informations à l'utilisateur.
Dans notre cas, cin ne parvient pas à extraire un nombre du buffer pour le stocker dans la variable age donc il ne la modifie pas. Et lorsque l'on demande le nom de l'utilisateur, il extrait ce que contenait le buffer et le place dans la variable nom .

Pour résoudre ce problème, il nous faut vider le buffer après chaque opération d'extraction. Il existe justement une méthode permettant de vider le buffer, ignore() , dont le prototype est le suivant :

istream &ignore(streamsize n = 1, int delim = EOF);

ignore() permet d'extraire des caractères du buffer (en les ignorant). L'extraction se termine lorsque n caractères ont été extrais ou que le caractère delim est rencontré (qui est aussi extrait). La valeur retournée est une référence vers l'objet qui a utilisé la méthode.

Paramètres :

  • n : le nombre de caractères à extraire. Par défaut 1 seul.

  • delim : le caractère auquel s'arrêter s'il est rencontré. Par défaut c'est le caractère de fin de ligne.

C'est bien beau mais quelle valeur on va donner à n pour être sûr que le buffer soit vidé en entier

Et bien là encore, la librairie standard a tout prévu.
La classe numeric_limits permet, entre autres, de connaitre la valeur maximale que peut prendre une variable d'un type numérique donné via la méthode statique numeric_limits<T>::max() , dont voici le prototype :

T max();

numeric_limits<T>::max() retourne une variable de type T dont la valeur est la valeur la plus grande que puisse prendre une variable de ce type.

La librairie standard fournit aussi la classe streamsize qui représente la taille d'un flux.
Pour vider entièrement notre buffer, on fera donc cin.ignore(numeric_limits<streamsize>::max()); (numeric_limits<streamsize>::max() retourne la taille maximale d'un flux, donc la taille maximale du buffer).

Première approche

Notre programme devient donc :

#include <iostream> #include <cstdlib> #include <limits> // Permet d'avoir accès à la classe numeric_limmits using namespace std; int main(int argc, char **argv) { unsigned short int age; cout << "Entrez votre âge : "; cin >> age; cin.clear(); // Il faut quand même utiliser clear() car ignore() est une méthode d'extraction cin.ignore(numeric_limits<streamsize>::max()); char nom[15]; cout << "Entrez votre nom : "; cin >> nom; cin.clear(); // Il faut quand même utiliser clear() car ignore() est une méthode d'extraction cin.ignore(numeric_limits<streamsize>::max()); cout << endl << "Bonjour " << nom << ", vous avez " << age << " ans." << endl; return EXIT_SUCCESS; }

Voici la sortie obtenue :

Entrez votre âge : n'importe Entrez votre nom : ooprog Bonjour ooprog, vous avez 32509 ans.

Cette fois-ci on demande bien à l'utilisateur de saisir son nom même s'il entre un âge invalide.

Approche plus sécuritaire

On pourrait se demander ce qu'il advient lorsque l'on utilise deux fois consécutivement cette méthode. Et bien essayons !

#include <iostream> #include <cstdlib> #include <limits> using namespace std; int main(int argc, char **argv) { unsigned short int age; cin >> age; /* Premier vidage */ cin.clear(); cin.ignore(numeric_limits<streamsize>::max()); /* Second vidage */ cin.clear(); cin.ignore(numeric_limits<streamsize>::max()); return EXIT_SUCCESS; }

Résultat, le programme se met en pause. Afin d'éviter cela nous avons deux solutions :

  1. Faire attention quant à l'utilisation de la méthode ignore().

  2. Faire en sorte de ne vider le buffer que lorsqu'il n'est pas vide.

Il va de soi que la deuxième solution est la meilleure. Aussi nous faut-il un moyen de savoir si le buffer est vide.
cin ne dispose d'aucune méthode retournant la taille du buffer mais par contre, on peut se déplacer à l'intérieur, via la méthode seekg(), dont voici le prototype :

istream &seekg(streamoff off, ios_base::seekdir dir);

seekg() permet de se positionner dans le buffer. La valeur retournée est une référence vers l'objet qui a utilisé la méthode. De plus si l'on essaie d'aller à une position qui n'existe pas, l'état est modifié et on pourra le savoir en utilisant la méthode fail() .

Paramètres :

  • off : la position à laquelle se rendre à partir de dir .

  • dir : point de départ du déplacement, peut prendre les valeurs ios::beg , ios::cur ou ios::end , pour commencer respectivement à partir du début, de la position courante ou de la fin.

Donc pour savoir si le buffer est vide, on pourra faire :

cin.seekg(0, ios::end); // On se positionne à la fin du buffer if(!cin.fail()) { /* Le buffer n'est pas vide */ } else { /* Le buffer est vide */ }

Donc pour vider notre buffer uniquement s'il est vide, on fait :

cin.clear(); cin.seekg(0, ios::end); if(!cin.fail()) { cin.ignore(numeric_limits<streamsize>::max()); // Le flux a déjà un état valide donc inutile de faire appel à clear() } else { cin.clear(); // Le flux est dans un état invalide donc on le remet en état valide }

Voilà une méthode qui marche parfaitement.
J'en profite pour créer une fonction qui permettra de vider le buffer.

void vider_buffer() { cin.clear(); cin.seekg(0, ios::end); if(!cin.fail()) { cin.ignore(numeric_limits<streamsize>::max()); // Le flux a déjà un état valide donc inutile de faire appel à clear() } else { cin.clear(); // Le flux est dasn un état invalide donc on le remet en état valide } }

Maintenant que nous avons solutionné le second problème, il nous faut passer au premier, à savoir la validation des données entrées...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

La saisie sécurisée en C++

Prix sur demande