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

Probleme avec scanf

118 réponses
Avatar
Bakounine
bonjour

je suis en train de faire un petit programme client et serveur qui echange
des messages. Dans cetains messages il y a deux valeur separer par des deux
points.

ex:

valeurun:valeurdeux

j'aimerais savoir si sscanf permet de gerer le separateur : ou faut il
utiliser une autre fonction? j'ai utilise sscanf("%s:%s", var1, var2); mais
ca ne marche pas.

Merci d'avance pour votre reponse.

10 réponses

Avatar
Richard Delorme



Tu as raison, mais il faut être pragmatique : quand un OP demande comment
utiliser scanf() pour faire un split() de chaines, je ne suis pas de ceux qui
proposent le format %[^:] , je préconise d'utiliser strchr() et d'éviter
scanf().


Où est cette solution qui préconise strchr ?
Celle que j'ai vu, est signé Yves Roman, et n'est pas complètement
satisfaisante : écrit dans la chaine de départ, ne vérifie pas grand
chose, lente (parcours 2 fois la chaine), longue (7 lignes contre 1 pour
le sscanf), etc.



C'est exact

Il faut quand même noter que sscanf parcours systématiquement toute la chaine de
départ au moins une fois, même s'il n'en extrait que le 1er caractère :


Ça dépend des implémentations :

$ gcc testscanf.c -o testscanf
$ testscanf debut
fin
10 duree sscanf = 0
debut
fin
250000 duree sscanf = 35
debut
fin
499990 duree sscanf = 101

$ diet gcc testscanf.c -o testscanf
/tmp/ccOMiNv2.o(.text+0x8c): In function `TestSscanf':
: warning: warning: difftime introduces an unnecessary floating point
dependency. Don't use it!
/usr/diet/lib-i386/libc.a(vprintf.o)(.text+0x6b): In function `vprintf':
: warning: warning: the printf functions add several kilobytes of bloat.
/usr/diet/lib-i386/libc.a(vsscanf.o)(.text+0x7b): In function `vsscanf':
: warning: warning: the scanf functions add several kilobytes of bloat.
$ testscanf debut
fin
10 duree sscanf = 0
debut
fin
250000 duree sscanf = 0
debut
fin
499990 duree sscanf = 0

--
Richard



Avatar
Targeur fou
Charlie Gordon wrote:
"Targeur fou" wrote in message
news:
Charlie Gordon wrote:

Conclusion : NE PAS UTILISER SCANF !


Il est où le problème avec scanf ? ;-) Dans ce qui suit, quelle
est


la différence avec fgets() d'un point de vue pratique par exemple
?



#include <stdio.h>

#define LG_SAISIE_STDIN 20
#define STR_(s) #s
#define STR(s) STR_(s)

int main(void)
{
int rc;
char tamponSaisie[LG_SAISIE_STDIN+1] = { '' };

rc = scanf("%"STR(LG_SAISIE_STDIN)"[^n]%*[^n]",tamponSaisie);
getchar();
if (rc != EOF) {
printf("Donnees saisies : n%sn", tamponSaisie);
}
else {
puts("Erreur lors de la saisie");
}

return 0;
}


Comme je l'écris dans ma réponse à Antoine, ta proposition est
très

lourde à mettre en oeuvre, hors de portée du débutant, et très
limitée en pratique :


Je suis d'accord pour dire que c'est difficile pour un débutant (hors
de portée me parait exagéré, sinon je n'aurais pas pu le faire ;-),
j'ai lu la spec de scanf et je me suis démerdé avec. Je suis aussi
d'accord pour la limitation pratique pour le spécificateur de longueur
de saisie qui doit être un entier obligatoirement décimal, quoique
encore pour un nombre de caractères à saisir, donner ce dernier en
octal ou en hexa, il faut être pervers pour le faire. Par contre, je
ne suis pas d'accord pour la lourdeur de mise en oeuvre, ca ne prend
pas beaucoup plus de volume de code qu'avec fgets.

tes macros ne fonctionnent que si LG_SAISIE_STDIN est elle-même une
macro s'expansant à une constante numérique décimale isolée.

exemples de définitions qui incompatibles avec ton approche :
#define LG_SAISIE_STDIN +20
#define LG_SAISIE_STDIN 0x20
#define LG_SAISIE_STDIN (20)
#define LG_SAISIE_STDIN (19+1)
enum { LG_SAISIE_STDIN = 20 };
int LG_SAISIE_STDIN = 20; // en variable locale de main(), C99

Quant à la différence avec fgets(), elle existe bel et bien :

- fgets() ne consomme pas la partie de la ligne d'input qui ne
"tient" pas dans le buffer, contrairement au format (cryptique)
%*[^n] dont c'est précisément l'objet.


Ca ne joue que sur les perfs, pas bien grave si l'on a qu'une saisie
par ci par là. Ca rejoint tout à fait ta dernière remarque à propos
des diffs fgets/scanf.

- fgets() laisse le n à la fin du buffer sauf en cas de ligne trop
longue ou de fin de fichier sans n

- fgets() consomme le n en fin de ligne, alors que dans ta
proposition, n est remis dans stdin en fin de ligne, qu'il faut
consommer avec le getchar() incongru et sans explication.

- en cas de lignes vides dans l'input, scanf() retourne une valeur 0
et le contenu du buffer est indéterminé, ce que tu ne testes pas
correctement.


Conneries de ma part, le getchar() est inutile, n est remis dans stdin
mais n'est pas consommé par scanf. je voulais récupérer le n "pour
faire comme avec fgets()" mais j'ai oublié de le prendre et de le
mettre dans tamponSaisie. Et heureusement d'ailleurs, car scanf
consomme (au max) LG_SAISIE_STDIN caractères et non
(LG_SAISIE_STDIN-1) comme fgets, ce qui aurait pu avoir pour effet de
virer le dernier de tamponSaisie.


- l'utilisation de fgets() est beaucoup plus lisible.


...oui.


- je parierais fort que l'efficacité d'un fgets() en lieu et place
du

scanf() serait sans commune mesure. Mais il faudrait un programme
qui execute massivement ce code sur une grande quantité de
données

pour mesurer une réelle différence de performances. J'ai testé
avec

un gros fichier et c'est 4 fois plus lent avec scanf que avec
fgets()

ou la fonction fgetline que je propose plus bas.

Certains de ces comportements de fgets() sont parfois inadéquats,
mais la solution consistant à lui substituer un format de scanf
totalement cryptique est peu recommandable.


Je n'ai rien contre fgets(), loin de là.

Regis



Avatar
Charlie Gordon
"Antoine Leca" wrote in message
news:d4qbk9$hoc$
En <news:d4q7s8$8sv$,
Charlie Gordon va escriure:
"Richard Delorme" wrote in message
news:42700131$0$26067$
Où est cette solution qui préconise strchr ?
Celle que j'ai vu, est signé Yves Roman, et n'est pas complètement
satisfaisante : écrit dans la chaine de départ, ne vérifie pas grand
chose, lente (parcours 2 fois la chaine), longue (7 lignes contre 1
pour le sscanf), etc.


Je ne vois pas en quoi la solution de Yves Roman est plus lente que
d'utiliser strtok()


Normalement strtok() est implémenté par un appel à strspn() pour sauter les
séparateurs initiaux (inutile ici, coût négligeable), suivi d'un autre à (au
choix) strpbrk() ou strcspn() pour aller chercher le séparateur suivant. Au
total un seul parcours de la chaîne cible (et pléthore de parcours de la
chaîne des séparateurs). *Si* la chaîne cible est potentiellement très
longue, cela peut valoir le coût. D'un autre côté, dans le cas contraire,
strtok() (et sscanf() ) sont probablement moins intéressants.


qui écrit AUSSI dans la chaine de départ, qui ce est souvent méconnu


On est quand même sensé savoir lire un peu plus loin que le bout de son nez:
le premier paramètre de strtok() est un char*, *pas* un char const*;
autrement dit, la chaîne sera modifiée.


et une raison supplémentaire de s'en méfier.


Certes.


Elle n'est sans doute pas plus lente non plus que scanf qui n'est
jamais implémenté en ligne et doit avec le format approprié faire
beaucoup plus de tests pour chaque caractère de msg.


« doit » ???

Par rapport à strtok(), sscanf() doit mettre en place son infrstructure et
parcourir la chaîne de format, ce qui a un coût certain mais unique. De
l'autre côté strtok() fait un appel inutile à strspn() pour sauter les
séparateurs initiaux (coût très faible). Après, tu as en fait la même chose:
chacun des deux lis la chaîne caractère par caractère, vérifie que le dit
caractère n'est pas dans la chaîne de référence ":" (le scanset pour
sscanf() et les séparateurs pour strtok()), puis recommence.

En fait, sscanf() peut être plus efficace que strtok(), car l'extraction du
scanset permet gratuitement de compter la longueur donc d'utiliser memchr()
pour la vérification, voire créer un arbre balancé (;-)), tandis que
strtok() va souvent utiliser strchr() pour cela, qui peut être moins
efficace.

Évidemment, on est à la merci des implémentations... par exemple, on peut
avoir un scanner qui à chaque vérification teste si on a un %[ (inclusif) ou
un %[^ (exclusif)... Ou encore une implémentation qui laisse le scanset en
place et qui traite l'extension a-z à chaque itération, donc un test pour
'-' à chaque caractère... :-(


Ma réponse concernait la proposition de Yves Roman qui utilise strchr() et non
strtok().
Et dont Richard Delorme critiquait l'inefficacité. Utiliser strtok(), ou
strtok_r(), ou encore sscanf() avec un séparateur unique est effectivement
particulièrement inefficace.

Chqrlie.



Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:4270c8cb$0$26063$

Celle que j'ai vu, est signé Yves Roman, et n'est pas complètement
satisfaisante : écrit dans la chaine de départ, ne vérifie pas grand
chose, lente (parcours 2 fois la chaine), longue (7 lignes contre 1 pour
le sscanf), etc.


Je ne vois pas en quoi la solution de Yves Roman est plus lente que
d'utiliser


strtok() qui écrit AUSSI dans la chaine de départ, qui ce est souvent
méconnu et


une raison supplémentaire de s'en méfier.


Oui, mais dans l'esprit de strtok, on ne recopie pas la chaine, mais on
met des pointeurs vers chaque champs :
char *var1, *var2;
var1 = msg;
var2 = strtok(msg, ":");


Tu n'as pas tort, mais ton exemple est faux : var2 == var1 sauf
dans le cas ou msg commence par un ou des ':'

L'utilisation "classique" de strtok() donne ceci :
char *var1, *var2;
var1 = strtok(msg, ":");
var2 = strtok(NULL, ":");

Mais le cas ou var1 est vide n'est pas supporté.
Bref strchr() est bien plus clair, et Ô combien plus rapide :

char *var1, *var2;
var1 = msg;
if ((var2 = strchr(msg, ':')) != NULL)
var2++;

Elle n'est sans doute pas plus lente non plus que scanf qui n'est jamais
implémenté en ligne et doit avec le format approprié faire beaucoup plus de
tests pour chaque caractère de msg.


scanf a le mérite de la concision.


Au prix d'une syntaxe cryptique, et d'une difficulté hors de portée du débutant
en particulier pour éviter les débordements de tableau.

[...]

Bref, à part être plus long que scanf, je ne vois pas ce qu'apporte
d'utiliser strchr.


C'est plus précis : le cas ou le séparateur est absent est facile à détecter.
Le cas ou var1 ou var2 sont vides n'apparaît pas comme une erreur alors que
scanf retournerait un nombre de champs traités inférieur à 2.


Chqrlie.



Avatar
Emmanuel Delahaye
Charlie Gordon wrote on 28/04/05 :
Mais la plupart des programmeurs C ne lisent pas le manuel et intuitent la
sémantique de strncat de son prototype et de la méconnaissance de celle de
strncpy. Donc ils l'utilisent le plus souvent de facon erronée.


Au risque de me répeter, ce n'est pas un problèmme de langage mais de
formation.

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

.sig under repair

Avatar
Richard Delorme

Tu n'as pas tort, mais ton exemple est faux : var2 == var1 sauf
dans le cas ou msg commence par un ou des ':'

L'utilisation "classique" de strtok() donne ceci :
char *var1, *var2;
var1 = strtok(msg, ":");
var2 = strtok(NULL, ":");


C'est marrant, c'est ce que j'avais commencé par écrire, puis j'ai fait
man strtok ou j'ai mal compris «element lexical suivant.»

Mais le cas ou var1 est vide n'est pas supporté.
Bref strchr() est bien plus clair, et Ô combien plus rapide :

char *var1, *var2;
var1 = msg;
if ((var2 = strchr(msg, ':')) != NULL)
var2++;


Marche pas... (1 partout pour les exemples déficients ;-)

char *var1, *var2;
var1 = msg;
if ((var2 = strchr(msg, ':')) != NULL) {
*var2 = '';
var2++;
}


scanf a le mérite de la concision.


Au prix d'une syntaxe cryptique, et d'une difficulté hors de portée du débutant
en particulier pour éviter les débordements de tableau.


N'exagérons pas, comparé au regexp de Perl, ça reste très lisible et le
problème de débordement de tableau est identique pour les autres méthodes.


Bref, à part être plus long que scanf, je ne vois pas ce qu'apporte
d'utiliser strchr.



C'est plus précis : le cas ou le séparateur est absent est facile à détecter.
Le cas ou var1 ou var2 sont vides n'apparaît pas comme une erreur alors que
scanf retournerait un nombre de champs traités inférieur à 2.


Sauf que dans les exemples montrées jusqu'à présent, personne n'a traité
aucune erreur... scanf traite un champ vide comme une erreur, c'est un
point de vue qui se tient à mon avis.
Je ne pense pas que scanf soit une panacée, mais c'est sans doute un
outil simple et suffisant dans pas mal de cas.

--
Richard


Avatar
Emmanuel Delahaye
Charlie Gordon wrote on 28/04/05 :
Mais
s'ils sont comme je l'imagine consultants en logiciel embarqué sous-traitants
de l'industrie, ça revient à laisser jouer les enfants dans une salle
d'opération avec les instruments chirurgicaux : gare aux dégats. Les tests
exhaustifs, quelle fumisterie!


Je suis d'accord. Rien ne remplace la connaissance du langage. Les
tests peuvent passer à coté des UB...

"C is a sharp tool"

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

I once asked an expert COBOL programmer, how to
declare local variables in COBOL, the reply was:
"what is a local variable?"

Avatar
Harpo
Charlie Gordon wrote:

Quant à strncpy() et strncat() : relisez la spec et vous
verrez qu'elles ne font
pas ce que la plupart des programmeurs pensent savoir ou
comprendre.


Il y a la doc, s'ils ne la comprennent parfois un peu de travers
comme moi ce n'est pas un problème, comme ils font des tests
exhaustifs ils finissent par comprendre ce qu'ils font.


C'est une plaisanterie !


Non, il y a la doc. La première chose à faire est de lire cette foutue
doc.
Et de la lire *soigneusement*. Mais même en faisant cela on n'échappe
pas à des erreurs d'interprétation toujours possibles et quand on l'a
comprise on n'échappe pas à des erreurs de codage et autres,
Il y a aussi la possibilité que la doc ne corresponde pas à ce que fait
le programme, soit c'est du à un bug du programme soit à un bug de
documentation. Ce monde est plein de bugs partout.

Qu'on laisse jouer les bambins avec logo ou java, c'est pas bien
grave. Mais s'ils sont comme je l'imagine consultants en logiciel
embarqué sous-traitants de l'industrie, ça revient à laisser jouer les
enfants dans une salle d'opération
avec les instruments chirurgicaux : gare aux dégats. Les tests
exhaustifs, quelle fumisterie!


"tests exhaustifs" est une vue de l'esprit mais on peut s'en approcher,
il me semble au contraire nécessaire d'en approcher les limites et
d'avoir des solutions de repli lorsqu'on commet des bugs.
Pour reprendre ta comparaison, on commence à vraiment découper dans la
bidoche lorsque le programme est livré. C'est avant cela qu'il faut
veiller à ne pas faire de dégâts au moment réel.

De plus, il n'y a pas que le matériel embarqué, si tu fais mettons un
truc qui te génère du html et que les titres de chapitre ont une
apparence dégueulasse sur le browser X en résolution 320x240, c'est
certes désolant mais tu peux t'en contrebattre les glaouis avec
allégresse car il n'y a pas mort d'homme.

Dans le matériel embarqué, j'imagine qu'on embauche pas des petits
génies de l'info qui ont réussi un 'hello world' amélioré avec des
couleurs mais plutôt des gens rigoureux et un peu intransigeants comme
Emdel. Tout se teste.
De plus, dans l'informatique embarquée il y a une différence entre le
réglage du débit de l'injection du carbu d'un motoculteur et la
conduite d'un TGV.

Si les tests ne servaient à rien, on mettrait directement en fonction
sur un TGV les programmes dès qu'ils sont compilés, mais il faudra me
payer cher pour que je prenne un ticket.



Avatar
Emmanuel Delahaye
Charlie Gordon wrote on 28/04/05 :
Tu utilises strncat pour réaliser en fait une copie avec limitation de
taille, c'est un peu surprenant pour le lecteur non averti, mais ce n'est pas
un bug, en revanche ton sprintf causera un debordement de tableau :-(
Utilises un tampon intermédiaire ou snprintf().

Meme chose pour FIC_str_time, et plus généralement partout ou tu utilises
sprintf.

Je vois que tu n'as pas vu à quoi servaient


LIM_PTR (sdate, size);
et
CHK_PTR (sdate, size);

Bien sûr, mal est fait et en théorie, je suis déjà satellisé, mais
crois ce genre de test (en mode débug) ça fait le ménage!

Je préfèrerais utiliser snprintf(), mais rien de tel en C90. alors on
fait ce qu'on peut...

Dans FIC_str_size, dans le cas d'une taille nulle, tu utilises strncat sans
avoir au préalable initialisé la destination (BUG!)


Hou, pas bien ça!

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

.sig under repair

Avatar
Emmanuel Delahaye
(supersedes )

Charlie Gordon wrote on 28/04/05 :
Tu utilises strncat pour réaliser en fait une copie avec limitation de
taille, c'est un peu surprenant pour le lecteur non averti, mais ce n'est
pas un bug, en revanche ton sprintf causera un debordement de tableau :-(
Utilises un tampon intermédiaire ou snprintf().

Meme chose pour FIC_str_time, et plus généralement partout ou tu utilises
sprintf.

Je vois que tu n'as pas vu à quoi servaient


LIM_PTR (sdate, size);
et
CHK_PTR (sdate, size);

Bien sûr, le mal est fait et en théorie, je suis déjà satellisé, mais
crois ce genre de test (en mode débug) ça fait le ménage!

Je préfèrerais utiliser snprintf(), mais rien de tel en C90. alors on
fait ce qu'on peut...

Dans FIC_str_size, dans le cas d'une taille nulle, tu utilises strncat sans
avoir au préalable initialisé la destination (BUG!)


Hou, pas bien ça!

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

"There are 10 types of people in the world today;
those that understand binary, and those that dont."