De l'utilité du "fflush"

De l'utilité du "fflush" - C - Programmation

Marsh Posté le 08-08-2004 à 10:55:25    

Bonjour,
voici un bout de code qui devrait fonctionner mais qui a un soucis

Code :
  1. #include <stdio.h>
  2. #define SIZE (20)
  3. int main()
  4. {
  5. char chaine[SIZE + 1];
  6. long nb=0;
  7. do {
  8.  printf("Entre une chaîne: " ); fflush(stdout);
  9.  fgets(chaine, SIZE + 1, stdin);
  10.  printf("Entre un nombre: " ); fflush(stdout);
  11.     scanf("%ld", &nb);
  12.  printf("Ta chaine est [%s] et ton nombre est [%ld]\n", chaine, nb);
  13. }while (strncmp(chaine, "EOT", 3) != 0);
  14. return(0);
  15. }


 
Le soucis est que, une fois sur 2, la saisie de la chaîne ne se fait pas. Ou plutôt, le "fgets()" récupère un "\n" résiduel du buffer d'entrée et donc ne s'arrète pas pour attendre ma frappe.
On peut, bien sûr, mettre un "fflush(stdin)" avant le "fgets()", mais comme l'a si aimablement fais remarquer "Taz", la fonction "fflush" n'a de sens que pour les buffers de sortie (man fflush).
 
On peux contourner le problème en rajoutant la macro suivante

Code :
  1. #define FFLUSH_STDIN (\
  2. (stdin)->_IO_read_ptr=NULL,\
  3. (stdin)->_IO_read_end=NULL,\
  4. (stdin)->_IO_read_base=NULL,\
  5. (stdin)->_IO_write_ptr=NULL,\
  6. (stdin)->_IO_write_end=NULL,\
  7. (stdin)->_IO_write_base=NULL)


... et en l'appelant avant chaque "fgets()" et "scanf()"
 
Chose curieuse: Si la boucle ne contient que du "fgets", tout va bien. C'est quand on y rajoute la saisie numérique "scanf" que ça commence à foirer.
Peut-être que l'omnipotent "Taz" voudra bien, avec sa gentillesse et sa politesse légendaires, descendre de son Olympe et venir aimablement nous indiquer, à nous pauvres mortels, le pourquoi du comment...


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 10:55:25   

Reply

Marsh Posté le 08-08-2004 à 11:24:09    

fgets récupère l' '\n' ? y a pas de problème, strchr + '\0' et y a plus de souci.
 
quelque chose ne te plait pas dans ton entrée ? surtout, ne la purge pas comme un porc, il peut y avoir plusieurs ko de données. la solution ? le mieux, c'est de sauter tous les espaces (ou \n selon) jusqu'a du texte ou EOF.
 
cela dit ton code est incorrect, si EOF est atteint, boucle infinie. et je ne parle pas d'une mauvaise saisie.
 
la règle générale veut qu'on n'utilise pas scanf. on lui préfère fgets + sscanf qui permet de sécuriser le tout
 
 
quand à ton SIZE, utilise plutot l'opérateur SIZEOF

Reply

Marsh Posté le 08-08-2004 à 11:49:04    

Ouahou ! Une réponse sympa :) ... sauf que je ne comprends pas tout, et que je ne suis pas d'accord avec tout ce que je comprends...
 

Taz a écrit :

fgets récupère l' '\n' ? y a pas de problème, strchr + '\0' et y a plus de souci.


Si je lance un "strchr('\n')", cela me renverra l'adresse de l'octet contenant un "\n". Si je rajoute à cette adresse la valeur '\0', je ne vois pas trop ce que cela va faire... (je ne peux même pas tester, je n'utilise Linux qu'au bureau !!!)
 

Taz a écrit :

quelque chose ne te plait pas dans ton entrée ? surtout, ne la purge pas comme un porc, il peut y avoir plusieurs ko de données. la solution ? le mieux, c'est de sauter tous les espaces (ou \n selon) jusqu'a du texte ou EOF.


Contrairement aux idées reçues, les porcs sont des animaux naturellement très propres (et accessoirement très bons). C'est notre culture agraire européenne qui les plonge dans la fange et leur donne cette image peu reluisante. Ceci dit, je ne comprends pas pourquoi le "scanf" de la boucle précédente me laisse un '\n' résiduel. Je le purge à ma façon (et toi à la tienne), mais j'aimerais mieux soigner le mal que ses symptômes...
 

Taz a écrit :

cela dit ton code est incorrect, si EOF est atteint, boucle infinie. et je ne parle pas d'une mauvaise saisie.


C'était un exemple rapide pour illustrer une boucle. Mon premier jet a été d'écrire "while(1)" mais j'ai préféré, lors de mes essais, rajouter une condition de fin. Evidemment, si je veux faire une boucle "blindée anti-problèmes", je la ferai de façon différente...
 

Taz a écrit :

la règle générale veut qu'on n'utilise pas scanf. on lui préfère fgets + sscanf qui permet de sécuriser le tout


Cela revient à soigner les symptômes du mal => quel est le soucis inhérent de "scanf" ???
 

Taz a écrit :

quand à ton SIZE, utilise plutot l'opérateur SIZEOF


Je considère que "SIZE" est plus propre. Si ma chaine devient, plus tard, un pointeur (parce que je la récupère dans une fonction par exemple ou bien que je l'alloue dynamiquement avec "malloc()" ), sizeof(chaine) renverra "4" alors que "SIZE" sera toujours vu comme la taille de ma chaine. Le préprocesseur a quand-même une certaine utilité pour faciliter la programmation et surtout la maintenance....


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 12:29:27    

1) pour l'écrasage de '\n' final que te donne fgets, ben c'est tévite d'avoir ça dans ton programme
[chaine
]
 
2) purger c'est tout sauf sain. Faut voir plus loin que le bout de ton nez, le buffer de stdin ne contient pas que un \n. c'est encore plus vrai si tu travaille avec un fichier. une manière saine de purger, et de fgetc'er jusqu'à (nouvelle ligne, caractère, EOF). Tout le reste c'est bidon et dangereux.
 
4)scanf n'a aucun de problème. quand tu lui demande de lire un int, je vois pas pourquoi il lirait les caractères suivants. tu ne maitrises pas ce comportement ? ce n'est pas pour autant une raison de bousiller stdin à chaque fois que tu as un '\n' qui te déplait. lit le, déplace toi jusqu'à la prochaine donnée qui t'intéresse, mais ne fait rien d'autre. tu ne ferais jamais une purge brutal avec un fichier disque. stdin c'est un fichier. si tu parsais un fichier, tu ne t'amuserais jamais à balarder toutes les lignes suivantes, à perdre ta position dans le fichier, etc. fais de meme avec stdin.
 
5) tableau -> fgets. pointeur -> getline.
rien d'autre.
le préprocesseur est ton ennemi. sizeof est un opérateur soumis au control du compilateur. il faut l'utiliser le plus possible et se débarasser des nombres magiques

Reply

Marsh Posté le 08-08-2004 à 13:20:31    

Je ne suis pas obtu, j'essaierai demain de gérer le clavier à ta façon, ne serait-ce que pour voir ce qu'il contient après que j'ai saisi mon nombre. Mais pour moi, tout ce qu'il y avait dans <I>stdin</I> après le nombre entré était inutile => fflush (évidemment, il faudrait que je vois avec un "pipe" ou "/dev/random" ce que cela donne...)

Taz a écrit :

le préprocesseur est ton ennemi.


Hum... j'arrive bien à maîtriser mon ennemi. Le seul danger que je lui connaisse pour lequel je n'ai pas de solution est "l'effet de bord". Mais j'évite d'appeler des macro inconnues avec "++". En fait, j'évite systématiquement "++" lorsque j'appelle une fonction...


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 13:28:30    

« après le nombre entré était inutile => fflush (évidemment, il faudrait que je vois avec un "pipe" ou "/dev/random" ce que cela donne...) »
 
 
ben non. tu te trompes. ça ne t'ai pas inutile. Ça va te servir la maintenant tout de suite dès la prochaine itération de ta boucle. flusher stdin, c'est le foutre en l'air et au final avoir une application au comportement imprévisible.
 

Code :
  1. #include <stdio.h>
  2. #define FFLUSH_STDIN do{\
  3. (stdin)->_IO_read_ptr=NULL;\
  4. (stdin)->_IO_read_end=NULL;\
  5. (stdin)->_IO_read_base=NULL;\
  6. (stdin)->_IO_write_ptr=NULL;\
  7. (stdin)->_IO_write_end=NULL;\
  8. (stdin)->_IO_write_base=NULL;}while(0)
  9. int main()
  10. {
  11.   int i, j;
  12.   scanf("%d", &i);
  13.   FFLUSH_STDIN;
  14.   scanf("%d", &j);
  15.   printf("%d %d\n", i, j);
  16.   return 0;
  17. }


 

Code :
  1. [13:27:00][pts/7][/tmp][#18][&1]
  2. benoit@athlon >>> ./a.out
  3. 1
  4. 2
  5. 1 2
  6. [13:27:32][pts/7][/tmp][#19][&1]
  7. benoit@athlon >>> echo -e '1\n2' | ./a.out
  8. 1 1207674016

bravo ... c'est grandiose ... [:pfff]
 
[edit] d'ailleurs, "1 2" est un entrée correcte que ton FFLUSH_STDIN rejète également [/edit]
 
 
Quant au préprocesseur, vu la tronche de ta macro, je me  
marre


Message édité par Taz le 08-08-2004 à 14:06:04
Reply

Marsh Posté le 08-08-2004 à 15:26:02    

[citation=817475,0,6][nom]Taz a écrit[/nomQuant au préprocesseur, vu la tronche de ta macro, je me marre[/citation]
La macro que tu montres n'est pas celle que j'ai écrite au début du topic. Je ne comprends même pas la boucle

Code :
  1. do {...} while(0);


qui ne fait qu'une seule passe (utilité d'une boucle ici ???)
 
Tu peux te marrer mais ce n'est pas la peine d'écrire une macro débile pour prouver que le préprocesseur est un ennemi !


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 18:08:38    

tu vois, tu n'y connais rien, tu utilises les macros à ton insu, tu n'as même pas conscience d'à quel point ta macro est mauvaise et dangereuse à utiliser.

Reply

Marsh Posté le 08-08-2004 à 18:18:40    

Sve@r a écrit :

La macro que tu montres n'est pas celle que j'ai écrite au début du topic. Je ne comprends même pas la boucle

Code :
  1. do {...} while(0);


qui ne fait qu'une seule passe (utilité d'une boucle ici ???)
 
Tu peux te marrer mais ce n'est pas la peine d'écrire une macro débile pour prouver que le préprocesseur est un ennemi !


 
ta reflechi à ce que ca fait si je fais par exemple
 

Code :
  1. if( ... ) FFLUSH_STDIN


 
 ??

Reply

Marsh Posté le 08-08-2004 à 19:30:46    

Si, par le plus grand des hasards, je devais impérativement écrire une macro qui commence à être complexe, avec des boucles etc, et que je ne puisse pas écrire une fonction (certains ont vraiment l'air de vouloir chercher le cas le plus abscons qui ferait planter le code), alors je l'imbriquerais entre accolades

Code :
  1. #define MACRO_COMPLIQUEE {\
  2. if (...) {\
  3.   do {\
  4.    instruction;\
  5.    instruction;\
  6.   } while (...);\
  7. }\
  8. else {\
  9.   instruction;\
  10. }\
  11. }


ensuite, cela devrait (j'ai pas testé) fonctionner même dans un if style...

Code :
  1. if( ... ) MACRO_COMPLIQUEE


Mais je pense qu'il vaudrait mieux encadrer son appel avec des accolades du style

Code :
  1. if( ... ) { MACRO_COMPLIQUEE }


 
Mais tout cela est d'un autre débat. Le débat originel était le problème des octets parasites résidents dans "stdin" après la saisie d'un nombre. Et, accessoirement, le danger d'utiliser une simple macro "SIZE" à la place de sizeof() pour gérer le contrôle de taille dans fgets()


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 19:30:46   

Reply

Marsh Posté le 08-08-2004 à 19:33:44    

ta solution est pire ... et alors tes présomptions sur l'utilisation de ta macro, ça vaut rien du tout ...
 
ET BORDEL CES OCTETS NE SONT PAS PARASITES, ILS SONT LES DONNÉES !

Reply

Marsh Posté le 08-08-2004 à 19:34:32    

Sve@r a écrit :

une simple macro "SIZE" à la place de sizeof()

pense au nombre de personne au monde qui ont écrit une macro SIZE ou N

Reply

Marsh Posté le 08-08-2004 à 19:46:13    

Taz a écrit :

ET BORDEL CES OCTETS NE SONT PAS PARASITES, ILS SONT LES DONNÉES !


 
Hum... c'est bien le forum mais ça devient vite un dialogue de sourds... :D  
1) on reprend mon premier code, une saisie de chaine, une saisie de nombre
2) pour la chaine, je rentre "Taz<entrée>"
3) pour le nombre, je rentre "21<entrée>"
4) mon programme m'affiche "Votre chaine est [Taz\n] et votre nombre est[21]" (là, c'est ok)
5) boucle suivante: Pas d'attente pour la chaine, juste attente pour le nombre => je rentre 22
6) mon programme m'affiche "Votre chaine est [\n] et votre nombre est [22]"
 
Ce "\n" récupéré par le "fgets" à la seconde boucle n'est peut-être pas parasite... mais ce n'est certainement pas de la donnée (ou alors c'est celle du lutin du clavier...)


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 19:54:23    

c'est normal ta le '\n' que scanf lit pas qui traine dans le tampon
 
fait plutot scanf("%ld\n", &nb);

Reply

Marsh Posté le 08-08-2004 à 19:56:41    

si c'est la donnée. tu as tapé le '\n', encore heureux que tu le récupères. tu n'en veux pas ? et bien saute le. maintenant si tu fous en l'air systématiquement les données de l'utilisateur, tu t'exposes à de graves problème.
 
on va prendre un exemple simple parmi tant d'autres d'un programme à rendre après un TP, le professeur spécifie l'emploi du programme : c'est très courant. Maintenant pas con, ton prof il remplis un fichier avec le jeu de données, et il appelle les programmes à corriger avec. Va lui expliquer que ton programme il marche pas ... il te répondra qu'il en a rin à secouer, qu'il donne les données et que toi tu les balardes, que ce n'est pas son problème si tu ne sais pas analyser des données, et qu'il te mets donc une note conséquente.
 
non, c'est vraiment une connerie montrueuse ce que tu fais là.

Reply

Marsh Posté le 08-08-2004 à 19:59:06    

cris56 a écrit :

c'est normal ta le '\n' que scanf lit pas qui traine dans le tampon
 
fait plutot scanf("%ld\n", &nb);

euh non, là c'est pire ... :) la solution, c'est un truc du genre
 
while((c=fgetc(stdin)) != EOF && c != '\n')
  ;
 
 
mets le problème avec scanf, c'est si ton guss rentre n'importe quoi, il salis ton stdin, et c'est chiant à remettre en état. mais lire inconditionnellement des lignes et ensuite tenter des les analyser, c'est sans faille.

Reply

Marsh Posté le 08-08-2004 à 19:59:53    

cris56 a écrit :

c'est normal ta le '\n' que scanf lit pas qui traine dans le tampon
 
fait plutot scanf("%ld\n", &nb);


 
Désolé, j'aurais du le dire mais j'avais déjà essayé cette solution.
Dans ce cas, lorsque le pgm demande le nombre, je dois taper 2 fois sur entrée pour que mon nombre soit enregistré... mais le 2° entrée est bien de nouveau récupéré par "fgets"
 
Sur un autre forum, qqun a parlé de "fpurge" pour régler ce soucis. Je ne connais pas cette fonction. Qqun la connais ?


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 08-08-2004 à 20:01:28    

ça existe pas, mais comprends bien que c'est enfantin d'écrire quoi que ce soit pour 'sauter' les quelques espaces qui t'ennuient.

Reply

Marsh Posté le 08-08-2004 à 20:10:19    

Taz a écrit :

euh non, là c'est pire ... :) la solution, c'est un truc du genre
 
while((c=fgetc(stdin)) != EOF && c != '\n')
  ;
 
 
mets le problème avec scanf, c'est si ton guss rentre n'importe quoi, il salis ton stdin, et c'est chiant à remettre en état. mais lire inconditionnellement des lignes et ensuite tenter des les analyser, c'est sans faille.


 
 donc il vaut mieux saisir uniquement des entier ou des reels avec scanf et faire si necessaire
while((c=fgetc(stdin)) != EOF && c != '\n');  
 
ca revient au mem je suppose  
 
while((c=getchar()) != EOF && c != '\n');

Reply

Marsh Posté le 08-08-2004 à 20:24:51    

getchar est souvent définit comme une macro. (c étant un int) (on peut envisager une version while((c=getchar()) != EOF && isspace(c)); if(c != EOF) ungetc(stdin); /* etc */
 
pour le reste, il y a une mouvement qui vise à ne plus utilisé du tout scanf, pourquoi que ce soit en faveur de fgets+sscanf lors de lecture interactive.

Reply

Marsh Posté le 08-08-2004 à 20:31:00    

c'est ce que je fait, surtout quand je parse un fichier texte, je passe toujours par un buffer temporaire pour eviter tout ces problemes

Reply

Marsh Posté le 08-08-2004 à 20:32:36    

la preuve que c'est une technique fiable et éprouvée.

Reply

Marsh Posté le 28-10-2007 à 18:49:02    

Taz a écrit :

 si c'est la donnée. tu as tapé le '\n', encore heureux que tu le récupères. tu n'en veux pas ? et bien saute le. maintenant si tu fous en l'air systématiquement les données de l'utilisateur, tu t'exposes à de graves problème.
 
on va prendre un exemple simple parmi tant d'autres d'un programme à rendre après un TP, le professeur spécifie l'emploi du programme : c'est très courant. Maintenant pas con, ton prof il remplis un fichier avec le jeu de données, et il appelle les programmes à corriger avec. Va lui expliquer que ton programme il marche pas ... il te répondra qu'il en a rin à secouer, qu'il donne les données et que toi tu les balardes, que ce n'est pas son problème si tu ne sais pas analyser des données, et qu'il te mets donc une note conséquente.
 
non, c'est vraiment une connerie montrueuse ce que tu fais là.


 
Hola calmos. T'es trop dans une optique "lecture de fichier binaire sur l'entrée standard". Ici c'était pour lire des saisies typiquement tapées par un utilisateur humain. En général les terminaux envoient ce qu'a tapé l'utilisateur lorsqu'il appuie sur entrée. Dès lors une saisie = une ligne de texte systematiquement.
Donc, non, le \n n'est pas de la donnée. Si on s'est assuré que fgets a pu lire une ligne (le dernier caractere est bien \n) alors on peut se passer du \n et garder les caractères essentiels de la ligne.  
 
Ce qu'il manquait c'est juste de décider quoi faire si une ligne n'a pas pu être lue (ex: ligne trop grande). Afficher un message d'erreur aurait été le bienvenu.
 
Le programme de test utilisant la macro FFLUSH_STDIN ne fait pas foi car une telle macro est completement à proscrire (ça devrait être interdit de toucher aux champs de tout type abstrait). Si on avait utilisé une fonction qui "nettoie" correctement, l'algo aurait été correct.

Reply

Marsh Posté le 28-10-2007 à 23:39:47    

Sve@r a écrit :


Si je lance un "strchr('\n')", cela me renverra l'adresse de l'octet contenant un "\n". Si je rajoute à cette adresse la valeur '\0', je ne vois pas trop ce que cela va faire... (je ne peux même pas tester, je n'utilise Linux qu'au bureau !!!)


Huh, tu ne connais pas le très idiomatique :

Code :
  1. /* search ... */
  2. char *p = strchr(s, '\n');
  3. if (p != NULL)
  4. {
  5.    /* and kill */
  6.    *p = 0;
  7. }


qui se complète d'une purge du flux (si on veut) en cas d'absence de '\n' ...

Code :
  1. else
  2. {
  3.    int c;
  4.    while ((c = fgetc(stdin)) != '\n' && c != EOF)
  5.    {
  6.    }
  7. }


ou toute autre action intelligente visant à récupérer les données (realloc() etc.) ?


Message édité par Emmanuel Delahaye le 28-10-2007 à 23:46:46

---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 28-10-2007 à 23:44:40    

Sve@r a écrit :

[citation=817475,0,6][nom]Taz a écrit[/nomQuant au préprocesseur, vu la tronche de ta macro, je me marre[/citation]
La macro que tu montres n'est pas celle que j'ai écrite au début du topic. Je ne comprends même pas la boucle

Code :
  1. do {...} while(0);


qui ne fait qu'une seule passe (utilité d'une boucle ici ???)


Euh, c'est

Code :
  1. do {...} while(0)


dans une macro... Le but de cette structure de code est de regrouper les instructions dans un bloc ({}) et de forcer l'usage du ';' dans l'utilisation de la macro. C'est pour ça qu'il n'est pas dans sa définition.

 

On aurait pu faire

Code :
  1. ...
  2. {\
  3.    bla();\
  4. }


mais ça n'aurait pas forcé l'usage du ';'.

 

Ces techniques sont assez élémentaires et très courantes...


Message édité par Emmanuel Delahaye le 28-10-2007 à 23:47:30

---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 29-10-2007 à 09:02:44    

ça va le déterrage ?

Reply

Marsh Posté le 29-10-2007 à 10:25:24    

Taz a écrit :

ça va le déterrage ?


Purée, 2004, j'avais pas vu... Mais le sujet est intéressant...
 


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 30-10-2007 à 15:15:19    

Emmanuel Delahaye a écrit :


Purée, 2004, j'avais pas vu... Mais le sujet est intéressant...
 


 
Ouaip... il l'était à l'époque. Aujourd'hui on passe par getline et on a moins de problème de ligne tronquée...


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 30-10-2007 à 21:14:03    

Mais getline c'est pas standaaaareuuuuuh

Reply

Marsh Posté le 31-10-2007 à 02:44:39    

matafan a écrit :

Mais getline c'est pas standaaaareuuuuuh


mais facile à reproduire avec du code standard...
 
http://mapage.noos.fr/emdel/inputs.htm


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed