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

1 2 3 4 5
Avatar
Anthony Fleury
Pascal wrote:

Je viens de tester le fgets, et j'ai les même soucis.
[...]

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??


Et quelle est la fonction qui n'est pas montrée ici et qui sert à prendre la
réponse (ici 3) dans l'entrée standard, et y laisse un 'n' dedans ?? En
gros, la gestion du menu pose le même problème que tout à l'heure à mon
avis, quand tu prends le 3 tu tapes sur ton clavier '3' 'n' et ca laisse
le 'n' que fgets() se charge de prendre pour lui et pour sa fin de
chaine !

Anthony
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra

Avatar
Pascal
Bon je vais essayer d'être plus clair et simple :

+ dans une fonction j'ai ça :
void creerAgenda()
{
[...]
printf("Entrer le nom de l'agenda : ");
fgets(nom_agenda, TAILLE_NOM_AGENDA, stdin);
...

+ dans mon programme principale, qui appelle les sous fonctions, j'ai un
switch qui me permet de choisir l'action :

switch (rep)
{
[...]
case 2 :
creerAgenda(); <---------- là, le programme saute le fgets
break;
case 3 :
printf("Entrer votre nom : ");
fgets(nom, TAILLE_NOM, stdin);
printf("Entrer votre prenom : ");
fgets(prenom, TAILLE_NOM, stdin);
printf("Entrer votre adresse : ");
fgets(adresse, TAILLE_ADRESSE, stdin);
printf("Entrer votre tel : ");
fgets(tel, TAILLE_TEL, stdin);
printf("Entrer votre mail : ");
fgets(mail, TAILLE_MAIL, stdin);
[...]

Je tape 2 (pour aller dans le case 2) :
il va bien dans la fonction creerAgenda.
sur l'ecran, ça m'affiche à la vitesse de l'eclair la phrase "Entrer
le nom de l'agenda", mais je peux pas rentrer ma réponse.
Et je reviens au switch...

Si je tape 3, j'ai ça sur mon écran :

Rep : 3
Entrer votre nom : Entrer votre prenom : pascal
Entrer adresse : 20 rue
Entrer tel : 00000000
[..]

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??
Avatar
Emmanuel Delahaye
Pascal wrote on 02/11/04 :
Bon je vais essayer d'être plus clair et simple :


<...>

Si je tape 3, j'ai ça sur mon écran :

Rep : 3
Entrer votre nom : Entrer votre prenom : pascal
Entrer adresse : 20 rue
Entrer tel : 00000000
[..]

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??


Relis la réponse d'Antony Fleury.

--
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
Anthony Fleury
Pascal wrote:

J'ai essayé fgets, ça ne marche pas non plus. Il me saute le fgets :


Normal ca doit être la même erreur !

printf("Entrer votre nom : ");//param est une struct qui contient les
champs nom, prenom etc...
fgets(param.nom, TAILLE_NOM, stdin);


[...]

et sur mon ecran j'ai ça :
**************************************
Agenda Répartie
**************************************
Choisissez le numéro :
1 - Lister les agendas disponibles
2 - Créer un agenda
3 - Insérer un contact
4 - Rechercher un contact
5 - Supprimer un contact

0 - Quitter le programme

Rep : 3


Qui se charge de prendre cette réponse ? quel est l'état du buffer d'entrée
après que le 3 soit pris ?

Entrer votre nom : Entrer votre prenom :

Et la je peux rentrer le reste des infos normalement(prenom, mail,
tel...), mais pour le nom, je n'ai rien rentré. Il est tout de suite
passé au prenom.


La solution fgets() est bien car d'une part elle permet de contrôler le
nombre de caractères pris, et donc l'utilisation du buffer, mais aussi
parce qu'elle laisse le buffer d'entrée vide si jamais tu l'utilises bien
(en gros si il n'y a pas d''n' dans ta chaine, le buffer d'entrée n'est
pas vide et ton buffer n'était pas assez grand, à toi d'agir). Cependant,
si jamais quelqu'un (comme scanf sur un int) laisse un 'n' dans le buffer
d'entrée, vu que fgets() s'arrête au premier 'n' rencontré, elle
s'arrêtera à cet 'n' et le même problème réapparait.
En gros et en simplifié, toute utilisation de scanf devrait être suivi d'un
vidage du buffer d'entrée pour le laisser dans le même état qu'avant le
scanf (la boucle while d'un précédent post).

Anthony
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra

Avatar
Pascal

Et quelle est la fonction qui n'est pas montrée ici et qui sert à prendre la
réponse (ici 3) dans l'entrée standard, et y laisse un 'n' dedans ?? En
gros, la gestion du menu pose le même problème que tout à l'heure à mon
avis, quand tu prends le 3 tu tapes sur ton clavier '3' 'n' et ca laisse
le 'n' que fgets() se charge de prendre pour lui et pour sa fin de
chaine !



Ton dernier post, je n'ai pas compris ton dernier paragraphe...

Pour récupérer le choix de ma réponse dans le switch, je faisais
auparavant un scanf (le dernier et seul scanf que j'ai laissé puisque je
ne pouvais pas le remplacer par un fgets puisque c'est normalement un
chiffre que l'utilisateur rentre, et donc un caractère et non une chaine
de caractère).
Les problèmes que j'ai eu, c'est ce que je vous ai déja dit : il me
sautais la première question (demande de nom) et allait directement à
prenom.

J'ai changé ce scanf par un fgetc. Et maintenant, je ne peux même plus
rentrer les nom, prenom, adresse etc... C'est affiché rapidement, mais
je ne peux pas y répondre.

Pour les plus déterminé, j'ai mis mon code de ma fonction principale en
entier ci dessous :

void interface(char *host)
{
int rep;
int *result;
info param;
CLIENT *clnt;
char c;


rep = -1;
//établie la connexion avec le serveur "host"
clnt = clnt_create (host, AGENDA, VERSION_UN, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
param = login(clnt);
while (rep != 0)
{
system("clear");
printf("**************************************n");
printf(" Agenda Répartien");
printf("**************************************n");
printf("Choisissez le numéro :n");
printf("1 - Lister les agendas disponiblesn");
printf("2 - Créer un agendan");
printf("3 - Insérer un contactn");
printf("4 - Rechercher un contactn");
printf("5 - Supprimer un contactnn");
printf("0 - Quitter le programmenn");
printf("Rep : ");
fflush(stdin);
rep = getc(stdin);
switch (rep)
{
case 1 :
listAgenda(clnt);
break;
case 2 :
creerAgenda(clnt);
break;
case 3 :
malloc_info(&param);
printf("Entrer votre nom : ");fflush(stdin);
fgets(param.nom, TAILLE_NOM, stdin);
printf("Entrer votre prenom : ");fflush(stdin);
fgets(param.prenom, TAILLE_NOM, stdin);
printf("Entrer votre adresse : ");fflush(stdin);
fgets(param.adresse, TAILLE_ADRESSE, stdin);
printf("Entrer votre tel : ");fflush(stdin);
fgets(param.tel, TAILLE_TEL, stdin);
printf("Entrer votre mail : ");fflush(stdin);
fgets(param.mail, TAILLE_MAIL, stdin);
result = inserer_1(&param, clnt);
if (result == (int *) NULL) {
clnt_perror (clnt, "call failed");
}
break;
case 4 :
malloc_info(&param);
printf("Entrer le nom de la personne à rechercher: ");fflush(stdin);
fgets(param.nom, TAILLE_NOM, stdin);
result = rechercher_1(&param, clnt);
if (result == (int *) NULL) {
clnt_perror (clnt, "call failed");
}
break;
case 5 :
malloc_info(&param);
printf("Entrer le nom de la personne à supprimer: ");fflush(stdin);
fgets(param.nom, TAILLE_NOM, stdin);
result = rechercher_1(&param, clnt);
if (result == (int *) NULL) {
clnt_perror (clnt, "call failed");
}
break;
case 0 :
printf("Etes vous sûr de vouloir quitter le programme?
(o/n)");fflush(stdin);
if (fgetc(stdin) == 'n')
rep = -1;
break;
defaut:
printf("Erreur de saisien");
break;

}
}
}

Avatar
Anthony Fleury
Pascal wrote:


Et quelle est la fonction qui n'est pas montrée ici et qui sert à prendre
la réponse (ici 3) dans l'entrée standard, et y laisse un 'n' dedans ??
En gros, la gestion du menu pose le même problème que tout à l'heure à
mon avis, quand tu prends le 3 tu tapes sur ton clavier '3' 'n' et ca
laisse le 'n' que fgets() se charge de prendre pour lui et pour sa fin
de chaine !



Ton dernier post, je n'ai pas compris ton dernier paragraphe...


Je vais tenter de la refaire.

Pour récupérer le choix de ma réponse dans le switch, je faisais
auparavant un scanf (le dernier et seul scanf que j'ai laissé puisque je
ne pouvais pas le remplacer par un fgets puisque c'est normalement un
chiffre que l'utilisateur rentre, et donc un caractère et non une chaine
de caractère).
Les problèmes que j'ai eu, c'est ce que je vous ai déja dit : il me
sautais la première question (demande de nom) et allait directement à
prenom.


Alors, une petite démonstration :

#include <stdio.h>

int main(void) {
int carac;
char chaine[42];

scanf("%d", &carac);
fgets(chaine, 42, stdin);
return 0;
}

Alors maintenant voyons ce qu'il se passe.
Le programme attend qu'un entier soit rentré pour le mettre dans carac. Tu
rentres par exemple 3. Tu tapes donc '3' puis 'Entrée' et le 'Entrée' se
traduit par un 'n' dans le buffer d'entrée.

Seulement, scanf va prendre seulement le 3 et le mettre dans carac. Le 'n'
il va le laisser dans stdin.
Ensuite, fgets() va tenter de lire sur stdin 41 caractères, si il ne
rencontre pas de caractère 'n' avant le 41ème. Seulement, tu as vu avant
qque scanf a laissé un 'n', donc fgets va le rencontrer directement, et
pouf, il va pas s'arrêter pour te demander la chaine.

Une solution a ce problème est donc :

#include <stdio.h>

int main() {
int carac;
char chaine[42];

scanf("%d", &carac);
while(fgetc(stdin) != 'n');
fgets(chaine, 42, stdin);
return 0;
}

Là le while() va enlever le 'n' et tout autre caractère incongru qui
pourrait être avant lui dans stdin. Par exemple, si jamais un ptit malin a
tapé quand tu veux rentrer le chiffre : 3 3 3 3 aa 3 3 'Entrée' alors il va
mettre le 3 dans carac et virer tout le reste puis va te demander le nom.
C'est pas tout le temps le comportement voulu, mais bon, il faut savoir ce
que ton code fait et les gens doivent de toute facon répondre aux questions
quand elles arrivent.

Ce que je disais donc, si tu laisses des scanfs, mets le while() derrière,
qui videra le buffer d'entrée et ainsi fera que les questions ne seront pas
sautées. Ne remplace pas spécialement ton scanf ou alors fait attention.

Attention avec ce while, ca peut tourner à la boucle infinie si jamais ton
entrée est redirigée sur un fichier ou si tu fais le mariole avec des cas
spéciaux (cf le thread « vider le buffer d'entrée ». En gros, il faudrait
vérifier aussi qu'il n'y a pas d'erreurs de lectures dans fgetc du genre
une fin de fichier... Mais pour le départ et pour comprendre et résoudre le
problème pour un TP, cette ligne est suffisante je pense.

J'ai changé ce scanf par un fgetc. Et maintenant, je ne peux même plus
rentrer les nom, prenom, adresse etc... C'est affiché rapidement, mais
je ne peux pas y répondre.


Je vois l'erreur avant de voir le code :-)

Pour les plus déterminé, j'ai mis mon code de ma fonction principale en
entier ci dessous :

void interface(char *host)
{
int rep;
int *result;
info param;
CLIENT *clnt;
char c;


rep = -1;
//établie la connexion avec le serveur "host"
clnt = clnt_create (host, AGENDA, VERSION_UN, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
param = login(clnt);
while (rep != 0)
{
system("clear");
printf("**************************************n");
printf(" Agenda Répartien");
printf("**************************************n");
printf("Choisissez le numéro :n");
printf("1 - Lister les agendas disponiblesn");
printf("2 - Créer un agendan");
printf("3 - Insérer un contactn");
printf("4 - Rechercher un contactn");
printf("5 - Supprimer un contactnn");
printf("0 - Quitter le programmenn");
printf("Rep : ");
fflush(stdin);


hum, Lire la FAQ ! fflush(stdin) n'est pas utile, vu que stdin en général
n'est pas un flux accessible en écriture. Le moyen de le vider c'est le
fameux while du dessus, en gros fflush sur stdin ne sert à rien et n'a
aucun effet.

rep = getc(stdin);


Pourquoi pas...

switch (rep)
{
case 1 :


AÏE !

ASCII(7) Linux Programmer's Manual ASCII(7)

NAME
ascii - the ASCII character set encoded in octal, decimal,
and hexadecimal

DESCRIPTION
Oct Dec Hex Char Oct Dec Hex Char
------------------------------------------------------------
000 0 00 NUL '' 100 64 40 @
001 1 01 SOH 101 65 41 A

Le caractère numéro 1 c'est SOH, si un utilisateur peut taper ca au clavier,
présente le moi.
En gros, tu fais case 1, case 2... comme si getc te récupérais la valeur
entière que l'utilisateur avait tapé.
Même si getc renvoie un int, il récupère le *caractère* qui est tapé. Donc
si tu tapes un '1', il va te renvoyer la valeur dans ton système d'encodage
du 1. Il faut donc mettre case '1' qui désigne le CARACTÈRE 1 et non case 1
qui désigne le caractère SOH.

Plus généralement (je cherchais l'exemple d'une boucle de ce genre dans ma
tête et je ne retrouvais plus comment faire cette « bêtise ») ce genre de
boucles est une mauvaise idées.

J'en ai vu pas mal (et j'ai bien dû faire ca un jour aussi) écrire ceci :

int main(void) {
int r = 0;
while(r != 3 /*au hasard, pour quitter*/) {
printf("Le menu...n");
printf("Entrez votre choix : ");
/* ici les gens consciencieux vérifient la valeur de retour de scanf */
/* r n'est significatif que si la lecture ne s'est faite sans problème !*/
/* mais combien la vérifient ?? */
scanf("%dn", &r);
switch(r) {
case 0:
/*...*/
case 1:
/*...*/
case 2:
/*...*/
case 3:
/*...*/
default:
}
}
}

Et là tu ne vois peut être pas le problème, mais moi si ! déroulons ce
programme. Il nous demande d'entrée notre choix. Avec un scanf(), pourquoi
pas hein ! Puis il vérifie si cette réponse fait partie de l'ensemble des
entiers du menu. Sinon il y a le défaut (qui lui demande de resaissir).
Alors maintenant, l'utilisateur lambda qui n'aime pas les consignes (ou le
prof qui veut te mettre une mauvaise note) se dit pourquoi je ne rentrerais
pas un caractère 'a' à la place d'un chiffre ? et bien fait ceci et ce
programme part en boucle infini ! (oui pas de vérification de la valeur de
retour de scanf, qui fera une erreur à chaque tentative de lecture du a,
comme la valeur de départ est une valeur dans l'ensemble des valeurs
admissible et que de toute facon le default en général dans ce genre de
programme ne fait pas quitter la boucle mais demande de recommencer et bien
ca part en vrille et faut y aller à coup de Ctrl C).

Anthony
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra


Avatar
Antoine Leca
En , Emmanuel Delahaye va escriure:
Jean-Marc wrote on 02/11/04 :
scanf, fscanf et sscanf sont pratiques pour lire des données
formattées, par exemple pour lire des données dans un fichier
dont on connait avec certitude la structure (parce qu'on l'a
écrit soi même par exemple).


Et encore... Il vaut parfois mieux passer par fgets(), puis sscanf().


Pas d'accord.
Si tu relis un fichier que tu as toi-même créé (enfin, le même programme),
il n'y a pas de raison pour que tu aies des incidents ou des débordements.
Bien sûr, avoir un numéro de version, ou bien le locale, au début pour un
fichier « pas trop vivant » ne fait pas forcément du mal (et ceux-là, il
vaut mieux les lire avec fgets et sscanf, quite à faire un rewind après).
Mais sinon, en général, fgets va consommer de la mémoire sans apporter grand
chose d'autre que de rassurer le programmeur ou le mainteneur (ce qui n'est
pas forcément à dédaigner).

Normalement, scanf() sert uniquement pour les filtres, et surtout pas pour
les entrées de l'utilisateur au clavier (généralement le même défaut que
gets()). Autrement dit, pas vraiment la fonction la plus utile qui soit.


De toutes façons, il faut tester le code retour de *scanf() (nombre de
conversions réussies).


Alors là oui, certainement. Et normalement, quand tu reçois quelque chose
d'autre que ce qui est attendu, dehors, on essaye pas de réparer les dégâts
pour continuer quand même.


Antoine


Avatar
Richard Delorme

Ensuite pour demander le nom, scanf est peut sécurisé tout de même, car tu
peux rentrer 42 caractères alors que seulement 20 sont reservés, et la
pouf...

Donc préferer fgets() :

fgets(nom, NOMBRE_DE_CARACTERE_MAX, stdin);


En quoi :
scanf("%" #NOMBRE_DE_CARACTERE_MAX "s", nom);
est moins sûr ?

ca c'est sûrement parce que scanf a un comportement « merdique ». En effet,
si tu fais ceci :

int nombre, char caractere;

printf("Entrez le nombren");
scanf("%d", &nombre);
printf("Entrez le caractèren");
scanf("%c", &caractere);

tu vas avoir la surprise que le caractère est sauté (de même pour une chaine
dans ton cas).

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');


On peut faire ça avec un scanf :
scanf("%d%*[^n]", &nombre); getchar();

C'est plus sûr que ton while qui peut boucler indéfiniment s'il
rencontre un EOF.

--
Richard

Avatar
Laurent Deniau
Antoine Leca wrote:
En , Emmanuel Delahaye va escriure:

Jean-Marc wrote on 02/11/04 :

scanf, fscanf et sscanf sont pratiques pour lire des données
formattées, par exemple pour lire des données dans un fichier
dont on connait avec certitude la structure (parce qu'on l'a
écrit soi même par exemple).


Et encore... Il vaut parfois mieux passer par fgets(), puis sscanf().



Pas d'accord.
Si tu relis un fichier que tu as toi-même créé (enfin, le même programme),
il n'y a pas de raison pour que tu aies des incidents ou des débordements.
Bien sûr, avoir un numéro de version, ou bien le locale, au début pour un
fichier « pas trop vivant » ne fait pas forcément du mal (et ceux-là, il
vaut mieux les lire avec fgets et sscanf, quite à faire un rewind après).
Mais sinon, en général, fgets va consommer de la mémoire sans apporter grand
chose d'autre que de rassurer le programmeur ou le mainteneur (ce qui n'est
pas forcément à dédaigner).


Tu omets egalement le probleme de performance de sscanf dans le cas ou
la chaine de charactere est longue. Probleme que j'ai deja souleve
ici-meme. La combinaison fgets+sscanf n'est pas garantie d'etre plus
rapide que fscanf directement, bien que ce soit generalement le but
recherche de cette construction.

a+, ld.



Avatar
Richard Delorme

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


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

--
Richard

1 2 3 4 5