Comment lire (en toute sécurité) l’entrée de l’utilisateur avec la fonction getline

Lire des chaînes en C était autrefois une chose très dangereuse à faire. Lors de la lecture des entrées de l’utilisateur, les programmeurs peuvent être tentés d’utiliser le gets
fonction de la bibliothèque standard C. L’utilisation pour gets
est assez simple :
char *gets(char *string);
C’est, gets
lit les données de l’entrée standard et stocke le résultat dans une variable de chaîne. En utilisant gets
renvoie un pointeur sur la chaîne, ou la valeur NULL si rien n’a été lu.
Comme exemple simple, nous pourrions poser une question à l’utilisateur et lire le résultat dans une chaîne :
#include <stdio.h>
#include <string.h>int
main()
{
char city[10]; // Such as "Chicago"// this is bad .. please don't use gets
puts("Where do you live?");
gets(city);printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
Entrer une valeur relativement courte avec le programme ci-dessus fonctionne assez bien :
Where do you live?
Chicago
<Chicago> is length 7
Cependant, le gets
La fonction est très simple et lira naïvement les données jusqu’à ce qu’elle pense que l’utilisateur a terminé. Mais gets
ne vérifie pas que la chaîne est suffisamment longue pour contenir l’entrée de l’utilisateur. La saisie d’une valeur très longue entraînera gets
pour stocker plus de données que la variable de chaîne ne peut en contenir, ce qui entraîne l’écrasement d’autres parties de la mémoire.
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
Segmentation fault (core dumped)
Au mieux, écraser des parties de la mémoire casse simplement le programme. Au pire, cela introduit un bogue de sécurité critique où un mauvais utilisateur peut insérer des données arbitraires dans la mémoire de l’ordinateur via votre programme.
C’est pourquoi le gets
La fonction est dangereuse à utiliser dans un programme. En utilisant gets
, vous n’avez aucun contrôle sur la quantité de données que votre programme tente de lire de l’utilisateur. Cela conduit souvent à un débordement de tampon.
Le fgets
La fonction a toujours été la méthode recommandée pour lire les chaînes en toute sécurité. Cette version de gets
fournit un contrôle de sécurité en ne lisant qu’un certain nombre de caractères, passés en argument de la fonction :
char *fgets(char *string, int size, FILE *stream);
Le fgets
la fonction lit à partir du pointeur de fichier et stocke les données dans une variable de chaîne, mais uniquement jusqu’à la longueur indiquée par size
. Nous pouvons tester cela en mettant à jour notre exemple de programme pour utiliser fgets
à la place de gets
:
#include <stdio.h>#include <string.h>
int
main()
{
char city[10]; // Such as “Chicago”
// fgets is better but not perfect
puts(“Where do you live?”);
fgets(city, 10, stdin);
printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
Si vous compilez et exécutez ce programme, vous pouvez entrer un nom de ville arbitrairement long à l’invite. Cependant, le programme ne lira que suffisamment de données pour tenir dans une variable de chaîne de size
=10. Et parce que C ajoute un caractère nul (‘\0’) à la fin des chaînes, cela signifiefgets
ne lira que 9 caractères dans la chaîne :
Where do you live?
Minneapolis
<Minneapol> is length 9
Bien que cela soit certainement plus sûr que d’utiliser fgets
pour lire l’entrée de l’utilisateur, il le fait au prix de “couper” l’entrée de votre utilisateur si elle est trop longue.
Une solution plus flexible pour lire des données longues consiste à permettre à la fonction de lecture de chaîne d’allouer plus de mémoire à la chaîne, si l’utilisateur a entré plus de données que la variable ne peut en contenir. En redimensionnant la variable de chaîne selon les besoins, le programme dispose toujours de suffisamment d’espace pour stocker l’entrée de l’utilisateur.
Le getline
fonction fait exactement cela. Cette fonction lit l’entrée d’un flux d’entrée, tel que le clavier ou un fichier, et stocke les données dans une variable de chaîne. Mais contrairement à fgets
et gets
, getline
redimensionne la chaîne avec realloc
pour s’assurer qu’il y a suffisamment de mémoire pour stocker l’entrée complète.
ssize_t getline(char **pstring, size_t *size, FILE *stream);
Le getline
est en fait un wrapper à une fonction similaire appelée getdelim
qui lit les données jusqu’à un caractère délimiteur spécial. Dans ce cas, getline
utilise une nouvelle ligne (‘\n’) comme délimiteur, car lors de la lecture d’une entrée utilisateur à partir du clavier ou d’un fichier, les lignes de données sont séparées par un caractère de nouvelle ligne.
Le résultat est une méthode beaucoup plus sûre pour lire des données arbitraires, une ligne à la fois. Utiliser getline
, définissez un pointeur de chaîne et définissez-le sur NULL pour indiquer qu’aucune mémoire n’a encore été réservée. Définissez également une variable “taille de chaîne” de type size_t
et lui donner une valeur nulle. Quand vous appelez getline
, vous utiliserez des pointeurs vers les variables de chaîne et de taille de chaîne et indiquerez où lire les données. Pour un exemple de programme, nous pouvons lire sur l’entrée standard :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int
main()
{
char *string = NULL;
size_t size = 0;
ssize_t chars_read;// read a long string with getline
puts("Enter a really long string:");
chars_read = getline(&string, &size, stdin);
printf("getline returned %ld\n", chars_read);// check for errors
if (chars_read < 0) {
puts("couldn't read the input");
free(string);
return 1;
}// print the string
printf("<%s> is length %ld\n", string, strlen(string));
// free the memory used by string
free(string);
return 0;
}
Comme le getline
lit les données, il réallouera automatiquement plus de mémoire pour la variable de chaîne selon les besoins. Lorsque la fonction a lu toutes les données d’une ligne, elle met à jour la taille de la chaîne via le pointeur et renvoie le nombre de caractères lus, y compris le délimiteur.
Enter a really long string:
Supercalifragilisticexpialidocious
getline returned 35
<Supercalifragilisticexpialidocious
> is length 35
Notez que la chaîne inclut le caractère délimiteur. Pour getline
, le délimiteur est le retour à la ligne, c’est pourquoi la sortie comporte un saut de ligne. Si vous ne voulez pas de délimiteur dans votre valeur de chaîne, vous pouvez utiliser une autre fonction pour remplacer le délimiteur par un caractère nul dans la chaîne.
Avec getline
, les programmeurs peuvent éviter en toute sécurité l’un des pièges courants de la programmation en C. Vous ne pouvez jamais savoir quelles données votre utilisateur pourrait essayer d’entrer, c’est pourquoi l’utilisation de gets
est dangereux, et fgets
est gênant. Plutôt, getline
offre un moyen plus flexible de lire les données utilisateur dans votre programme sans casser le système.