Avec p2 qui pointe sur une chaine saisie avec fgets (ex."abcd")
dans for ( p2 ; *p2 ; p2++ )
Pourquoi l'incrémentation se fait sur 6 intervalles en mémoire.
0 = a
1 = b
2 = c
3 = d
4 = \0
5 = ???
A quoi correspond la dernière position (numero 5) ?
(En fait, je veux dire que je dois décrémenter 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ère.
Question supplémentaire (culture général en informatique) :
La chaine enregistrée avec fgets se trouve dans la memoire ram ou
dans un des registres?
Merci
A rien de sp�cial. Probablement ce qu'il y avait dans le buffer � cette
case l� avant. De toute fa�on ton exemple est suspect...
Si tu as saisi ta cha�ne au clavier, tu devrais avoir le caract�re de
fin de ligne dans le buffer. Et il ne figure pas dans ton exemple.
Autre possibilit�, tu lis un fichier binaire qui contient '\0' �
l'offset 4 et '???' � l'offset 5. Bref ton fgets() est all� lire plus
loin que tu ne crois. En effet fgets() lit jusqu'� la fin de ligne et
'\0' n'est pas une fin de ligne et peut donc �tre lu comme tout caract�re.
Peux tu pr�ciser la fa�on dont tu utilises fgets()?
>
> (En fait, je veux dire que je dois d�cr�menter 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�re.
C'est pas �a le soucis. C'est le fait que tu doives faire p2[-2] pour
pointer sur 'd'. En toute logique avec ton code qui s�pare le *p2 et le
p2++, � la sortie p2 *doit* pointer sur le \0, et donc 'd' est en
p2[-1]. Apr�s si tu as �cris ta boucle for ainsi:
for(p2 = ...; *p2++; ) { ... }
(i.e. tu teste *p2 et tu incr�mente p2 dans la foul�e, ind�pendamment du
r�sultat du test). Alors � la sortie de la boucle p2 est all� un pas
plus loin que le '\0'. Dans ce cas l�, et ce cas l� uniquement, 'd' est
accessible via p2[-2] (-1 : on pointe sur '\0', -2 : on pointe sur 'd').
Maintenant une autre possibilit� si tu l'indiques, tu s�pares
effectivement le *p2 et le p2++ (ou ++p2 suivant ce qu'on pr�f�re)
c'est que tu modifie p2 � l'int�rieure de la boucle et que tu sorte
d'elle via un break ou un goto. Bref un truc pas tr�s propre.
> Question suppl�mentaire (culture g�n�ral en informatique) :
> La chaine enregistr�e avec fgets se trouve dans la memoire ram ou
> dans un des registres?
Dans fgets() on doit passer un buffer de stockage.
(man fgets)
#include <stdio.h>
char *fgets(char *BUF, int N, FILE *FP);
Donc la question ne se pose pas.
En outre j'ai du mal � imaginer un buffer de longeur variable stock�
dans les registres du CPU. A part quelques CPU exotiques auquels on peut
acceder aux registres via un tableau (i.e. ils sont accessibles de fa�on
ind�x�e via un autre registre), je ne vois pas trop le moyen pratique
pour faire cela. Quant � l'int�r�t, il est nul. Les registres sont plus
utiles aux calculs qu'� stocker des valeurs. Pour stocker des valeurs la
RAM suffi amplement, et si on craint les temps acc�s il faut comprendre
qu'avec fgets() on fait des entr�es sortie donc des temps d'un ordre de
grandeur infiniment sup�rieur aux acc�s m�moire.
sam.
> Si tu as saisi ta cha�ne au clavier, tu devrais avoir le caract�re de
> fin de ligne dans le buffer. Et il ne figure pas dans ton exemple.
>
> Autre possibilit�, tu lis un fichier binaire qui contient '\0' �
> l'offset 4 et '???' � l'offset 5. Bref ton fgets() est all� lire plus
> loin que tu ne crois. En effet fgets() lit jusqu'� la fin de ligne et
> '\0' n'est pas une fin de ligne et peut donc �tre lu comme tout caract�re.
Une autre option est que tu lise un fichier se terminant par abcd (sans
retour � la ligne).. et dans ce cas fgets() ajoute un '\0' final.
> Peux tu pr�ciser la fa�on dont tu utilises fgets()?
Ca reste d'actualit�.
sam.
Grand merci,
Voici la cause de mes questions :
int main(void)
{
char Ch[101] ;
char *p1, *p2 ;
int Pali ;
p2 = Ch ;
printf("\nEntrez une ligne de texte (max.100 caract.) : \n") ;
fgets(Ch, 101, stdin) ;
for ( p2 ; *p2 ; p2++ )
;
p2 = p2-2 ;
...
La même chose avec gets :
printf("Entrez une ligne de texte (max.100 caractères) :\n");
gets(CH);
/* Placer P2 sur la dernière lettre de la chaîne */
for (P2=CH; *P2; P2++)
;
P2--;
voila.
Merci
> (En fait, je veux dire que je dois d�cr�menter 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�re.
>
Je crois que cette information est donn�e dans la premi�re r�ponse de
Samuel : c'est le caract�re de fin de ligne '\n'.
Du coup, ce qu'on a c'est :
0 = a
1 = b
2 = c
3 = d
4 = \n
5 = \0
Quand ta boucle s'arr�te, *p vaut \0, *(p-1) vaut \n et c'est fort
logiquement *(p-2) qui vaut 'd'.
--
Manuel P�gouri�-Gonnard Institut de math�matiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
> fgets(Ch, 101, stdin) ;
>
> for ( p2 ; *p2 ; p2++ )
> ;
Ok.. donc stdin et pas de bizzareries dans la boucle for. J'imagine que
tu termines ton entr�e avec un "return" plutot qu'un ctrl-d (ou ctrl-z
sous win/dos). Dans ce cas comme indiqu� par Manuel le buffer doit
contenir: {'a', 'b', 'c', 'd', '\n', '\0'}, et donc 'd' se trouve 2
places avant le '\0' point� par p2, d'o� le:
>
> p2 = p2-2 ;
> La m�me chose avec gets :
Oui! gets() mange le '\n' final.. et donc le buffer contient {'a', 'b',
'c', 'd', '\0'}, et p2 pointe 1 place apr�s le 'd', d'o� la n�c�ssit� de
ne reculer que d'une place:
> P2--;
En fait fgets() ne nettoie pas le caract�re de fin de ligne alors que
gets() si.
sam.
Comment le sais-tu ?
> 0 = a
> 1 = b
> 2 = c
> 3 = d
> 4 = \0
Correct
> 5 = ???
La valeur est indéfinie. Mais p ne devraot pointer sur cet emplacement
mémoire.
>
> A quoi correspond la dernière position (numero 5) ?
A rien.
> (En fait, je veux dire que je dois décrémenter 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ère.
Non. Mais il est probable que la saisie avec fgets de "abcd", suivie
de l'appui de la touche ENTER donne en réalité :
'a','b','c','d','\n',0
C'est probablement ça qui te trouble.
http://www.bien-programmer.fr/notes.php#saisie
> Question supplémentaire (culture général en informatique) :
> La chaine enregistrée avec fgets se trouve dans la memoire ram ou
> dans un des registres?
Elle se trouve en mémoire. C'est tout ce qu'on peut dire. Selon la
façon dont la variable a été définie, elle sera en mémoire
automatique, statique ou allouée.
http://www.bien-programmer.fr/notes.php#donnees
Nota : la notion de mémoire est plus abstraite (mémoire 'logique') que
la notion de RAM (mémoire physique).
> En fait fgets() ne nettoie pas le caractère de fin de ligne alors que
> gets() si.
En fait, gets() ne 'nettoie' pas. En fait, il le lit avec getc() (il
faut bien déterminer quelle est la fin de la saisie ...), puis le
replace dans le stdin avec ungetc().
Ce qui fait que la saisie suivante n'est pas bloquante.
de toutes façons, il ne faut pas utiliser getc() qui est un bug.
>
> En fait, gets() ne 'nettoie' pas. En fait, il le lit avec getc() (il
> faut bien d�terminer quelle est la fin de la saisie ...), puis le
> replace dans le stdin avec ungetc().
Pure sp�culation. Tu sais ce que c'est que l'"encapsulation" ?
En outre, Plauger �crit (page 291):
Other functions have logic that parallels fgetc but avoids calling it in
the interest of speed. One is fread, (...). Two others are fgets and gets.
Et quand on regarde son code de gets, on ne lit aucun appel � getc, il
n'y a que des appels � des fonctions priv�es de bas niveau.
> de toutes fa�ons, il ne faut pas utiliser getc() qui est un bug.
gets() est un bug tu veux dire ?
C'est ce que j'ai toujours entendu partout en sorte que je ne sais m�me
pas ce qu'elle fait. Donc je viens de regarder et je trouve que c'est
exactement la fonction dont nombre de d�butants r�vent disposer.
Combien de fois ai-je lu cette plainte :
"je comprends pas pourquoi mais scanf lit le jour mais pas le mois ?
Comment je fais ?"
?
R�ponse :
1�) scanf et printf sont les colifichets de la programmation bling-bling.
2�) Utilise gets().
:))
Illustration :
#include <stdio.h>
int main(void)
{
char t[100];
gets(t);
printf("%s\n", t);
return 0;
}
candide@candide-desktop:~$ c99 fclc.c
/tmp/ccyhtwXO.o: In function `main':
fclc.c:(.text+0x23): warning: the `gets' function is dangerous and
should not be used.
candide@candide-desktop:~$ ./x
4 novembre 2009
4 novembre 2009
candide@candide-desktop:~$
C'est décrit comme ça dans le K&R ...
> > de toutes façons, il ne faut pas utiliser getc() qui est un bug.
>
> gets() est un bug tu veux dire ?
Oui, erreur de frappe.
> C'est ce que j'ai toujours entendu partout en sorte que je ne sais même
> pas ce qu'elle fait. Donc je viens de regarder et je trouve que c'est
> exactement la fonction dont nombre de débutants rêvent disposer.
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.
> Combien de fois ai-je lu cette plainte :
>
> "je comprends pas pourquoi mais scanf lit le jour mais pas le mois ?
> Comment je fais ?"
On apprend à utiliser fgets().
> Réponse :
>
> 1°) scanf et printf sont les colifichets de la programmation bling-bling.
Pff... tu te répètes et ça n'a aucun intérêt...
> 2°) Utilise gets().
Faux. Inciter un débutant à utiliser un trou de sécurité est soit de
l'ignorance soit du sabotage.
> #include <stdio.h>
>
> int main(void)
> {
> char t[100];
>
> gets(t);
et si je tapes 100 caractères, il se passe quoi ? UB. Tout peut
arriver.
> printf("%s\n", t);
>
> return 0;
>
> }
>
> candide@candide-desktop:~$ 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 ?
gets() ...
La saisie trop longue ne peut être évitée ...
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 '\0' � 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 ...
>>
>> candide@candide-desktop:~$ 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 !
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 '\0' � 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...
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].
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 fgets
> 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, fgets
> ne donne pas satisfaction car la plupart du temps, l'utilisateur ne veut
> pas du '\n' final et par contre il veut le '\0' à sa place ce qui est
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ètre à
> 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 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 ...
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;;
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 à ce 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 pire
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");
}
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.
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_mot=130
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".
Dans mon cours, il y a "les fonctions d'entree ont un code d'erreur, meme
si on reprend un idiome tout pret, le minimum syndical, c'est erreur -> abort".
Et c'est quoi le code d'erreur de scanf ? Et pourquoi getc ne retourne pas
un char ? Et ça veut dire quoi que la valeur de retour est "unsigned char
cast to an int" ?
Non, les codes de retour, c'était la partie "maintenant, on fait attention",
et ça va aussi avec les retours de malloc, etc.
Ben, on fait ca fonction par fonction, en expliquant comment marche la fonction
ou ce qu'il faut faire avec.
Pour scanf, c'est quand meme tres simple: ca renvoie le nombre de valeurs
converties avec succes, et autre chose en cas de pepin.
C'est pas dur de faire
r = scanf("%d", &d);
if (r != 1) {
fprintf(stderr, "Chef, on a un probleme\n");
abort();
}
Pour getc: expliquer getc avant de parler d'EOF, ca me parait aberrant.
Dans le pire des cas, je suis pret a leur donner un idiome qui marche,
a utiliser tel quel, quitte a expliquer certains details apres...
> Et c'est quoi le code d'erreur de scanf ? Et pourquoi getc ne retourne pas
> un char ? Et �a veut dire quoi que la valeur de retour est "unsigned char
> cast to an int" ?
>
Ici et dans tes messages pr�c�dents, tu as parfaitement traduit les
r�actions des �tudiants (et encore d'�tudiants qui r�agissent !)
> Non, les codes de retour, c'�tait la partie "maintenant, on fait attention",
> et �a va aussi avec les retours de malloc, etc.
Absolument. Question de progressivit� qui s'impose pour moi comme une
�vidence.
Bon, je crois que tu ne comprends mon point de vue. Je ne dis pas pas
fgets() soit mal con�ue ou inutile. Si les programmeurs comp�tents
disent en choeur depuis 20 ans que fgets() est une fonction adapt�e � la
capture de lignes et que gets() est un bug, c'est qu'ils ont
certainement raison et un bon programmeur doit s'y plier. Qu'il soit �
la charge du programmeur de g�rer la m�moire du tampon ne me choque pas
du tout.
Ce que je dis, c'est que contrairement � ce que vous pensez, fgets() est
une fonction compliqu�e � comprendre par les d�butants (d�butants �a va
jusqu'� L3 Info, oui !). Ce qui m'affole c'est que vous (-ed- et toi)
n'arriviez pas � concevoir cette difficult�. Alors tu as peut-�tre en
face de toi des �tudiants en �cole d'ing�nieurs en _informatique_ (et
donc qui a priori doivent bouffer du C/C++) et qui sont peut-�tre assez
dou�s mais sache qu'un �tudiant � la fac en L1 de sciences de
l'ing�nieur (= physique-chimie-meca), il a �norm�ment de mal avec le C
de m�me qu'un �l�ve dans une �coles d'ing�nieurs g�n�ralistes qui doit
passer sous les fourches caudines du C.
> 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.
Oui, donc on ne parle pas des m�mes personnes. Oui bien utiliser fgets()
ou savoir utiliser une fonction �quivalente propos�e par une
biblioth�que fait partie du boulot d'un programmeur C (au sens large).
>
> C'est pas une raison pour lui donner des trucs faux.
Fondamentalement, je milite pour une censure des entr�es/sorties
ou tout du moins un usage contr�l� et limit� et de
leur donner une fonction :
*) ludique (jeu en console, �viter la recompilation
syst�matique lors du "test")
*) utilitaire (et � ce moment pas d'entr�es sur l'entr�e standard qui
est super-casse gueule et de toute fa�on peu commode) pour pouvoir
r�cup�rer des entr�es en grand nombre (un lexique, un graphe, etc) ou
produire des sorties de taille importante
Disons que dans l'apprentissage du langage C, pour moi, la maitrise des
entr�es n'est pas prioritaire et cela r�sulte d'une �vidence : conna�tre
le contenu de stdio.h n�cessite de bien conna�tre le langage
(d'ailleurs, ce n'est pas pour rien que la Norme s�pare le langage de
la biblioth�que). Dans mon auto-formation du C, je me rends compte
que finalement, avoir essay� de comprendre des fonctions standard de
stdio.h ou string.h n'a fait que ralentir ma progression un peu comme
si je courais deux li�vres � la fois.
>
> 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.
Je comprends ton point de vue et si tes �tudiants n'en sont pas
troubl�s, il n'y a aucune raison de s'en priver. Pour moi, les
entr�es ne sont qu'un pr�texte et sont un domaine � part. M�langer les
deux en d�but d'apprentissage, c'est de la surcharge, tel est mon avis
en tous cas.
> 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.
Simple une fois qu'on a compris comme dirait monsieur de lapalisse.
Moi, ce n'est pas comme �a que je mesure la difficult� de compr�hension.
je pense que l'acquisition des m�canisme de fgets() cause
majoritairement un trouble, le t�moignage du codeur de CPython qui
assur�ment est un codeur exp�riment� en t�moigne et il te suffit de
parcourir les archives de ce forum et d'autres forums pour comprendre
que c'est une r�alit�.
> Les entrees sont compliquees. C'est le cas dans tous les langages.
> Il faudra bien tot ou tard l'expliquer a tes debutants.
Bof, �a d�pend � qui tu t'adresses. Il y a
�norm�ment de gens qui ne font du C qu'�
l'�cole ou � la fac.
>
> 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.
>
Oui, je comprends que tu sois irrit� par ce que j'ai dit toi qui
es sensible aux questions de s�curit�.
> 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...
Je n'enseigne pas le C ni l'informatique d'ailleurs. Mais j'ai pu faire
des TP de graphes � des �tudiants (math et infos) qui avaient un ou deux
ans ans de C, et le r�sultat n'�tait vraiment pas brillant, et il ne
s'agit pas de d�ficiences dans la connaissance des E/S mais tout
simplement dans le langage. Et cela concernait aussi les �tudiants qui
avait fait un parcours parall�le genre BTS voire IUT.
J'ai �crit :
"ta" faq
tu as vu les guillemets ? tu connais la s�mantique des guillemets en
fran�ais ?
Par ailleurs, tu es le r�dacteur de la r�ponse, Celui Qui Sait. Donc Tu
assumes Tes Erreurs, en particulier Ton prototype de fgets() est faux,
�a la fout mal quand on pr�tend enseigner fgets().
> Si il y a une erreur, il suffit de la signaler.
Faudrait aussi les corriger. T'as corrig� sur ton site la grossi�re
erreur que contient ta fonction de comparaison d'entiers pour qsort() ?
> Qui ne fait pas
> d'erreurs...
Pas toi ;)
>
>> 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 ...
Non j'ai lu et relu tes explications, c'est vraiment le degr� z�ro.
>
>> 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.
>
> 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'...
Oui, elle ne se suffit pas � elle-m�me pour les besoins les plus triviaux.
>
> 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�...
Tu crois m'impressionner ...
>> 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.
Non, ce n'est pas la seule fa�on.
>
>> 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 ?
Vu la sp�cification de fgets(), le '\n' arrive � la fin s'il arrive.
Donc a priori ton strchr() fait deux fois plus de boulot que strlen()
puisque strchr() doit tester deux caract�res ('\n' et contr�ler la
sortie de la cha�ne). Bon effectivement, �a c'est la th�orie mais en
pratique les temps sont �quivalents donc je retire ma remarque.
> si il y a un '\n' ... Bref, de la complication inutile. Je commence �
> comprendre pourquoi tu trouves �a compliqu�.
Tu ne peux pas comprendre, question d'empathie.
> SI tu n'utilises pas les
> m�thodes simples, c'est s�r que tout devient plus complexe...
>
C'est une m�thode plus simple car je sais que le '\n' est � la fin m�me
si ton code est plus canonique.
>
>> Apprendre fgets correctement est un cauchemar, pas simplement pour le
>
> Mais non. Peut �tre que tu as du mal, mais la plupart y arrivent.
>
Explique-nous donc le comportement (effets de bord, retour) de fgets()
lorsque son deuxi�me argument vaut 1.
> 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.
Le Rationale est pourtant bien mod�r� concernant cette fonction :
The Committee decided that gets was useful and convenient in those
special circumstances when the programmer does have adequate control
over the input, and as longstanding existing practice, it needed a
standard specification.
Non, j'ai des mecs qui ne sont pas specialement plus doues que les tiens.
Simplement, je sais que je vais les lacher dans la nature pour de vrai, et
qu'ils vont pondre du vrai code qui va etre utilise. Ma mission n'est pas
juste qu'ils comprennent, mais de faire ce que je peux pour qu'ils pondent
du code qui marchent.
Comme je sais tres bien qu'un etudiant retient surtout ce qui l'arrange,
je sais tres bien que, si je lui explique d'abord comment faire des trucs
simples qui ne marchent pas vraiment, puis des trucs plus compliques qui
fonctionnent, il va retenir la premiere version et se depecher d'oublier
la 2e. Donc c'est un point sur lequel je suis inflexible. Et tant pis si
ca va un peu moins vite et si on n'a pas vu toutes les subtilites du
langage a l'arrivee.
J'essaie de leur apprendre comment faire du code qui fonctionne. Pas
forcement du code super-astucieux. Pas forcement des algorithmes super-precis
et relativement tordus. Je ne vois pas pourquoi je ne leur dirais pas que
les entrees-sorties, c'est complique, et que pour un programme qui interagit
avec l'utilisateur, le programmeur va passer facilement 40% de son temps a
faire marcher correctement les dites interactions (en particulier, a valider
les entrees et a s'assurer qu'il n'y a aucune ambiguite pour l'utilisateur).
Si tu veux d'autres exemples, je passe aussi pas mal de temps sur les tableaux,
et je leur dis systematiquement que, dans le doute, il faut verifier l'index
avant d'acceder a un element de tableau. Ou sinon, essayer de faire plus
simple dans le code. Je leur dis aussi qu'ils ne seront pas evalues sur les
astuces tordues qu'ils peuvent mettre dans leur code, bien au contraire. Que
c'est plus difficile de faire du code simple et clair.
Dans un autre ordre d'idees, en programmation OO, je vois passer des tas
d'etudiants qui ont tres bien retenu toutes les subtilites de l'heritage
et qui savent quand les arguments seront co/contra-variants, mais qui degainent
l'heritage pour absolument tout, et me font des classes valises la ou la
composition est l'outil approprie. Ou bien qui ne connaissent pas des pattern
aussi utiles que Template Method ou Compiler Firewall...
Sauf absurdite manifeste du cursus, tu as des gens qui vont passer par celui-ci
et qui vont ensuite pondre du C dans l'industrie. Enseigner gets() a ces
gens-la, c'est ni plus ni moins qu'une faute professionnelle. Et je pese
mes mots.
Le comite a tort, ca lui arrive. Le monde est de plus en plus hostile,
avec des possibilites de piratage un peu partout, et de l'informatique
qui est utilisee de maniere routiniere pour des missions critiques, ou
l'echec peut occasionner des pertes de vies humaines. Il est plus que temps
d'essayer de monter un peu le niveau et d'essayer de limiter la casse.
Il ne faut jamais oublier que le comite represente la communaute du C dans
son ensemble, et que celle-ci contient aussi des debiles qui ne se sont
pas encore reveilles.
Une fois de plus, le C n'est pas un langage pour débutants en
informatique... Trop de subtilités à gérer d'un coup... Par contre,
pour les habitués d'un autre langage de programmation, passer un peu
de temps pour expliquer ces subtilités n'est pas du luxe. Certains
verront alors le C comme un langage compliqué, oui, c'est possible,
car la complexité est 'cachée' dans les autres langages. Ici, on est
proche du fonctionnement réel, donc les choses sont un peu moins
'bisounours'... C'est le prix à payer pour avoir du code efficace.
Ceci-dit, les E/S en Java, c'est pas d'une simplicité biblique non
plus... Quand au Pasca standard, ça n'existe carrément pas !
Heureusement, il y a Turbo Pascal (ou freePascal).
> Une fois de plus, le C n'est pas un langage pour d�butants en
> informatique...
Phrase que tu nous ressors r�guli�rement et qui n'a en fait aucun sens
tant les exp�riences sont diverses et tant cela d�pend du contenu que tu
vas mettre dans "le C". Plus qu'une question de difficult� intrins�que,
c'est une question (� capacit�s �gales) de :
1�) qualit� de l'exposition (en particulier la progressivit�, l'absence
de surcharge, la contextualisation, le choix des exemples)
2�) motivation (en particulier la finalit� de formation) de celui qui
apprend.
> Trop de subtilit�s � g�rer d'un coup...
Effectivement et c'est pour cela qu'il faut "parall�liser" les
difficult�s � exposer ce que tu as du mal � comprendre.
Et c'est tellement vrai qu'en lisant ton site je me rends compte que tu
n'as pas compl�tement compris comment fonctionne fgets(). En effet, je
lis :
-------------- http://www.bien-programmer.fr/notes.php --------------
Gestion des fins de ligne
On constate que lorsque fgets() lit une ligne enti�re, un '\n' se
retrouve � la fin de la chaine saisie. La pr�sence de '\n' est g�nante
ou non selon l'application.
Ceci dit, dans tous les cas, il est conseill� d'en d�tecter la pr�sence.
En effet, sa pr�sence indique que la ligne a �t� lue enti�rement, alors
que son absence indique que la ligne a �t� tronqu�e, et que d'autres
caract�res (au minimum un '\n') attendent pour �tre lus.
---------------------------------------------------------------------
Je crois que c'est incorrect car si je termine le flux par eof, alors
fgets() ne renverra pas NULL et il n'y aura pas de '\n' (le buffer est
vide) et pourtant la ligne a �t� lue enti�rement.
... c'est effectivement un classique des tests, s'assurer que ca marche
encore meme s'il n'y a pas de \n en fin de fichier.
Cote description, la page de man d'OpenBSD est on ne peut plus claire, et
rend la fonction parfaitement utilisable.
[Quitte a faire de la pub, c'est tres souvent le cas. C'est en grande
partie lie au fait que le projet utilise ces fonctions dans des conditions
reelles, et qu'il y a des gens que ca interesse de rendre ces pages les
plus claires possibles.]
DESCRIPTION
The fgets() function reads at most size-1 characters from the given
stream and stores them in the string str. Reading stops when a newline
character is found, at end-of-file, or on error. The newline, if any, is
retained. The string will be NUL-terminated if fgets() succeeds; other-
wise the contents of str are undefined.
The gets() function is equivalent to fgets() with an infinite size and a
stream of stdin, except that the newline character (if any) is not stored
in the string. It is the caller's responsibility to ensure that the in-
put line, if any, is sufficiently short to fit in the string.
[nota bene: il n'est pas dit comment on peut s'assurer que la ligne en entree
va marcher pour gets(). Il n'y a pratiquement peu de cas ou l'appelant peut
assurer cette responsabilite -> gets() ne sert a rien].
RETURN VALUES
Upon successful completion, fgets() and gets() return a pointer to the
string. If end-of-file or an error occurs before any characters are
read, they return NULL. The fgets() and gets() functions do not distin-
guish between end-of-file and error, and callers must use feof(3) and
ferror(3) to determine which occurred. Whether fgets() can possibly fail
with a size argument of 1 is implementation-dependent. On OpenBSD,
fgets() will never return NULL when size is 1.
Les points cruciaux sont:
- il faut toujours verifier si fgets a reussi.
- le resultat est NUL-terminated des lors que la fonction a reussi.
- en cas d'erreur, il faut utiliser feof et ferror pour savoir ce qui
s'est passe.
- il ne faut pas dependre de particularites de l'implementation si size==1.
Le reste (comment voir si on a lu une ligne complete, comment lire une
ligne complete dans un tampon de taille variable, comment detecter et
signaler la fin de fichier) se deduit trivialement(*) de la description.
*: au sens mathematique usuel. La description est suffisamment precise pour
qu'on puisse bosser avec, surtout si on la complete avec celle de feof et
ferror. Et on peut considerer que c'est a la portee de n'importe qui dote
d'un cerveau en etat de fonctionner, chose un peu rare de nos jours.
Une autre facon de voir, c'est de dire que ca n'a pas de sens d'enseigner
le C a des gens:
- qui n'ont pas les prerequis necessaires;
- qui sont trop cons pour comprendre (desole, c'est pas du tout
politically-correct).
Du coup, c'est legitime de larguer le bagage en trop, et d'enseigner quelque
chose de correct pour les gens qui vont s'en servir en vrai.
A mon sens, le C correct est a la portee de qui s'en donne la peine. C'est
pas simple, ca demande du boulot et de l'opiniatrete. Mais il n'y a pas de
place pour les feignants qui ne feront pas les efforts necessaires, et ca ne
fait aucun sens de faire de la demagogie et d'essayer de faire croire que c'est
un langage simple et accessible au debutant sans faire d'efforts.
> Cote description, la page de man d'OpenBSD est on ne peut plus claire, et
> rend la fonction parfaitement utilisable.
>
> [Quitte a faire de la pub, c'est tres souvent le cas. C'est en grande
> partie lie au fait que le projet utilise ces fonctions dans des conditions
> reelles, et qu'il y a des gens que ca interesse de rendre ces pages les
> plus claires possibles.]
>
Merci pour la pub, je note. En plus on peut les consulter en ligne,
c'est pratique.
--
Manuel P�gouri�-Gonnard Institut de math�matiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
le 07/11/2009 à 18:01, Manuel Pégourié-Gonnard a écrit dans le message
<hd4961$vtg$1...@talisker.lacave.net> :
> Marc Espie scripsit :
>
>> Cote description, la page de man d'OpenBSD est on ne peut plus claire, et
>> rend la fonction parfaitement utilisable.
>>
>> [Quitte a faire de la pub, c'est tres souvent le cas. C'est en grande
>> partie lie au fait que le projet utilise ces fonctions dans des conditions
>> reelles, et qu'il y a des gens que ca interesse de rendre ces pages les
>> plus claires possibles.]
>>
> Merci pour la pub, je note. En plus on peut les consulter en ligne,
> c'est pratique.
Si tu veux de la doc pas trop mal faite et disponible à tout moment via
la commande man, je te conseille les pages de manuel issues de SUSv3.
Elles sont disponibles sur toutes bonnes distributions sinon c'est là :
<http://www.kernel.org/pub/linux/docs/man-pages/man-pages-posix/>.
Je me permets de poster un exemple pour fgets(3) :
------------------------------------------------------------------------------
FGETS(3P) POSIX Programmer's Manual FGETS(3P)
PROLOG
This manual page is part of the POSIX Programmer's Manual. The Linux
implementation of this interface may differ (consult the corresponding
Linux manual page for details of Linux behavior), or the interface may
not be implemented on Linux.
NAME
fgets - get a string from a stream
SYNOPSIS
#include <stdio.h>
char *fgets(char *restrict s, int n, FILE *restrict stream);
DESCRIPTION
The fgets() function shall read bytes from stream into the array
pointed to by s, until n-1 bytes are read, or a <newline> is read and
transferred to s, or an end-of-file condition is encountered. The
string is then terminated with a null byte.
The fgets() function may mark the st_atime field of the file associated
with stream for update. The st_atime field shall be marked for update
by the first successful execution of fgetc(), fgets(), fgetwc(),
fgetws(), fread(), fscanf(), getc(), getchar(), gets(), or scanf()
using stream that returns data not supplied by a prior call to ungetc()
or ungetwc().
RETURN VALUE
Upon successful completion, fgets() shall return s. If the stream is at
end-of-file, the end-of-file indicator for the stream shall be set and
fgets() shall return a null pointer. If a read error occurs, the error
indicator for the stream shall be set, fgets() shall return a null
pointer, and shall set errno to indicate the error.
ERRORS
Refer to fgetc().
The following sections are informative.
EXAMPLES
Reading Input
The following example uses fgets() to read each line of input.
{LINE_MAX}, which defines the maximum size of the input line, is
defined in the <limits.h> header.
#include <stdio.h>
...
char line[LINE_MAX];
...
while (fgets(line, LINE_MAX, fp) != NULL) {
...
}
...
APPLICATION USAGE
None.
RATIONALE
None.
FUTURE DIRECTIONS
None.
SEE ALSO
fopen(), fread(), gets(), the Base Definitions volume of
IEEE Std 1003.1-2001, <stdio.h>
COPYRIGHT
Portions of this text are reprinted and reproduced in electronic form
from IEEE Std 1003.1, 2003 Edition, Standard for Information Technology
-- Portable Operating System Interface (POSIX), The Open Group Base
Specifications Issue 6, Copyright (C) 2001-2003 by the Institute of
Electrical and Electronics Engineers, Inc and The Open Group. In the
event of any discrepancy between this version and the original IEEE and
The Open Group Standard, the original IEEE and The Open Group Standard
is the referee document. The original Standard can be obtained online
at http://www.opengroup.org/unix/online.html .
IEEE/The Open Group 2003 FGETS(3P)
------------------------------------------------------------------------------
On notera d'ailleurs que pour gets(3) il est dit :
------------------------------------------------------------------------------
APPLICATION USAGE
Reading a line that overflows the array pointed to by s results in
undefined behavior. The use of fgets() is recommended.
Since the user cannot specify the length of the buffer passed to
gets(), use of this function is discouraged. The length of the string
read is unlimited. It is possible to overflow this buffer in such a way
as to cause applications to fail, or possible system security viola‐
tions.
It is recommended that the fgets() function should be used to read
input lines.
------------------------------------------------------------------------------
D'un autre coté les pages de manuel de Linux sont pratiques pour savoir
à quel standard une fonction est conforme :
------------------------------------------------------------------------------
CONFORMING TO
C89, C99, POSIX.1-2001. LSB deprecates gets(). POSIX.1-2008 removes
the specification of gets().
------------------------------------------------------------------------------
--
Benoit Izac
Ben �a d�pend � qui on s'adresse. Personnellement, je pense comprendre
ce qu'elle dit mais si tu prends un �tudiant de Licence d'info _lambda_,
je suis s�r qu'il comprend quasiment rien.
> et
> rend la fonction parfaitement utilisable.
>
Je sais pas si tu as donn� le texte complet mais il faudrait que ce
texte abstrait soit accompagn� de snippets.
>
> DESCRIPTION
>
> The fgets() function reads at most size-1 characters from the given
> stream and stores them in the string str. Reading stops when a newline
> character is found, at end-of-file, or on error. The newline, if any, is
> retained. The string will be NUL-terminated if fgets() succeeds; other-
> wise the contents of str are undefined.
>
> The gets() function is equivalent to fgets() with an infinite size and a
> stream of stdin, except that the newline character (if any) is not stored
> in the string. It is the caller's responsibility to ensure that the in-
> put line, if any, is sufficiently short to fit in the string.
>
> [nota bene: il n'est pas dit comment on peut s'assurer que la ligne en entree
> va marcher pour gets(). Il n'y a pratiquement peu de cas ou l'appelant peut
> assurer cette responsabilite -> gets() ne sert a rien].
>
> RETURN VALUES
> Upon successful completion, fgets() and gets() return a pointer to the
> string. If end-of-file or an error occurs before any characters are
> read, they return NULL. The fgets() and gets() functions do not distin-
> guish between end-of-file and error, and callers must use feof(3) and
> ferror(3) to determine which occurred. Whether fgets() can possibly fail
> with a size argument of 1 is implementation-dependent. On OpenBSD,
> fgets() will never return NULL when size is 1.
>
Le texte est plus friendly voire plus pr�cis que celui de la Norme mais
il en reste assez proche.
Ce texte donne la r�ponse � la question que je posais � -ed- sur le
comportement de fget() lorsque son 2�me argument n vaut 1 ("un"). Le
texte de la Norme est assez difficile � interpr�ter sur ce point.
D'apr�s mes essais sous Ubuntu, gcc ne renvoie pas NULL si n==1. Par
contre, dans son impl�mentation de fgets(), Plauger commence par
char *(fgets)(char *buf, int n, FILE *str)
{ /* get a line from stream */
unsigned char *s;
if (n<=1)
return (NULL); /* sic */
/* suite du code omise */
}
Je signale que cette question avait �t� d�j� soulev� sur comp.std.c par
notre ami charlie :
http://groups.google.fr/group/comp.std.c/msg/b7d1d6438cb0fc1a?hl=fr
Tout cela me conduit � penser que cette fonction n'est pas si simple
m�me si probablement d'usage basique chez les programmeurs C "pur".
Au passage, je suis quand m�me all� regarder dans du code r�el. Par
exemple dans les GNU core utilities, fichier getdate.c je trouve ceci :
/* ligne 0 */
#if TEST
#include <stdio.h>
int
main (int ac, char **av)
{
char buff[BUFSIZ];
time_t d;
printf ("Enter date, or blank line to exit.\n\t> ");
fflush (stdout);
buff[BUFSIZ - 1] = 0;
while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
{
d = get_date (buff, 0);
if (d == (time_t) -1)
printf ("Bad format - couldn't convert.\n");
else
printf ("%s", ctime (&d));
printf ("\t> ");
fflush (stdout);
}
return 0;
}
#endif /* defined TEST */
Pourquoi ligne 14 l'auteur place-t-il un 0 en final ?
Sinon, il est assez fr�quent que les entr�es soient faites non avec
fgets() mais avec fgetc() et analogues. Exemple typique, la calculatrice
en console calc (arbitrary precision calculator). Ou encore le bien
connu gnu-indent.
> ferror. Et on peut considerer que c'est a la portee de n'importe qui dote
> d'un cerveau en etat de fonctionner, chose un peu rare de nos jours.
>
:))
>
> Une autre facon de voir, c'est de dire que ca n'a pas de sens d'enseigner
> le C a des gens:
> - qui n'ont pas les prerequis necessaires;
> - qui sont trop cons pour comprendre (desole, c'est pas du tout
> politically-correct).
>
Alors, il faut qu'ils soient vraiment tr�s tr�s cons car m�me moi j'ai
fini par comprendre ! :)
> Du coup, c'est legitime de larguer le bagage en trop, et d'enseigner quelque
> chose de correct pour les gens qui vont s'en servir en vrai.
>
> A mon sens, le C correct est a la portee de qui s'en donne la peine. C'est
> pas simple, ca demande du boulot et de l'opiniatrete. Mais il n'y a pas de
> place pour les feignants qui ne feront pas les efforts necessaires, et ca ne
> fait aucun sens de faire de la demagogie et d'essayer de faire croire que c'est
> un langage simple et accessible au debutant sans faire d'efforts.
Tu me rassures ;)
J'ai l'impression que certaines personnes ont quand m�me des facilit�s.
Ce n'est pas trop mon cas, et le C que je comprends aujourd'hui m'a
demand� des milliers d'heures de lecture et de travail. Et quand je
vois le peu de temps que les �tudiants passent � bosser, je suis en fait
pas tr�s �tonn� qu'ils aient un niveau tr�s m�diocre en C.
le 07/11/2009 à 23:33, candide <can...@free.invalid> a écrit dans le
message <4af5f5ae$0$22505$426a...@news.free.fr> :
> Au passage, je suis quand même allé regarder dans du code réel. Par
> exemple dans les GNU core utilities, fichier getdate.c je trouve ceci :
>
> /* ligne 0 */
> #if TEST
>
> #include <stdio.h>
>
> int
> main (int ac, char **av)
> {
> char buff[BUFSIZ];
> time_t d;
>
> printf ("Enter date, or blank line to exit.\n\t> ");
> fflush (stdout);
>
> buff[BUFSIZ - 1] = 0;
> while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
> {
> d = get_date (buff, 0);
> if (d == (time_t) -1)
> printf ("Bad format - couldn't convert.\n");
> else
> printf ("%s", ctime (&d));
> printf ("\t> ");
> fflush (stdout);
> }
> return 0;
> }
> #endif /* defined TEST */
>
>
> Pourquoi ligne 14 l'auteur place-t-il un 0 en final ?
Ceinture et bretelle ? Je n'en vois pas l'intérêt. Par contre, ce qui me
choque c'est qu'il écrive :
char buff[BUFSIZ];
while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
d'une part c'est BUFSIZ et non (BUFSIZ - 1) qui devrait être utilisé,
d'autre part tester le retour de fgets est suffisant et je pense que le
« && buff[0] » ne sera jamais évalué.
--
Benoit Izac
le 07/11/2009 à 23:33, candide <can...@free.invalid> a écrit dans le
message <4af5f5ae$0$22505$426a...@news.free.fr> :
> Au passage, je suis quand même allé regarder dans du code réel. Par
> exemple dans les GNU core utilities, fichier getdate.c je trouve ceci :
>
> /* ligne 0 */
> #if TEST
>
> #include <stdio.h>
>
> int
> main (int ac, char **av)
> {
> char buff[BUFSIZ];
> time_t d;
>
> printf ("Enter date, or blank line to exit.\n\t> ");
> fflush (stdout);
>
> buff[BUFSIZ - 1] = 0;
> while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
> {
> d = get_date (buff, 0);
> if (d == (time_t) -1)
> printf ("Bad format - couldn't convert.\n");
> else
> printf ("%s", ctime (&d));
> printf ("\t> ");
> fflush (stdout);
> }
> return 0;
> }
> #endif /* defined TEST */
>
>
> Pourquoi ligne 14 l'auteur place-t-il un 0 en final ?
Ceinture et bretelle ? Je n'en vois pas l'intérêt. Par contre, ce qui me
choque c'est qu'il écrive :
char buff[BUFSIZ];
while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
d'une part c'est BUFSIZ et non (BUFSIZ - 1) qui devrait être utilisé,
d'autre part tester le retour de fgets est suffisant et je pense que le
« && buff[0] » n'échouera jamais.
--
Benoit Izac
while (fgets (buff, ...) && buff [0])
permet de tester si on a entré une chaine non vide
(dont le premier caractère est différent de \0),
à peu près équivalent à écrire : && strlen (buff) > 0
1/ je ne prendrais pas forcement le code GNU comme exemple de bonnes
pratiques (assez rarement en fait, mais c'est un avis perso).
2/ je ne sais pas si tu le fais expres, mais le #if TEST...#endif indique
assez clairement que ce que tu es en train de lire, c'est un bete test
de regression. Il est assez frequent que ceux-ci soient ecrits avec
nettement moins de soin que du code "reel", puisqu'ils sont normalement
executes dans un environnement parfaitement controle...
le 08/11/2009 à 10:52, Alain BARTHE a écrit dans le message
<4af694e5$0$6413$426a...@news.free.fr> :
>> char buff[BUFSIZ];
>> while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
>>
>> d'une part c'est BUFSIZ et non (BUFSIZ - 1) qui devrait être utilisé,
>> d'autre part tester le retour de fgets est suffisant et je pense que le
>> « && buff[0] » n'échouera jamais.
>>
> Si je ne me trompe pas,
>
> while (fgets (buff, ...) && buff [0])
>
> permet de tester si on a entré une chaine non vide
> (dont le premier caractère est différent de \0),
> à peu près équivalent à écrire : && strlen (buff) > 0
Le problème c'est qu'ici :
char buff[BUFSIZ]; /* buff[0] vaut n'importe quoi */
buff[BUFSIZ - 1] = 0;
while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
Si on rentre une chaîne vide fgets va renvoyer NULL donc buff[0] ne sera
pas évalué.
De plus, si on imagine que fgets ne renverrai pas NULL (ce qui est faux)
et donc que buff[0] soit évalué, il vaudrait n'importe quoi sur le
premier appel et vaudrait toujours quelque chose sur les appels suivants
car il était déjà valide à l'appel précédent.
--
Benoit Izac
> Au passage, je suis quand même allé regarder dans du code réel. Par
> exemple dans les GNU core utilities, fichier getdate.c je trouve ceci :
>
> /* ligne 0 */
> #if TEST
>
> #include <stdio.h>
>
> int
> main (int ac, char **av)
> {
> char buff[BUFSIZ];
> time_t d;
>
> printf ("Enter date, or blank line to exit.\n\t> ");
> fflush (stdout);
>
> buff[BUFSIZ - 1] = 0;
> while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
> {
> d = get_date (buff, 0);
> if (d == (time_t) -1)
> printf ("Bad format - couldn't convert.\n");
> else
> printf ("%s", ctime (&d));
> printf ("\t> ");
> fflush (stdout);
> }
> return 0;}
>
> #endif /* defined TEST */
>
> Pourquoi ligne 14 l'auteur place-t-il un 0 en final ?
<...>
> Et quand je
> vois le peu de temps que les étudiants passent à bosser, je suis en fait
> pas très étonné qu'ils aient un niveau très médiocre en C.
En effet. Ce code est erroné. Il ne fait pas ce qu'il prétend. Ceci
est correct :
#include <stdio.h>
#include <time.h>
static time_t get_date (char const buff[], int n)
{
time_t d = 0;
/* a completer ... */
(void) buff;
(void) n;
return d;
}
static void fclean (char *line, FILE * fp)
{
char *p = strchr (line, '\n');
if (p != NULL)
{
*p = 0;
}
else
{
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF)
{
}
}
}
int main (void)
{
char buff[BUFSIZ];
time_t d;
printf ("Enter date, or blank line to exit.\n\t> ");
fflush (stdout);
while (fgets (buff, BUFSIZ, stdin) && buff[0] != '\n')
{
fclean (buff, stdin);
> Si tu veux d'autres exemples, je passe aussi pas mal de temps sur les tableaux,
> et je leur dis systematiquement que, dans le doute, il faut verifier l'index
> avant d'acceder a un element de tableau. Ou sinon, essayer de faire plus
> simple dans le code. Je leur dis aussi qu'ils ne seront pas evalues sur les
> astuces tordues qu'ils peuvent mettre dans leur code, bien au contraire. Que
> c'est plus difficile de faire du code simple et clair.
Ca fait presque 6 mois que j'apprends le C seul avec livres, tuto,
exercices et usenet... J'ai une formation initiale en comptabilité.
Je passe aussi beaucoup de temps sur les tableaux car pour un premier
langage de programmation je trouve la logique des tableaux assez
proche d'une gestion de données comme on trouve dans les bases de
données... Je sais SQL plus puissant mais c'est que pour les
requêtes...
Y a-t-il un intérêt à approfondir les tableaux en C dans mon cas?
J'essaye actuellement de bien comprendre les tableaux, les pointeurs
et l'arithmétique avant de continuer...
Merci
Ca depend ce que tu appelles "approfondir" et ton sujet d'etudes precis.
Les tableaux permettent d'explorer plein de concepts algorithmiques a
peu de frais, c'est un investissement fort sympathique de ce point de vue.
Dans la vraie vie, c'est aussi une des structures de donnees les plus
utilisees: un des enormes avantages des tableaux, c'est qu'il n'y a pas
de pointeur (par opposition aux listes, par exemple), donc ca fait des
structures de donnees compactes en memoire ET localisees a un endroit
precis: pour beaucoup de problemes, ca va etre plus efficace que des listes.
(en fait, les listes ne deviennent sympathiques que dans les cas tres
dynamiques, ou on passe son temps a inserer et supprimer des trucs un peu
partout. Pour le reste, c'est souvent le nombre d'acces memoire qui domine le
temps d'execution).
Apres, il y a des points de detail qui ne sont pas forcement tres interessants.
Les tableaux multi-dimensionnels, par exemple, ont une applicabilite somme
toute assez faible. C'est plus interessant de savoir comment faire des
tableaux de taille quelconque (pointeurs, malloc et realloc).
Mieux vaut explorer les tableaux de base en combinaison avec d'autres
elements du langage, e.g., l'utilisation des tableaux de pointeurs (exemple
simple: tableaux de chaines de caracteres), puis les tableaux de pointeurs
de structure.
A ton niveau, c'est sans doute plus important de reflechir aux problemes
de robustesse de ce que tu fais, et d'essayer de voir comment arranger ton
code pour avoir des choses solides et comprehensibles (e.g., faire de
l'idiomatique). En particulier, le fait que les tableaux ne sont pas
checkes en taille, ce qui impose rapidement de devoir passer la taille
reelle du tableau en parametre supplementaire... puis les tableaux dynamiques
qui conduisent rapidement au pattern standard size/capacity...
>> while (fgets (buff, ...) && buff [0])
>>
>> permet de tester si on a entré une chaine non vide
>> (dont le premier caractère est différent de \0),
>> à peu près équivalent à écrire : && strlen (buff) > 0
>
> Le problème c'est qu'ici :
>
> char buff[BUFSIZ]; /* buff[0] vaut n'importe quoi */
> buff[BUFSIZ - 1] = 0;
> while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
>
> Si on rentre une chaîne vide fgets va renvoyer NULL donc buff[0] ne sera
> pas évalué.
>
> De plus, si on imagine que fgets ne renverrai pas NULL (ce qui est faux)
> et donc que buff[0] soit évalué, il vaudrait n'importe quoi sur le
> premier appel et vaudrait toujours quelque chose sur les appels suivants
> car il était déjà valide à l'appel précédent.
J'ai (encore) dit une bêtise. fgets() ne renvoie pas NULL si on a '\0'
en entrée.
J'avais fais mon test avec
% printf '' | ./mon_prog
or ça renvoie EOF
% printf '%c' '' | ./mon_prog
envoie bien un '\0'
Par curiosité, j'aimerai bien savoir comment on fait pour envoyer un
'\0' lors d'une saisie (sous UNIX).
Dans tous les cas, avoir un '\0' en entrée sur un fichier texte est peu
vraisemblable.
--
Benoit Izac
> Par curiosité, j'aimerai bien savoir comment on fait pour envoyer un
> '\0' lors d'une saisie (sous UNIX).
Ctrl-@
> Dans tous les cas, avoir un '\0' en entrée sur un fichier texte est peu
> vraisemblable.
Pas plus invraisemblable qu'un autre caractère. Les outils GNU utilisent
très souvent le NUL comme séparateur de nom de fichier. Voir "find" par
exemple.
--
Éric Lévénez
FAQ de fclc : <http://www.levenez.com/lang/c/faq/>
le 08/11/2009 à 15:51, Éric Lévénez a écrit dans le message
<4af6db0f$0$16708$426a...@news.free.fr> :
>> Par curiosité, j'aimerai bien savoir comment on fait pour envoyer un
>> '\0' lors d'une saisie (sous UNIX).
>
> Ctrl-@
Super ! Je savais que ctrl-a => 1, ctrl-b => 2, ..., ctrl-z => 26 (c'est
facile de s'en souvenir), mais impossible de trouver le 0. Je viens
de m'apercevoir que tout ceci est documenté dans ascii(7).
>> Dans tous les cas, avoir un '\0' en entrée sur un fichier texte est peu
>> vraisemblable.
>
> Pas plus invraisemblable qu'un autre caractère. Les outils GNU
> utilisent très souvent le NUL comme séparateur de nom de fichier. Voir
> "find" par exemple.
Mauvais exemple, find n'enverra pas un '\0' en premier caractère ('\0'
ne peut pas apparaître dans un nom de fichier) et je doute que fgets()
permette de traiter la sortie d'un find -print0 car il est impossible de
savoir si le '\0' fait parti de la chaîne ou a été rajouté par fgets().
De plus, dans son exemple, ça commençait tout de même par un
printf ("Enter date, or blank line to exit.\n\t> ");
ce qui sous-entend que la saisie va ce faire au clavier.
--
Benoit Izac
Voui. En même temps, c'est difficile de s'en passer dans certaines
formations, qui vont devoir aller écrire un peu de C de temps en temps,
sans que ce soit leur boulot.
> Certains
> verront alors le C comme un langage compliqué, oui, c'est possible,
> car la complexité est 'cachée' dans les autres langages. Ici, on est
> proche du fonctionnement réel, donc les choses sont un peu moins
> 'bisounours'... C'est le prix à payer pour avoir du code efficace.
Non, le C est aussi compliqué parce que les gens qui l'ont fait
on commis des erreurs, et que la compatibilit ascendante était une
priorité.
Le fait que char soit parfois signé, parfois non n'a rien à voir
avec les perfs.
La conversion des signés en non-signés lors d'opérations mixtes
signés/non signé n'a rien à voir avec les perfs.
Le fait de garder gets n'a rien à voir avec les perfs.
La sémantique de getc n'a rien à voir avec les perfs.
La conversion implicite des tableaux en pointeur n'a pas grand
chose à voir avec les perfs.
La syntaxe des pointeurs de fonction n'a rien à voir avec les perfs.
L'absence de type booléen facile à utiliser n'a pas grand chose
à voir avec les perfs.
La nécessité de faire un #ifndef/#define n'a rien à voir avec les
perfs.
> Ceci-dit, les E/S en Java, c'est pas d'une simplicité biblique non
> plus... Quand au Pasca standard, ça n'existe carrément pas !
> Heureusement, il y a Turbo Pascal (ou freePascal).
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_mot=130
Donc tu parles des flux, ce truc étrange où ce que tu lis
disparaît, et puis EOF, c'est un entier qu'on ne peut pas coder
sur un char, sauf que c'est -1 en pratique (il va vite y avoir
un étudiant qui va faire un printf("%d",EOF)), mais qu'en fait,
c'est en temps qu'unsigned char qu'il est pas représentable...
> Dans le pire des cas, je suis pret a leur donner un idiome qui marche,
> a utiliser tel quel, quitte a expliquer certains details apres...
J'ai hésité un temps à fournir une mini-bibliothèque:
int NEWBY_readInt();
char NEWBY_readChar();
mais j'ai laissé tombé.
L'idée de candide de faire aucune E/S pendant le premier quart
du cours m'intéressait aussi.
En fait, mon "idiome qui marche", ce fut "on va faire comme
si le monde était gentil pour le moment".
Puis, plus tard, la vraie vie "ça y est, vous venez de faire
exploser la fusée..."
Ben ca c'est exactement mon scanf() avec controle d'erreur rudimentaire.
On leur dit que voila, faut taper un entier suivi par "entree", et que s'ils
font autre chose, va y avoir des bricoles...
(rappel: de toutes facons, on ne peut pas flusher un flux d'entree de
facon portable, donc s'ils veulent des trucs plus funky, va falloir attendre...)
Que veux tu dire par �a n'existe pas ?
Qu'appelles-tu "flusher" ? Il me semblait qu'on avait
scanf("%*[^\n]%*c")
Par rapport a fflush, il manque l'aspect synchro dans le temps.
Lorsque tu fais fflush(stdout), tu t'attends a ce qu'il n'y a plus rien
dans tes tampons, et tout affiche juste apres la ligne en question.
De meme, parfois, tu aimerais bien pouvoir dire "okay, je vire tout ce qui
est en attente sur stdio, la maintenant, parce que je vais interagir
directement avec l'utilisateur, et que ca impose de gicler tout ce qu'il
a entre jusqu'ici".
Manque de bol, il n'y a pas d'equivalent au fflush(stdout) sur un flux
d'entree. Sous Unix, tu peux jouer avec termios, a coup de tcflush, ou
de tcsetattr(...TCSAFLUSH), mais c'est bien evidemment non portable.
Ça fout la trouille ...
> > Dans tous les cas, avoir un '\0' en entrée sur un fichier texte est peu
> > vraisemblable.
>
> Pas plus invraisemblable qu'un autre caractère. Les outils GNU utilisent
> très souvent le NUL comme séparateur de nom de fichier. Voir "find" par
> exemple.
Ce qui n'est pas compatible avec les fonctions C standards de
traitement des chaines... dont fgets() !
Faire entrer un 0 dans un stdin est AMA, un UB ...
Le Pascal standard ne gère les les flux.
Cette opération doit etre faite systématiquement et de manière
'intelligente' après chaque appel à une fonction de saisie. C'est
tout. L'ordre à respecter absolument est :
1 - Saisir
2 - Nettoyer
et pas le contraire comme on le voit trop souvent. On ne peut jamais
présumer ce de qui c'est passé avant, on ne sait donc pas comment
vider le flux. Par contre, après une saisie, on sait parfaitement ce
qu'il y a à faire (ça dépend de la fonction, parfois de la valeur
saisie etc.).
Cette méthode est évidemment portable.
> Ce qui n'est pas compatible avec les fonctions C standards de
> traitement des chaines... dont fgets() !
Si tu commences � confondre stdio.h et string.h ...
fgets ne traite pas de cha�ne (=string en C ie char* termin� par 0).
>
> Faire entrer un 0 dans un stdin est AMA, un UB ...
"AMA" ?? c'est pour un sondage ou quoi ? Si tu crois que c'est un UB
donne chapitres et versets.
Mais �a me para�t hautement improbable, il suffit de penser � une simple
redirection de stdin depuis un fichier dans lequel on a envoy� une suite
de cha�nes, termin�es par un 0 comme il se doit.
>Cette op�ソスration doit etre faite syst�ソスmatiquement et de mani�ソスre
>'intelligente' apr�ソスs chaque appel �ソス une fonction de saisie. C'est
>tout. L'ordre �ソス respecter absolument est :
>1 - Saisir
>2 - Nettoyer
>et pas le contraire comme on le voit trop souvent. On ne peut jamais
>pr�ソスsumer ce de qui c'est pass�ソス avant, on ne sait donc pas comment
>vider le flux. Par contre, apr�ソスs une saisie, on sait parfaitement ce
>qu'il y a �ソス faire (�ソスa d�ソスpend de la fonction, parfois de la valeur
>saisie etc.).
>Cette m�ソスthode est �ソスvidemment portable.
Oui, mais non. Il y a des cas non portables dans les interfaces utilisateur,
pour tout un ensemble de raisons.
En particulier, tu reprends souvent un flux sur lequel tu n'as pas eu la
main, donc il faut parfois pouvoir le nettoyer d'abord.
Typiquement, l'interface Unix dont je parlais (termios) n'est pas faite
selon ton modele du monde, et le resultat n'est pas concu pour interagir
comme tu le voudrais....
Malheureusement d'accord avec candide, pour une fois.
stdin est un simple flux de caracteres. On suppose d'habitude qu'il n'y
a pas de 0 dessus (et tout ce qui concerne les fonctions classiques de
gestion de chaine de caracteres du C fait pareil), mais il n'y a pas
vraiment moyen d'en etre sur.
En regle generale, si on utilise des fonctions telles que fgets,
ca veut dire qu'on a de bonnes chances de perdre des choses si on tombe
sur un '\0' dans le buffer. Si on veut traiter des entrees quelconques
qui peuvent comporter des 0, fgets n'est pas une fonction appropriee...
Avec fgets, on considere qu'il n'y a pas de 0 dans notre entree, et que
s'il y en a, on va juste laisser tomber silencieusement le fragment de
chaine qui suit.
Il n'y a pas d'UB la-dedans: la spec de fgets est parfaitement claire:
on s'arrete seulement sur un '\n', un EOF, ou un buffer tout plein.
On continue au-dela d'un '\0'...
Et quand un programme commence par exemple par :
program Test(input, output);
Tu consid�res que ce ne sont pas des flux ?
Qu'il y ait moins de fonctions pour g�rer les flux en pascal qu'en c je
veux bien (mais elle sont mieux foutues en pascal) mais de l� � dire
qu'il n'y a pas de flux en pascal standard. A moins qu'on n'ait pas la
m�me d�finitions de flux.
Non. Si tu veux une interface utilisateur solide, tu dois distinguer
deux cas tr�s diff�rents :
- soit tu ne contr�les pas exactement ce qui se passe autour, et la
bonne s�quence c'est alors
0 - Nettoyer
1 - Saisir
2 - Nettoyer
pour �viter de se faire polluer par des interactions non d�sir�es.
�videmment c'est d'autant plus important lorsque la saisie est en fait
la confirmation pour une op�ration avec des cons�quences importantes :
un exemple serait la derni�re question pos�e dans l'utilitaire fdisk...
- soit tu contr�les ce qui se passe autour, en particulier si tu es
suppos�ment au milieu de ton programme et que celui-ci est cens� avoir
le focus (donc qu'il ne vient pas de r�aliser une op�ration de six
minutes sans donner aucun ressenti � l'utilisateur... mais je d�rive),
et alors oui la bonne s�quence c'est bien
1 - Saisir
2 - Nettoyer
(ce qui permet � l'utilisateur habitu� d'anticiper dans le tampon
clavier ce qui va arriver).
Par ailleurs, dans une biblioth�que d'entr�es-sorties bien con�ue,
l'op�ration 2-Nettoyer devrait �tre gratuite (d�j� faite) dans le cas
normal d'op�ration : cela �vite une potentielle erreur au programmeur,
et aussi les effets de course, genre le syst�me qui pique le focus entre
les deux op�rations, le passe � un autre fil qui fait une op�ration
diff�rente d'ES, et le repasse un quantum plus tard pour le nettoyage...
qui va mettre un max de boxon, puisqu'� contre-temps !
Antoine
7.19.2p2 : stdin est un flux texte, donc son comportement n'est garanti
que pour les caract�res imprimables, '\t' et '\n'. '\0' ne fait pas
partie de cette liste, donc le comportement n'est pas d�fini.
En POSIX, les choses sont diff�rentes, mais je n'ai pas �t� v�rifi� ce
qui se passe avec \0 dans les flux d'entr�es, je ne serais pas outre
mesure surpris que ce cas soit exclus pour que les impl�mentations aient
le droit d'�tre pourries sur ce point et qu'en m�me temps les
programmeurs �vitent de tabler sur une possibilit� qui est quand m�me
tr�s pi�geuse...
Par ailleurs, une bonne impl�mentation va accepter correctement des \0
en entr�e (et la plupart des *nix aujourd'hui le font), et dnc un bon
programme se doit de pr�voir ce qui peut se passer quand on lui envoie
des \0 impr�vus dans le flux.
Antoine
En fait, gets() est � l'origine du plus gros bogue de l'internet (le ver
de Morris, � cause d'un gets() par vraiment s�r dans l'utilitaire finger
de BSD).
Dans les ann�es 85-94, le contexte �tait alors � des machines bien moins
puissantes, les programmes �taient g�n�ralement consid�r�s comme
uniform�ment ou g�n�ralement b�n�fiques, et les ravages du ver de Morris
ont frapp� les esprits mais cela n'a pas �t� interpr�t� par tout le
monde comme la n�cessit� de revoir � fond toutes les m�thodes de
programmation, surtout en C.
En 2001 puis en 2003 les programmeurs de Microsoft ont subi deux chocs
similaires (Code Red puis Slammer), qui les ont conduits � revoir en
profondeur leurs mani�res de programmer.
Aujourd'hui, les choses ont pas mal chang�es : ainsi le comit� C (dont
pon peut penser ce que l'on veut, mais il y a l� des gens qui savent un
peu de quoi ils parlent) est aujourd'hui tr�s engag� dans la volont� de
rendre C � s�r � (je ne me prononcerais pas sur la qualit� de leurs
solutions concr�tes, par exemple aux probl�mes de fgets()).
Antoine
S'ils etaient engages de facon pratique, ils auraient integre strlcpy et
strlcat a la prochaine version de la norme, au lieu des especes de monstres
de fonctions de chaine validees, que personne ne va utiliser tellement elles
sont lourdingues et malcommodes (et en plus, ne correspondent a aucun prior
art).
> 7.19.2p2 : stdin est un flux texte, donc son comportement n'est garanti
> que pour les caract�res imprimables, '\t' et '\n'. '\0' ne fait pas
> partie de cette liste, donc le comportement n'est pas d�fini.
J'aime bien le "no new-line character is immediately preceded by space
characters"...
En 7.19.3p7 il est indiqu� que stdin est un flux de texte au d�marrage,
mais comme on peut changer cela juste apr�s, qu'est-ce qui oblige dans
la norme stdin a �tre un flux texte apr�s le d�marrage du programme ?
--
�ric L�v�nez
En fait si, c'est directement d�riv� de ce que MS a fait dans VC++ 2005.
J'ai pas dit que je trouvai(s) cela extraordinaire, pas taper !
Antoine
C'est d� aux impl�mentations qui ne savent enregistrer que des
enregistrements de taille fixe (genre mainframes des ann�es 70), et qui
utilisent l'espace comme caract�re de bourrage : le code de stdio est
alors pri� de supprimer tout espace en fin de ligne ; ce qui fait qu'il
n'est pas possible de garder de mani�re perenne et portable des
caract�res espace en fin de ligne.
D'un point de vue d'interface, je trouve cela plut�t bien, les espaces
en fin de ligne peuvent souvent �tre un probl�me et rarement une solution.
> En 7.19.3p7 il est indiqu� que stdin est un flux de texte au d�marrage,
> mais comme on peut changer cela juste apr�s, qu'est-ce qui oblige dans
> la norme stdin a �tre un flux texte apr�s le d�marrage du programme ?
7.19.5.4p3 dit que l'effet et les conditions de r�ussite de
freopen(NULL, mode, stream) sont d�finies par l'impl�mentation, ce qui
permet � une impl�mentation de ne pas permettre cette utilisation du
tout, et fait que tu ne peux pas te pr�valoir de cette fonctionnalit� de
mani�re portable.
Mais peut-�tre existe-t-il d'autres mani�res de changer le mode de stdin
Antoine
Ah ben je reformule ailleurs: c'est juste extraordinairement lourdingue
et mal foutu. M'etonne pas que ca vienne de chez crosoft.
Est-ce que fgets a un comportement différent selon l'OS (Djgpp
Microsoft xp ou Linux Gcc) ? Parce que sous Windows le pointeur se
trouve 1 position supplémentaire après la fin de la chaîne saisie avec
fgets ce qui va en contradiction avec le premier post (peut-être j'ai
écrit le premier post sous linux et c'est pourquoi aujourd'hui je ne
me retrouve qu'une position après au lieu de 2.
Si quelqu'un peut m'aider. J'ai le sentiment de passer beaucoup de
temps sur point particulier qui ne devrait pas avoir d'importance pour
un débutant car à un niveau plus élevé, la saisie se fait de manière
très rigoureuse avec flush ou getchar =''\n' ....
Merci,
Pascal
> Si quelqu'un peut m'aider. J'ai le sentiment de passer beaucoup de
> temps sur point particulier qui ne devrait pas avoir d'importance pour
> un d�butant car � un niveau plus �lev�, la saisie se fait de mani�re
> tr�s rigoureuse avec flush ou getchar =''\n' ....
Tu t'es laiss� noy� dans les querelles un peu vaines de sp�cialistes qui
on fait d�river le fil loin, tr�s loin, de ton pb initial. Si tu
pouvais (re)poster l'essentiel du code pour qu'on rappelle correctement
ton histoire de -1 / -2? Parce que selon moi ton pb est simple et la
solution avait �t� donn�e tr�s tot dans le fil: buffer contenant
"abcd\n\0" et donc d est � l'offset -2 par rapport � la position du '\0'.
sam.
Mais quel pointeur ?
> Si quelqu'un peut m'aider. J'ai le sentiment de passer beaucoup de
> temps sur point particulier qui ne devrait pas avoir d'importance pour
> un débutant car à un niveau plus élevé, la saisie se fait de manière
> très rigoureuse avec flush ou getchar =''\n' ....
Tout est là :
> Tout est là :
>
Et on se demande comment il y en a encore qui posent des questions sur
le C ...
> http://www.bien-programmer.fr/notes.php#saisie
Langage C ou langage de bois ? Petit extrait :
"Saisie de données par un opérateur (stdin)"
"acquérir des données en provenance d'un opérateur"
"ce qui n'est évidemment pas le cas avec un opérateur humain"
Non mais t'as appris le C en corée du Nord ou quoi ?
Autres extraits de notre Monsieur Jourdain du C :
"Mode conversationnel" ?????????????? Ça me fait irrésistiblement penser
aux "commodités de la conversation " :))
"stdin est connecté à la partie 'clavier' d'un périphérique console"
J'ai une question m'sieur : où c'est qu'il est le port usb de vo't stdin
siouplait ?
"C'est insuffisant pour saisir autre chose qu'un simple <ENTER>."
Ah bon ? getchar() ne peut saisir que <ENTER> ? K&R ne savent donc pas
utiliser getchar().
"S'il faut saisir une valeur numérique, celle-ci sera d'abord saisie
sous forme de ligne, puis traduite par la fonction appropriée (strtol(),
strtoul(), strtod()) ou sscanf()) avec le filtre approprié : "
Ah ouais, il te faut 37 lignes de code pour saisir un malheureux entier
? Tu cherches à dégoûter les gens d'apprendre le C ? ou à faire croire
que t'es très très fort ?
Tiens, justement, regardons ton code :
//-------------------------------------
char temp[20];
do
{
char saisie[20];
/* Et quelques pédanteries plus loin, on lit ceci : */
ret = sscanf (saisie, "%[0-9-]s", temp);
//-------------------------------------
N'as-tu pas appris que sscanf() avec %s ajoute automatiquement un zéro à
la fin et donc que temp doit avoir au moins une case de plus que saisie ?
Et ton scanlist 0-9, il est portable ? Mon compilateur ne dit rien mais
la Norme dit :
If a - character is in the scanlist and is not the first, nor the second
where the first character is a ^, nor the last character, the behavior is
implementation-defined.
Bonjour,bsr,
Il s'agit de ce post plus haut dans cette discussion :
int main(void)
{
char Ch[101] ;
char *p1, *p2 ;
int Pali ;
p2 = Ch ;
printf("\nEntrez une ligne de texte (max.100 caract.) :
\n") ;
fgets(Ch, 101, stdin) ;
for ( p2 ; *p2 ; p2++ )
;
p2 = p2-2 ;
...
Avec windows, pour un autre exercice, je me suis retrouvé avec une
situation identique mais avec un résultat différent en ce sens que
p2-1 ou p2-- au lieu de p2=p2-2.
Il s'agit peut-être d'une confusion.
Pascal
> for ( p2 ; *p2 ; p2++ )
> ;
> p2 = p2-2 ;
Cette ligne est fausse. Si on appuie directement sur entree, le tampon
contiendra seulement un caractere: tu te retrouves avec p2 qui vaut
&(Ch[-1]). Tu es dans le domaine du comportement indefini...
Commence par corriger tout ca, on en reparle ensuite...
C'est normal, sous certains OS, dont ceux de Microsoft, la fin de ligne
contient deux caract�res ("\n\r" ou le contraire) au lien d'un seul
("\n") sous Unix.
Quelque soit l'OS, si s est un pointeur vers le dernier caract�re non
nul de la chaine, il suffit de v�rifier son contenu pour effacer les
caract�res de fin de ligne :
while (*s == '\n' || *s == '\r') {
*s-- = '\0';
}
--
Richard
Euh non, ce que tu decris, c'est une implementation "non conforme" ou du
moins qui a decide de ne pas implementer la distinction flux binaire/flux
texte.
D'apres 7.19.2, sur un flux texte, le systeme peut convertir les choses
pour que la lecture te montre un '\n' pour une fin de ligne.
D'apres 7.19.3.7, stdin, stdout et stderr sont des flux textes.
Si tu es sur une implementation qui distingue effectivement entre fopen "t"
et fopen "b", ben c'est ni plus ni moins qu'un bug.
(de facon assez drole, il *n'existe pas de facon standard* d'obtenir un flux
binaire qui correspond a stdin. De facon encore plus drole, d'apres 7.19.2.2,
il y a tres peu d'implementations standard dans la nature... c'est frequent
d'avoir des espaces avant le '\n', par exemple).
Tous les Unix sont donc non conformes ?
> D'apres 7.19.2, sur un flux texte, le systeme peut convertir les choses
> pour que la lecture te montre un '\n' pour une fin de ligne.
>
> D'apres 7.19.3.7, stdin, stdout et stderr sont des flux textes.
>
> Si tu es sur une implementation qui distingue effectivement entre fopen "t"
> et fopen "b", ben c'est ni plus ni moins qu'un bug.
Sauf que si le fichier vient d'un syst�me conforme qui "peut convertir
les choses" et est lu sur un syst�me conforme qui ne convertit rien ou
d'une autre mani�re, il n'y a pas de bug dans l'impl�mentation mais des
probl�mes de lecture quand m�me.
--
Richard
> De facon encore plus drole, d'apres 7.19.2.2, il y a tres peu
> d'implementations standard dans la nature... c'est frequent d'avoir des
> espaces avant le '\n', par exemple).
Quel probleme y a-t'il?
Whether space characters that are written out immediately before a new-line character
appear when read in is implementation-defined.
Ma comprehension est qu'un programme strictement conforme ne peut pas
dependre du fait qu'ils soient conserves, mais rien n'empeche une
implementation d'en supprimer, de conserver exactement ceux qui existent ou
d'en ajouter. (Je m'attends plutot aux deux premieres eventualites).
A+
--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Non.
> Parce que sous Windows le pointeur se
> trouve 1 position suppl�mentaire apr�s la fin de la cha�ne saisie
Huh ? Peux-tu pr�ciser le compilateur utilis� et les options de
compilations, s'il te pla�t.
Antoine
Euh, elle ne devrait pas (vu du programme, �videmment ; vu de
l'ext�rieur, c'est "\15\12").
Si c'est le cas, c'est probablement que tu utilises des options de
compilations (comme ajouter binmode.o[bj]) qui �loigne ton
impl�mentation de la norme (mais permettent de porter plus facilement
certains utilitaires issus d'Unix o� les programmeurs fain�ants ont omis
de mettre "rb", "r+b" ou "wb" pour les cha�nes de mode d'ouverture des
fichiers contenant des flux binaires).
> Quelque soit l'OS, si s est un pointeur vers le dernier caract�re non
> nul de la chaine, il suffit de v�rifier son contenu pour effacer les
> caract�res de fin de ligne :
>
> while (*s == '\n' || *s == '\r') {
> *s-- = '\0';
> }
Oui ; m�me si c'est inutile au vu de la norme, c'est en effet une
mani�re solide de faire.
Antoine
... et sur lesquels les formats de fichiers concernes sont effectivement
differents, ce qui est le cas sur windows.
C'est surtout une maniere fausse, ca s'appelle un buffer underflow....
Tu as raison, j'ai lu un peu trop vite, j'�tais sur l'id�e du paragraphe
du dessus, qui traitait des caract�res CR et LF...
Antoine
Non. Vu du c, et dans un flux ouvert en mode texte, une fin de ligne
est toujours '\n', quelque soit ce que fait l'OS (\n, \r ou \r\n).
Si le flux est ouvert en mode texte, le test de '\r' est inutile à
moins qu'il s'agisse d'être compatible evec des implémentations non
conformes... Si c'est le cas, on se s'en sort plus...
.
Sous Windows : djgpp
Linux : gcc
pas d'option de compilation que je sache (niveau débutant) : gcc
fichier.c -o fichier.exe ou gcc fichier.c -o fichier
Non conformes ? ce n'est pas la question. La Norme dit :
5.2.2 Character display semantics
(...)
Each of these escape sequences shall produce a unique
implementation-defined value which can be stored in a single char object.
The external representations in a text file need not be identical to the
internal representations, and are outside the scope of this
International Standard.
et le Rationale de confirmer :
Although ISO/IEC 646 deprecates the combination of the motion to the
next line with a motion to the initial position on the line, the C
Standard uses new-line to designate the end-of-line internal code
represented by the escape sequence '\n'. While this ambiguity is perhaps
unfortunate, use of the term in the latter sense is nearly universal
within the C community. But the knowledge that this internal code has
numerous external representations depending upon operating system and
medium is equally widespread.
> pas d'option de compilation que je sache (niveau d�butant) : gcc
> fichier.c -o fichier.exe ou gcc fichier.c -o fichier
Quel que soit le niveau, il est impensable de ne pas compiler avec
options allum�es (minimum -Wall -Wextra), c'est vraiment le B-A-BA. Tout
cours (oral ou �crit) qui n'insiste pas sur cela doit faire l'objet des
plus vives suspicions.
Est-ce que tu comprends la différence entre représentation externe (ce
qu'il y a physiquement dans le flux) et représentation interne (ce que
"voit" le C) ?
En binaire, il n'y a pas de différence, mais en mode texte il peut y
avoir des différences, selon le système :
Je le répète, le caractère '\n' vu du C produit dans un flux texte
soit
LF (en C '\n') sous Unix et ses clones,
CR (en C '\r') sous Mac non unixoïde.
CRLF (en C '\r''\n') sous DOS et Windows.
http://www.bien-programmer.fr/notes.php#fichiers
Si tu veux travailler avec les valeurs réelles, utilise le mode
binaire (c'est ce qu'on fait pour écrire un convertisseur comme
unix2dos etc.).
> <...>But the knowledge that this internal code has
> numerous external representations depending upon operating system and
> medium is equally widespread.
Ben y'a encore du boulot ... Chez certains, ça a du mal çà rentrer...
Exactement.
> evec des impl�mentations non conformes...
> Si c'est le cas, on se s'en sort plus...
Il ne s'agit pas d'impl�mentation non conformes.
Une impl�mentation conforme est capable de traduire tous les programmes
strictement conformes ; mais ce n'est pas exclusif (heureusement, car
peu de programmes sont strictement conformes) : une telle impl�mentation
peut AUSSI traduire des programmes non strictement conformes (la norme
les appelle programmes conformes, mais je vais laisser tomber ce terme
qui ne fait que rajouter � la confusion).
Et pour ce faire, les impl�menteurs programment des comportements, et
les utilisateurs jouent avec des options, qui peuvent parfois avoir des
effets surprenants ; il convient �MHA de se pr�munir dans la mesure du
possible des cons�quences n�gatives.
Une telle option dans le monde MSDOS est celle qui permet de d�finir ce
qu'est une fin de ligne, savoir si c'est CR+LF (normal) ou LF
(compatible avec d'autres environnements, par exemple pour �crire des
filtres); et avec la seconde option, tu te retrouves avec des \12 dans
les flux texte...
Antoine
Je n'ai pas fait le test avec Linux.
Mais je suis aller r�installer mon vieux DJGPP de derri�re les fagots
(d'apr�s les dates de fichiers, cela faisait plus de quatre ans que cela
ne m'�tait pas arriv� ; d'ailleurs, ce serait bien que tu te mettes �
jour, DJGPP n'est pas un compilateur pour Windows mais pour MS-DOS, un
produit certes tr�s utile mais dont le d�veloppement cessa en 1993) ;
et donc avec la version 2.04, sans utiliser d'options de compilation
sp�cifiques, j'ai le m�me comportement avec DJGPP qu'avec les autres :
la fin de ligne est constitu�e d'un seul caract�re, \n.
Voir ci-apr�s un programme de test compilable ; je n'ai pas r�ussi � lui
faire montrer des CR, m�me avec des acrobaties du genre
ECHO toto | prgm
prgm <turlututu
TYPE fichier | prgm
Je n'ai pas corrig� le d�bordement par le d�but (underflow), attention
avec les lignes vides...
Antoine
--<couper ici>--
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
int putcx(int c)
{
switch(c) {
case '\a': printf("\\a"); break;
case '\b': printf("\\b"); break;
case '\t': printf("\\t"); break;
case '\n': printf("\\n"); break;
case '\v': printf("\\v"); break;
case '\f': printf("\\f"); break;
case '\r': printf("\\r"); break;
case '\'': printf("\\'"); break;
case '\"': printf("\\\"");break;
case '\\': printf("\\\\");break;
/* les trigraphes et par cons�quent \? sont laiss�s
* comme exercice au lecteur attentif... */
default:
if( isprint(c) )
putchar(c);
else
printf("\\%03o",(unsigned char)c);
break;
}
return c;
}
#include <stdio.h>
int main(void)
{
char Ch[101] ;
char *p1, *p2 ;
int Pali ;
p2 = Ch ;
printf("\nEntrez une ligne de texte (max.100 caract.) : \n") ;
assert(NULL!=
fgets(Ch, 101, stdin)
);
printf("fgets donne <%.50s>, len=%u, soit \"",Ch,(unsigned)strlen(Ch));
for(p1=Ch;*p1;)putcx(*p1++); putcx(*p1);
for ( p2 ; *p2 ; p2++ )
;
printf("\"\np2 avance de %d, p2-2 pointe sur ", p2-Ch); putcx(p2[-2]);
printf(", p2-1=>"); putcx(p2[-1]);
printf(", p2=>"); putcx(*p2);
p2 = p2-2 ;
printf("\np2 = p2-2, p2 pointe maintenant sur "); putcx(*p2);
printf(", p2+1 pointe sur "); putcx(p2[1]);putchar('\n');
return 0;}