Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

comprendre fgets

94 réponses
Avatar
bpascal123
Bonjour

Avec p2 qui pointe sur une chaine saisie avec fgets (ex."abcd")
dans for ( p2 ; *p2 ; p2++ )
Pourquoi l'incr=E9mentation se fait sur 6 intervalles en m=E9moire.

0 =3D a
1 =3D b
2 =3D c
3 =3D d
4 =3D \0
5 =3D ???

A quoi correspond la derni=E8re position (numero 5) ?

(En fait, je veux dire que je dois d=E9cr=E9menter 2 fois apres la fin
de la boucle for pour pointer p2 sur 'd' en position 3). Ca veut dire
qu'en plus de '\0', fgets inclue un autre caract=E8re.

Question suppl=E9mentaire (culture g=E9n=E9ral en informatique) :
La chaine enregistr=E9e avec fgets se trouve dans la memoire ram ou
dans un des registres?

Merci

10 réponses

1 2 3 4 5
Avatar
-ed-
On 5 nov, 09:16, -ed- wrote:
En théorie, oui, mais il manque un paramètre essentiel : la taille
maximale du tableau de char. La saisie ne peut donc être évitée, et



La saisie trop longue ne peut être évitée ...
Avatar
candide
-ed- a écrit :

C'est décrit comme ça dans le K&R ...




Non. Une fois de plus, tu lis de manière approximative.

La fonction gets() est à peine citée dans K&R2 et aucun trou de sécurité
n'est signalée, ce qui set assez compréhensible à l'époque.


Dans K&R2, l'appel à getc() concerne justement fgets() et les auteurs
précisent qu'ils donnent juste l'implémentation sur _leur_ système :

-----------------
here they are, copied from the standard library on our system
-----------------


(ça n'a donc pas le caractère général que tu laissais entendre) et il
disent même qu'ils donnent cet exemple pour le motif suivant :

-----------------
To show that there is nothing special about functions like fgets and fputs
-----------------

En théorie, oui, mais il manque un paramètre essentiel : la taille
maximale du tableau de char. La saisie ne peut donc être évitée, et
c'est un trou de sécurité bien connu et imparable.




Et alors ? Je ne dis pas le contraire, tu me sors l'argumentaire que je
connais par coeur. T'as jamais joué aux pétards ? Ben il jamais trop
tard. Les entrées/sorties quand tu débutes, c'est du bling-bling, c'est
pour faire semblant, c'est juste pour donner de la "convivialité",
c'est pour que le débutant parte pas trop vite.

Les entrées/sorties dans la vraie vie c'est immensément plus complexe
que ton malheureux fgets(). Je regarde comment sont traitées les E/S
dans CPython (l'implémentation du langage Python et de sa lib). Ils
déplorent par exemple (voir les explications dans le fichier
fileobject.c) que sous MSVC 6, ils ne disposent pas d'une autre fonction
que fgets(), je cite :


------------------------
So we use fgets for speed(!), despite that it's painful.
------------------------



Combien de fois ai-je lu cette plainte :

"je comprends pas pourquoi mais scanf lit le jour mais pas le mois ?
Comment je fais ?"



Pff... tu te répètes et ça n'a aucun intérêt...



Aucun intérêt ? j'en doute fort quand on voit le nombre de programmes de
débutants et même de programmeurs déjà avancés qui plantent pour cause
de désir de fantaisie dans les entrées/sorties.


2°) Utilise gets().



Faux. Inciter un débutant à utiliser un trou de sécurité est soit de
l'ignorance soit du sabotage.



Ni l'un ni l'autre. On s'autorise à utiliser scanf avec le spécifieur %s
et on interdirait pas gets() ? Tu n'as jamais proposé à des débutants
d'utiliser scanf avec %s ???



et si je tapes 100 caractères, il se passe quoi ? UB. Tout peut
arriver.




Et alors quel est le problème si c'est pour un programme-jouet ?
Quand on débute et qu'on fait des E/S, on se base sur l'adage suivant :

"Always trust user input"


Apprendre fgets correctement est un cauchemar, pas simplement pour le
débutant. Même des programmeurs chevronnés ont des doutes sur la
sémantique de fgets(), voici ce que je le lis en commentaire dans les
sources de CPython :

------------------------
CAUTION: The C std isn't clear about this: in those cases where fgets
writes something into the buffer, can it write into any position beyond
the required trailing null byte?
------------------------



Pour des saisies au clavier, fgets ne suffit pas, il faut gérer le
buffer de stdin et il faut gérer la conformité de ce qui est capturé
avec ce qui est envoyé ce qui transforme un programme-jouet en un
monstre. Et même pour les besoins les plus triviaux du débutants, fgets
ne donne pas satisfaction car la plupart du temps, l'utilisateur ne veut
pas du 'n' final et par contre il veut le '' à sa place ce qui est
compliqué par le fait que, en fonction de l'entier donné en paramètre à
fgets(), le saut de ligne pourra ne pas être capturé. Bref, une prise de
tête pas possible pour saisir une malheureuse date ou un prénom+nom et
qui se trouve être juste un prétexte dans un programme. T'as pas le
temps de te retourner que ton débutant il est déjà parti apprendre
Python ou java ...



:~$ c99 fclc.c
/tmp/ccyhtwXO.o: In function `main':
fclc.c:(.text+0x23): warning: the `gets' function is dangerous and
should not be used.



Tiens tiens ! Jamais tu lis les messages ?



Mais quelle remarque perspicace !
Avatar
espie
In article <4af2a85e$0$403$,
candide wrote:
Et alors ? Je ne dis pas le contraire, tu me sors l'argumentaire que je
connais par coeur. T'as jamais joué aux pétards ? Ben il jamais trop
tard. Les entrées/sorties quand tu débutes, c'est du bling-bling, c'est
pour faire semblant, c'est juste pour donner de la "convivialité",
c'est pour que le débutant parte pas trop vite.



C'est pas une raison pour lui donner des trucs faux.

Et alors quel est le problème si c'est pour un programme-jouet ?
Quand on débute et qu'on fait des E/S, on se base sur l'adage suivant :

"Always trust user input"




Chez toi, peut-etre. Chez moi, non. Une gestion d'erreur minimale, ca n'est
pas complique: juste dire que c'est pas bon et quitter. C'est le minimum
vital aujourd'hui. Si tu apprends du C, on n'est plus il y a quinze ans,
et il faut des le debut eviter que tes debutants fassent du trou de securite
au kilometre.

Apprendre fgets correctement est un cauchemar, pas simplement pour le
débutant. Même des programmeurs chevronnés ont des doutes sur la
sémantique de fgets(), voici ce que je le lis en commentaire dans les
sources de CPython :

------------------------
CAUTION: The C std isn't clear about this: in those cases where fgets
writes something into the buffer, can it write into any position beyond
the required trailing null byte?



C'est pas parce que les gens qui font Cpython ne savent pas lire OU essaient
de faire des trucs tres bizarres que fgets est mauvais. C'est pourtant tres
simple d'emploi comme fonction si on sait lire.


Pour des saisies au clavier, fgets ne suffit pas, il faut gérer le
buffer de stdin et il faut gérer la conformité de ce qui est capturé
avec ce qui est envoyé ce qui transforme un programme-jouet en un
monstre. Et même pour les besoins les plus triviaux du débutants, fgets
ne donne pas satisfaction car la plupart du temps, l'utilisateur ne veut
pas du 'n' final et par contre il veut le '' à sa place ce qui est
compliqué par le fait que, en fonction de l'entier donné en paramètre à
fgets(), le saut de ligne pourra ne pas être capturé. Bref, une prise de
tête pas possible pour saisir une malheureuse date ou un prénom+nom et
qui se trouve être juste un prétexte dans un programme. T'as pas le
temps de te retourner que ton débutant il est déjà parti apprendre
Python ou java ...



Les entrees sont compliquees. C'est le cas dans tous les langages.
Il faudra bien tot ou tard l'expliquer a tes debutants. Si vraiment
tu t'y refuses, tu leur ponds une fonction qui fait l'essentiel du
boulot.

Mais sortir gets de sa poubelle, surtout avec tous les arguments
fallacieux que tu avances:
1/ c'est grotesque
2/ c'est une faute qui peut avoir des consequences graves.

Perso, j'aimerais bien que l'incidence des trous de securite diminue
dans ces prochaines annees, donc si tu pouvais arreter d'enseigner le C,
au fond, ca serait pas plus mal...
Avatar
candide
-ed- a écrit :
"

On apprend à utiliser fgets().




Tiens à propos, tu parles de fgets() dans "ta" faq sur developpez :

http://c.developpez.com/faq/?page=clavier_ecran#CONS_fgets

Tu t'es planté sur le prototype de fgets() : le 2nd paramètre est de
type int et pas size_t. Ça fait des années que ta question est dans
cette FAQ, des milliers de personnes l'ont lu et l'erreur est toujours
là, c'est assez éloquent.

Sinon, tes explications sont imbitables pour celui qui ne connait pas,
c'est vraiment d'une confusion ...

A part ça, fgets() est tellement bien pensée que la première chose qu'on
fait c'est de ... la recoder. Et c'est une pratique générale à ce que
j'ai pu constater.


Dans ta fonction read_stdin(), tu utilises strchr(), pourtant il est
quand même moins couteux d'utiliser strlen() et de se placer en avant
dernière position puisque c'est le seul endroit où 'n' pourrait se
trouver dans le buffer.


[Je répondrai au message de Marc Espie prochainement].
Avatar
-ed-
On 5 nov, 11:26, candide wrote:
Ni l'un ni l'autre. On s'autorise à utiliser scanf avec le spécifieur %s
et on interdirait pas gets() ? Tu n'as jamais proposé à des débutan ts
d'utiliser scanf avec %s ???



Non, ou alors avec une taille : "%32s", par exemple..

"Always trust user input"



Pourquoi ne pas raisonner juste dès le début ? Pourquoi consttruire du
faux qu'il faudra ensuite démolir et reconstrtuire ensuite ? Quel
intérêt ?

Le bon adage est "Never trust user input". C'est quoi ce jeu
d'inversion des valeurs ?

Apprendre fgets correctement est un cauchemar, pas simplement pour le



Mais non. Peut être que tu as du mal, mais la plupart y arrivent.

débutant. Même des programmeurs chevronnés ont des doutes sur la
sémantique de fgets(), voici ce que je le lis en commentaire dans les
sources de CPython :



Et si, au lieu de penser par procuration, tu essayais de te faire une
idée du monde par toi même ?


------------------------
CAUTION:  The C std isn't clear about this:  in those cases where fge ts
writes something into the buffer, can it write into any position beyond
the required trailing null byte?



Evidemment non. Quelle idée ?

Pour des saisies au clavier, fgets ne suffit pas, il faut gérer le



Si.

buffer de stdin et il faut gérer la conformité de ce qui est captur é
avec ce qui est envoyé



Comprend pas. Exemple ?

ce qui transforme un programme-jouet en un
monstre. Et même pour les besoins les plus triviaux du débutants, fge ts
ne donne pas satisfaction car la plupart du temps, l'utilisateur ne veut
pas du 'n' final  et par contre il veut le '' à sa place ce qui es t



Oui, et le remplacement est trivial avec strchr() et un pointeur... Ce
code est archi-connu et validé.

compliqué par le fait que, en fonction de l'entier donné en paramèt re à
fgets(), le saut de ligne pourra ne pas être capturé. Bref, une prise de



Wouah, il faut utiliser un if. C'est difficile ?

tête pas possible pour saisir une malheureuse date ou un prénom+nom e t
qui se trouve être juste un prétexte dans un programme. T'as pas le
temps de te retourner que ton débutant il est déjà parti apprendre
Python ou java ...



Chacun fait ce qu'il veut. Je ne fais pas de marketing ici, mais de la
technique. Lasiaie avec fgets() est simple, même si elle n'est pas
directe.

L'usage est de créer une fonction de nettoyage qui fait le boulot
simplement et clairement.

fgets (s, sizeof s, stdin); /* s est un un tableau de char * /
fclean (s, stdin);

C'est compliqué ? Cauchemardesque ? Tu ne sais pas coder fclean() ?
C'est trop fort pour toi ? Quel est l'utilité de faire peur au
débutant ? Pourquoi ne pas lui apprendre à maitriser le C, plutôt que
de chercher à l'en dégouter ? Si tu n'aimes pas le C, va jouer
ailleurs;;
Avatar
-ed-
On 6 nov, 02:59, candide wrote:
Tiens à propos, tu parles de fgets() dans "ta" faq sur developpez :



Ma FAQ ?

http://c.developpez.com/faq/?page=clavier_ecran#CONS_fgets

Tu t'es planté sur le prototype de fgets() : le 2nd paramètre est de
type int et pas size_t. Ça fait des années que ta question est dans
cette FAQ, des milliers de personnes l'ont lu et l'erreur est toujours
là, c'est assez éloquent.



Si il y a une erreur, il suffit de la signaler. Qui ne fait pas
d'erreurs...

Sinon, tes explications sont imbitables pour celui qui ne connait pas,
c'est vraiment d'une confusion ...



Il n'est de pire sourd que celui qui ne veut rien entendre ...

A part ça, fgets() est tellement bien pensée que la première chose qu'on
fait c'est de  ... la recoder. Et c'est une pratique générale à c e que
j'ai pu constater.



Bof, on peut très bien utiliser fgets() telle quelle. Il suffit de la
faire suivre par une fonction de nettoyage adéquate. C'est largement
suffisant pour faire du code rapidement et simplement (étudiants,
petits projets...) On peut aussi, si on préfère combiner les deux
fonctions en une seule. C'est peut être ce que tu appelles
'recoder'...

Mais on peut aussi recoder complétement une fonction de saisie de
lignes. Je l'ai fait dans ma CLIB (fget_line()). Elle utilise un
tableau dynamique automatique qui fait qu'on a plus de problèmes de
tailles... Reste à penser à libérer le buffer alloué... C'est pas p ire
que strdup() ...

Dans ta fonction read_stdin(), tu utilises strchr(), pourtant il est



Oui, car c'est la seule façon simple de déterminer si le 'n' est
présent ou non et d'agir en conséquence.

quand même moins couteux d'utiliser strlen() et de se placer en avant



Certainement pas. strchr() fait une boucle qui s'arrête au premier
caractère trouvé ou au 0 final. strlen() fait une boucle qui s'arrête
au zéro final. Tu vois la différence ? OK, dans ce cas précis, la
différence n'est que de 1 caractère (plus la chaine est longue, plus
la différence est insignifiante. mais sur les chaines courtes, ça peut
jouer...). Mais le vrai problème de strlen() est qu'on ne sait pas si
il y a un 'n' ou non. Ou alors il faut tester en s[len-1] pour savoir
si il y a un 'n' ... Bref, de la complication inutile. Je commence à
comprendre pourquoi tu trouves ça compliqué. SI tu n'utilises pas les
méthodes simples, c'est sûr que tout devient plus complexe...

dernière position puisque c'est le seul endroit où 'n' pourrait se
trouver dans le buffer.



Et si il n'y est pas ?

Franchement, je ne vois pas de plus simple et de plus clair que :

char *p = strchr (s, 'n');
if (p != NULL)
{
*p = 0;
}
else
{
/* agir en consequence (lire les caracteres restant, agrandir le
buffer, signaler etc. */
puts ("saisie trop longue");
}
Avatar
espie
In article <4af3831b$0$748$,
candide wrote:
A part ça, fgets() est tellement bien pensée que la première chose qu'on
fait c'est de ... la recoder. Et c'est une pratique générale à ce que
j'ai pu constater.



Grave n'importe quoi. C'est totalement inexact.

fgets() respecte les principes de conception de la libc. En particulier,
elle ne gere *pas* la memoire de l'utilisateur (ce qui se passe dans les
tampons internes des fichiers). Du coup, dans enormement de cas de figure,
pour faire quelque chose de robuste et tout terrain, il faut utiliser fgets
plus un allocateur memoire (celui de la libc par exemple, mais pas toujours).

Si tu ne t'en es pas encore rendu compte, c'est dommage: c'est un des
principes fondateurs de la libc (c'est comme ca qu'on peut facilement
savoir que strdup est un intrus, par exemple).

De toutes facons, la libc n'a pas vocation a te fournir des fonctions
"de haut niveau" utilisables telles quelles, mais des fonctions qui
t'isolent des details d'implementation, sans rien compromettre en terme
d'efficacite. fgets remplit parfaitement cette mission.

Pour des cas reels, on va combiner fgets avec une strategie de gestion
memoire, et ecrire l'API et le code qui va bien pour obtenir le resultat
voulu.


Si tu as la flemme de faire le code necessaire toi-meme, tu peux en
piquer ailleurs.

Perso, je n'ai jamais eu le moindre souci avec fgets, que j'utilise
regulierement pour du code portable.

Lorsque je fais des trucs specifiques BSD, j'utilise plus regulierement
fgetln, qui n'est absolument pas portable en dehors de BSD, qui peut etre
bien plus efficace (evite une copie sur pas mal d'algo), mais qui necessite
une gestion tres carree de ce qu'on fait (comprendre la duree de vie des
info renvoyees)...

En pratique, j'arrive a me faire les couches d'abstraction qui vont bien,
ce qui fait que mon code sait utiliser fgets si necessaire, et passer
sur fgetln s'il est disponible. Ca n'est pas tres complique. A mon sens,
c'est a la portee de n'importe quel programmeur C competent.
Avatar
Marc Boyer
Le 06-11-2009, -ed- a écrit :
On 5 nov, 11:26, candide wrote:
"Always trust user input"



Pourquoi ne pas raisonner juste dès le début ? Pourquoi consttruire du
faux qu'il faudra ensuite démolir et reconstrtuire ensuite ? Quel
intérêt ?



Pour borner le périmètre des choses à maitriser, puis l'angrandir
peu à peu ?

On n'apprend pas à conduire sur l'autoroute dès la première leçon.
On n'apprend pas la numérations en maternelle par les entiers
de Peano. Etc.

Le bon adage est "Never trust user input". C'est quoi ce jeu
d'inversion des valeurs ?



Un moyen pédogogique clair pour indiquer aux étudiants qu'on
commence par raisonner dans un monde gentil, puis qu'on passera
aux choses sérieuses par la suite.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
espie
In article <hd11j5$3ck$,
Marc Boyer wrote:
Le 06-11-2009, -ed- a écrit :
On 5 nov, 11:26, candide wrote:
"Always trust user input"



Pourquoi ne pas raisonner juste dès le début ? Pourquoi consttruire du
faux qu'il faudra ensuite démolir et reconstrtuire ensuite ? Quel
intérêt ?



Pour borner le périmètre des choses à maitriser, puis l'angrandir
peu à peu ?

On n'apprend pas à conduire sur l'autoroute dès la première leçon.
On n'apprend pas la numérations en maternelle par les entiers
de Peano. Etc.

Le bon adage est "Never trust user input". C'est quoi ce jeu
d'inversion des valeurs ?



Un moyen pédogogique clair pour indiquer aux étudiants qu'on
commence par raisonner dans un monde gentil, puis qu'on passera
aux choses sérieuses par la suite.



C'est debile. Passer cinq minutes a "je valide ce qui a ete entre, si
c'est pas bon -> abort", c'est quand meme pas serieux.
Avatar
Marc Boyer
Le 06-11-2009, Marc Espie a écrit :
In article <hd11j5$3ck$,
Un moyen pédogogique clair pour indiquer aux étudiants qu'on
commence par raisonner dans un monde gentil, puis qu'on passera
aux choses sérieuses par la suite.



C'est debile. Passer cinq minutes a "je valide ce qui a ete entre, si
c'est pas bon -> abort", c'est quand meme pas serieux.



Sauf que ça suppose que tu explique clairement ce qu'est une erreur,
la notion de fin de flux (toujours un bon moment à expliquer la fin
du clavier), ce qui nous ammène à EOF qui est un entier mais pas un
char, etc...

Tu te souviens qu'un débutant, ça a du mal à trouver le min et
le max dans un tableau de taille fixe ?

Donc, oui, dans mon cours il y avait "les E/S sont gentilles,
faisons de l'algorithmique de base", et puis plus loin "bienvenue
dans le monde réel".

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
1 2 3 4 5