OVH Cloud OVH Cloud

printf et scanf

102 réponses
Avatar
Pascal
Bonjour,

J'ai des pb bizarres : dans une fonction j'ai besoin que l'utilisateur
me donne plusieurs infos, je fais donc des printf("question") et des scanf.

Sur les 3 premieres questions, ca marche bien, sur les 2 dernières, le
programme les affiche, mais le scanf ne semble pas marcher. Pourtant le
scanf recup sur les 5 fois un char *. C'est un bug? D'ailleurs si je ne
mets pas de \n sur le printf, parfois c'est encore pire...
voici un ex :
char *nom;
printf("nom\n");
scanf("%s", nom);
etc .. prenom, adress, tel, mail...

Parfois le scanf est comme sauté. Dans une autre fonction, je fais la
meme chose : une question, une reponse. Il m'affiche bien la question,
mais le scanf comme s'il était inactif, le programme ne me laisse pas
répondre, et passe à l'instruction suivante...

10 réponses

Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:4188b604$0$15755$

Laisse tomber scanf(). Pas une fonction de débutant (ni même
d'expérimenté...)


Bah... il suffit de lire la doc...


Il ne suffit pas de la lire pour la comprendre...
Il ne suffit pas de la comprendre pour se servir de scanf sans se tromper
Il ne suffit pas d'utiliser correctement scanf pour faire des saisies réellement
contrôlées

Je suis bien d'accord avec Emmanuel, laisse tomber scanf.

Chqrlie.


Avatar
Charlie Gordon
"Hamiral" wrote in message
news:4187e613$0$15756$
fgets(nom, NOMBRE_DE_CARACTERE_MAX, stdin);


Dans ce cas-là il faudra déclarer nom comme suit :
char nom[NOMBRE_DE_CARACTERES_MAX + 1];
ou
char* nom = malloc( NOMBRE_DE_CARACTERES_MAX + 1 );

Sinon fgets va faire boum quand elle va ajouter le '' en fin de chaîne ...


Faux : le parametre de fgets est la taille du tableau destination. fgets n'y
stockera pas plus de taille-1 chars et toujours un NUL final.
En revanche, si tu veux que la saisie puisse autoriser des noms de longueur
NOMBRE_DE_CARACTERES_MAX, il faut allouer le buffer de saisie avec 2 caractères
supplémentaires : 1 pour le 'n' et 1 autre pour le '' final.

char buf[NOMBRE_DE_CARACTERES_MAX + 2];
if (fgets (buf, sizeof(buf), fp)) {
/* enlever le 'n' final */
char *p;
if ((p = strchr(buf, 'n')) != NULL)
*p = '';
/* stocker le nom ... */
}

A noter que l'absence de 'n' dans le buffer après fgets peut avoir 2 causes :
- Le fichier n'est pas terminé par un 'n', et nous venons de lire la dernière
ligne.
- La ligne était top longue pour le buffer, et une partie de la ligne n'a pas
été lue. C'est un problème qu'il faut gérer ici parce que sinon la fin de la
ligne sera lue dans la lecture suivante causant un décalage regrettable entre
les données et l'attente du programme.

Même un problème aussi simple est compliqué à implémenter correctement en C.

Chqrlie.


Avatar
Charlie Gordon
"Anthony Fleury" wrote in message
news:4187e28b$0$30787$

Pourquoi ? et bien parce que scanf a laissé le caractère 'n' qui est tapé
derrière le nombre pour valider la première saisie, et quand il regarde
l'entrée standard il va retrouver ce caractère et le prendre pour la saisie
d'après. Conclusion il va « ignorer » (du point de vue de l'utilisateur) la
saisie d'après :-)

Une solution pour éviter ceci (si tu persistes et/ou est obligé d'utiliser
scanf()) est de vider le buffer d'entrée après toute saisie avec scanf() :

while(fgetc(stdin) != 'n');

Achtung Minen !

Ceci est une boucle infinie dans le cas ou la fin de fichier est rencontrée sans
qu'un 'n' soit lu.

Il faut écrire :

while ((c = getc(fp)) != EOF && c!= 'n')
continue;

Chqrlie.

PS: le continue redondant est là pour éviter le statement vide, que je préfère
ne jamais utiliser, en particulier sur la même ligne que le while ou le for.

Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:4187e3b3$0$15755$

Saisir une ligne avec scanf est toutefois possible. P.ex. :

char adresse[81];
int n_lu;
printf("adresse >"); fflush(stdout);
n_lu = scanf("%80[^n]%*[^n]", adresse);
getchar();
if (n_lu != 1) {
/* message d'erreur */
}

La signification de tout ceci :
80[^n] lit tous les caractères jusqu'au (et à l'exclusion de)
fin-de-ligne ('n'), et à concurrence de 80 caractères, taille maximale
d'une chaine stockable dans adresse, tel qu'il est défini ici.
Le second %*[^n] sert à lire le reste ligne (si elle est trop longue),
tout en ne stockant pas le résultat.
Enfin, le getchar() qui suit lit le caractère 'n' qu'on avait pas
encore lu.
Enfin, on vérifie avec n_lu qu'une adresse a bien été entrée.


C'est bien gentil, mais comment fait-on pour spécifier la taille du buffer de
saisie si celle-ci n'est pas une constante explicite ?
Si c'est une macro, c'est déjà acrobatique et illisible d'utiliser le
préprocesseur pour cela.
Si c'est une expression, ça va pas le faire, et si cette expression n'est pas
constante...

Il y a bien une solution qui consiste à construire la chaine de format de scanf
avec un snprintf, mais j'aurais préféré une syntaxe plus directe du genre "%*s"
de printf qui dans scanf a une toute autre signification (encore une incohérence
regrettable de la libc, que la norme n'a pas comblée, préférant sans doute
dégoiser sans fin sur les conversions multibyte wchar_t avec le prefixe l ;-)

Par ailleurs, le texte de la norme contient un exemple que je reproduis ici :

EXAMPLE 2 The call:
#include <stdio.h>
/* ... */
int i; float x; char name[50];
fscanf(stdin, "%2d%f%*d %[0123456789]", &i, &x, name);
with input:
56789 0123 56a72
will assign to i the value 56 and to x the value 789.0, will skip 0123, and will
assign to name the
sequence 56. The next character read from the input stream will be a.

C'est quand même incroyable que des saletés de ce genre soient citées en exemple
dans la norme : ce code contient une erreur patente : la lecture de 'name' n'est
pas contrôlée. Des données suffisamment bien travaillées permettront de faire
faire à peu près n'importe quoi au programme qui execute ce code. Le format
"%49[0123456789]" aurait été nécessaire ici, ou éventuellement "%49[0-9]". De
toute façon le texte de l'explication du 'maximum field width' n'est pas
suffisamment clair, et il n'est pas repris pour l'explication des formats 's' et
'['.

Conclusion : laisser tomber cette daube de scanf.

Chqrlie.

Avatar
Charlie Gordon
"Pascal" wrote in message
news:4187f796$0$6275$
Si je tape 3, j'ai ça sur mon écran :
...

0 - Quitter le programme

Rep : 3
Entrer votre nom : Entrer votre prenom :

En gros, la première réponse, "nom" a été sauté, et il est directement
passé au prenom. Ensuite (adresse,tel, mail) ça se passe normalement. Où
est l'erreur??


A mon avis, tu ne "tapes" pas 3, mais 3 <entree>
Je ne sais pas comment tu lis le nombre dans ton programme, mais tu laisses
vraisemblablement le 'n' dans le buffer d'entrée et la saisie suivante (lecture
du nom) lit directement ce 'n' et passe à la suite.

Chqrlie.

Avatar
Laurent Deniau
Charlie Gordon wrote:
"Richard Delorme" wrote in message
news:4187e3b3$0$15755$


Saisir une ligne avec scanf est toutefois possible. P.ex. :

char adresse[81];
int n_lu;
printf("adresse >"); fflush(stdout);
n_lu = scanf("%80[^n]%*[^n]", adresse);
getchar();
if (n_lu != 1) {
/* message d'erreur */
}

La signification de tout ceci :
80[^n] lit tous les caractères jusqu'au (et à l'exclusion de)
fin-de-ligne ('n'), et à concurrence de 80 caractères, taille maximale
d'une chaine stockable dans adresse, tel qu'il est défini ici.
Le second %*[^n] sert à lire le reste ligne (si elle est trop longue),
tout en ne stockant pas le résultat.
Enfin, le getchar() qui suit lit le caractère 'n' qu'on avait pas
encore lu.
Enfin, on vérifie avec n_lu qu'une adresse a bien été entrée.



C'est bien gentil, mais comment fait-on pour spécifier la taille du buffer de
saisie si celle-ci n'est pas une constante explicite ?
Si c'est une macro, c'est déjà acrobatique et illisible d'utiliser le
préprocesseur pour cela.
Si c'est une expression, ça va pas le faire, et si cette expression n'est pas
constante...

Il y a bien une solution qui consiste à construire la chaine de format de scanf
avec un snprintf, mais j'aurais préféré une syntaxe plus directe du genre "%*s"
de printf qui dans scanf a une toute autre signification (encore une incohérence
regrettable de la libc, que la norme n'a pas comblée, préférant sans doute
dégoiser sans fin sur les conversions multibyte wchar_t avec le prefixe l ;-)

Par ailleurs, le texte de la norme contient un exemple que je reproduis ici :

EXAMPLE 2 The call:
#include <stdio.h>
/* ... */
int i; float x; char name[50];
fscanf(stdin, "%2d%f%*d %[0123456789]", &i, &x, name);
with input:
56789 0123 56a72
will assign to i the value 56 and to x the value 789.0, will skip 0123, and will
assign to name the
sequence 56. The next character read from the input stream will be a.

C'est quand même incroyable que des saletés de ce genre soient citées en exemple
dans la norme : ce code contient une erreur patente : la lecture de 'name' n'est
pas contrôlée. Des données suffisamment bien travaillées permettront de faire
faire à peu près n'importe quoi au programme qui execute ce code. Le format
"%49[0123456789]" aurait été nécessaire ici, ou éventuellement "%49[0-9]". De
toute façon le texte de l'explication du 'maximum field width' n'est pas
suffisamment clair, et il n'est pas repris pour l'explication des formats 's' et
'['.

Conclusion : laisser tomber cette daube de scanf.


Je suis d'accord que ca aurait put etre mieux pense. Mais cela doit tout
de meme etre pratique pour que C++ (Boost) et Java (PrintStream)
cherchent a refaire la meme chose (en plus securise certes).

J'ai toujours ete tres etonne de voir le manque de sysmetrie dans les
I/O du C, tant sur les familles de fonctions que sur les formats
utilises. En particulier ca n'aurait pas ete tres difficile de faire en
sorte que l'ensemble des fonctions soit sysmetrique et complet. C99 a
travaille dans ce sens, mais c'est pas suffisant...

a+, ld.


Avatar
Antoine Leca
En cmfv8i$pqd$, Charlie Gordon va escriure:
Par ailleurs, le texte de la norme contient un exemple que je
...

fscanf(stdin, "%2d%f%*d %[0123456789]", &i, &x, name);
...

C'est quand même incroyable que des saletés de ce genre soient citées
en exemple dans la norme : ce code contient une erreur patente :


Laquelle ?

la lecture de 'name' n'est pas contrôlée.


Ce n'est pas une erreur (à ma connaissance).
Les exemples de la norme ne sont pas des exemples de bon usage du langage ou
de bon goût: ce sont des exemples de comportement attendu ou pas; de ce fait
certains sont tordus et d'autres carrément incompréhensibles.

Des données suffisamment bien travaillées permettront de faire
faire à peu près n'importe quoi au programme qui execute ce code.


On s'en moque. En fait, au vu de cet exemple, le programmeur normal devrait
être effrayé par la complexité de scanf et donc fuir, ce qui est plutôt
sain, non ?

éventuellement "%49[0-9]".


Non légal.


Conclusion : laisser tomber cette daube de scanf.


La daube, c'est bon. Et plus elle est élaborée, meilleur c'est. ;-)


Antoine

Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

Je suis d'accord que ca aurait put etre mieux pense. Mais cela doit tout de
meme etre pratique pour que C++ (Boost) et Java (PrintStream) cherchent a
refaire la meme chose (en plus securise certes).


Pour les sorties, l'interet principal de l'utilisation des formats est
l'internationalisation et la possibilite de mettre les parametres dans
des ordres differents suivant les langages.

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Emmanuel Delahaye
Charlie Gordon wrote on 05/11/04 :
Il faut écrire :

while ((c = getc(fp)) != EOF && c!= 'n')
continue;

le continue redondant est là pour éviter le statement vide, que je préfère
ne jamais utiliser, en particulier sur la même ligne que le while ou le for.


Je mets

while ((c = getc(fp)) != EOF && c!= 'n')
{
}

En fait, je mets toujours les {}

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"C is a sharp tool"

Avatar
Richard Delorme
"Richard Delorme" wrote in message
news:4187e3b3$0$15755$


Saisir une ligne avec scanf est toutefois possible. P.ex. :

char adresse[81];
int n_lu;
printf("adresse >"); fflush(stdout);
n_lu = scanf("%80[^n]%*[^n]", adresse);
getchar();
if (n_lu != 1) {
/* message d'erreur */
}

La signification de tout ceci :
80[^n] lit tous les caractères jusqu'au (et à l'exclusion de)
fin-de-ligne ('n'), et à concurrence de 80 caractères, taille maximale
d'une chaine stockable dans adresse, tel qu'il est défini ici.
Le second %*[^n] sert à lire le reste ligne (si elle est trop longue),
tout en ne stockant pas le résultat.
Enfin, le getchar() qui suit lit le caractère 'n' qu'on avait pas
encore lu.
Enfin, on vérifie avec n_lu qu'une adresse a bien été entrée.



C'est bien gentil, mais comment fait-on pour spécifier la taille du buffer de
saisie si celle-ci n'est pas une constante explicite ?
Si c'est une macro, c'est déjà acrobatique et illisible d'utiliser le
préprocesseur pour cela.
Si c'est une expression, ça va pas le faire, et si cette expression n'est pas
constante...

Il y a bien une solution qui consiste à construire la chaine de format de scanf
avec un snprintf, mais j'aurais préféré une syntaxe plus directe du genre "%*s"
de printf qui dans scanf a une toute autre signification (encore une incohérence
regrettable de la libc, que la norme n'a pas comblée, préférant sans doute
dégoiser sans fin sur les conversions multibyte wchar_t avec le prefixe l ;-)


Il me semble qu'il y a des discussions pour utiliser %.*s, voire un
autre opérateur symétrique avec printf.

[...]

Conclusion : laisser tomber cette daube de scanf.


Des gens pensent le contraire, et qu'il faut laisser tomber fgets() en
faveur de scanf :

http://groups.google.fr/groups?threadmÉpifa%248jn%244%40sunnews.cern.ch

--
Richard