Comment empêcher scanf de provoquer un débordement de tampon dans C?

J’utilise ce code:

while ( scanf("%s", buf) == 1 ){ 

Quel serait le meilleur moyen d’empêcher un éventuel débordement de tampon afin de pouvoir transmettre des chaînes de longueur aléatoire?

Je sais que je peux limiter la chaîne d’entrée en appelant par exemple:

 while ( scanf("%20s", buf) == 1 ){ 

Mais je préférerais pouvoir traiter tout ce que les utilisateurs saisissent. Ou cela ne peut-il pas être fait en toute sécurité en utilisant scanf et je devrais utiliser des gadgets?

Dans leur livre The Practice of Programming (qui vaut la peine d’être lu), Kernighan et Pike discutent de ce problème et le résolvent en utilisant snprintf() pour créer la chaîne avec la taille de tampon appropriée pour passer à la famille de fonctions scanf() . . En effet:

 int scanner(const char *data, char *buffer, size_t buflen) { char format[32]; if (buflen == 0) return 0; snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1)); return sscanf(data, format, buffer); } 

Notez que cela limite encore l’entrée à la taille fournie en tant que “tampon”. Si vous avez besoin de plus d’espace, vous devez effectuer une allocation de mémoire ou utiliser une fonction de bibliothèque non standard qui effectue l’allocation de mémoire pour vous.


Notez que la version POSIX 2008 (2013) de la famille de fonctions scanf() prend en charge un modificateur de format m (un caractère d’affectation d’affectation) pour les entrées de chaîne ( %s , %c , %[ ). Au lieu de prendre un argument char * , il prend un argument char ** et alloue l’espace nécessaire à la valeur qu’il lit:

 char *buffer = 0; if (sscanf(data, "%ms", &buffer) == 1) { printf("Ssortingng is: <<%s>>\n", buffer); free(buffer); } 

Si la fonction sscanf() ne parvient pas à satisfaire toutes les spécifications de conversion, toute la mémoire allouée pour les conversions de type %ms est libérée avant le retour de la fonction.

Si vous utilisez gcc, vous pouvez utiliser l’extension GNU, a spécificateur pour que scanf () alloue de la mémoire pour que vous puissiez contenir l’entrée:

 int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; } 

Edit: Comme Jonathan l’a souligné, vous devriez consulter les pages de manuel de scanf car le spécificateur peut être différent ( %m ) et vous devrez peut-être activer certaines définitions lors de la compilation.

La plupart du temps, une combinaison de fgets et de sscanf fait l’affaire. L’autre chose serait d’écrire votre propre parsingur, si l’entrée est bien formatée. Notez également que votre deuxième exemple nécessite un peu de modification pour être utilisé en toute sécurité:

 #define LENGTH 42 #define str(x) # x #define xstr(x) str(x) /* ... */ int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

Ce qui précède supprime le stream d’entrée mais n’inclut pas le caractère de nouvelle ligne ( \n ). Vous devrez append un getchar() pour consumr cela. Vérifiez également si vous avez atteint la fin du stream:

 if (!feof(stdin)) { ... 

et c’est à peu près tout.

L’utilisation directe de scanf(3) et de ses variantes pose un certain nombre de problèmes. En règle générale, les utilisateurs et les cas d’utilisation non interactifs sont définis en termes de lignes d’entrée. Il est rare de voir un cas où, si suffisamment d’objects ne sont pas trouvés, plus de lignes résoudront le problème, mais c’est le mode par défaut pour scanf. (Si un utilisateur ne savait pas entrer un nombre sur la première ligne, une deuxième et une troisième ligne ne seraient probablement pas utiles.)

Au moins si vous fgets(3) le nombre de lignes de saisie dont votre programme aura besoin, vous n’aurez aucun débordement de tampon …

Limiter la longueur de l’entrée est nettement plus facile. Vous pouvez accepter une entrée arbitrairement longue en utilisant une boucle, en lisant un bit à la fois, en réaffectant de l’espace pour la chaîne si nécessaire …

Mais cela demande beaucoup de travail, de sorte que la plupart des programmeurs de C décodent l’entrée à une longueur arbitraire. Je suppose que vous le savez déjà, mais l’utilisation de fgets () ne vous permettra pas d’accepter des quantités arbitraires de texte – vous devrez toujours définir une limite.

Faire une fonction qui alloue la mémoire nécessaire à votre chaîne ne demande pas beaucoup de travail. C’est un petit c-function que j’ai écrit il y a quelque temps, je l’utilise toujours pour lire les chaînes.

Il retournera la chaîne de lecture ou si une erreur de mémoire se produit NULL. Mais sachez que vous devez libérer () votre chaîne et toujours vérifier sa valeur de retour.

 #define BUFFER 32 char *readSsortingng() { char *str = malloc(sizeof(char) * BUFFER), *err; int pos; for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++) { if(pos % BUFFER == BUFFER - 1) { if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL) free(str); str = err; } } if(str != NULL) str[pos] = '\0'; return str; }