OVH Cloud OVH Cloud

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
Richard Delorme wrote on 28/04/05 :

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.



http://www.developpez.net/forums/viewtopic.php?t31877



pas génial non plus... sauf si c'est pour montrer que sscanf est mieux.



J'ai pas dit que c'était bien, mieux ou quoi. Tu voulais une solution,
je t'en donne une toute fraîche. Si tu trouves qu'elle n'est pas bonne,
il falloir être un moins vague que "pas génial non plus"



Plus ou moins les mêmes reproches que précédemment : ne vérifie pas
grand chose, lente (parcours 3 fois la chaine), longue, etc.

/* On cherche la place du caractere '/' */
char *p_sep = strchr (string, '');

/* On place tous les caracteres a gauche du '' dans n1 */
char n1[32];
*n1 = 0;
strncat (n1, string, (size_t) (p_sep - string));

Par exemple, que se passe-t-il si string ne contient pas de '' ?

--
Richard




Avatar
Emmanuel Delahaye
Richard Delorme wrote on 28/04/05 :
Richard Delorme wrote on 28/04/05 :

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.


http://www.developpez.net/forums/viewtopic.php?t31877


pas génial non plus... sauf si c'est pour montrer que sscanf est mieux.


J'ai pas dit que c'était bien, mieux ou quoi. Tu voulais une solution,
je t'en donne une toute fraîche. Si tu trouves qu'elle n'est pas bonne,
il falloir être un moins vague que "pas génial non plus"


Plus ou moins les mêmes reproches que précédemment : ne vérifie pas
grand chose, lente (parcours 3 fois la chaine), longue, etc.


Je ne compte qu'un parcours fait en 2 fois...

/* On cherche la place du caractere '/' */
char *p_sep = strchr (string, '');


une demi fois

/* On place tous les caracteres a gauche du '' dans n1 */
char n1[32];
*n1 = 0;
strncat (n1, string, (size_t) (p_sep - string));


une autre demi fois... (en gros)

Par exemple, que se passe-t-il si string ne contient pas de '' ?


Je n'ai pas fait la version sécurisée, je voulais juste montrer que le
code du premier répondeur pouvait être simplifié.

C'est sûr que la version sscanf() est la plus concise.

--
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."





Avatar
Harpo
Emmanuel Delahaye wrote:


La solidité, la lisibilité, la maintenabilité, la réusabilité
tiennent sans doute en partie au langage


pas du tout (enfin, un peu quand même, sans pointeurs de fonctions, je
suis perdu, ou je passe en assembleur !)


Parler de pointeurs de fonctions est parler d'implémentation, en C ce
que l'on fait laborieusement avec une table de pointeurs de fonctions
et des choses comme ça peut être fait en une ligne en un langage
interprété.
L'interêt est dans le nombre de lignes qui est proportionnel au nombre
d'erreurs et inversement proportionnel à la lisibilité


mais surtout au design et le C permet
de faire des choses claires si on veut faire un peu d'effort.


Absolument. Ca ne demande qu'un peu de méthode, de rigueur, et, soit
beaucoup d'expérience pour les autodidactes comme moi, soit une bonne
formation. D'après ce que je vois, la formation n'est pas bonne
(enfin, ici, et sur d'autres forums, on ne voit que les problèmes...).


Il ne faut pas voir les choses en noir. Une formation n'a pas pour objet
d'en faire des experts dès leur sortie. Pour cela il faut de
l'expérience et de bonnes fréquentations.

Sais pas. On peut tout construire en C, y compris des fonctions de
très haut niveau... Certains ont franchi le pas et ont bati des
interpréteurs objets avec le C (Python, Ruby etc.)

Sinon, GTK est un bon exemple de ce que peut faire une bonnne
bibliothèque écrite en C pour construire des programme GUI (portabkes
en plus, ce qui ne gâche rien)...

Ok, Python et Ruby savent aussi faire ça...


Il y a 2 choses différentes. GTK, pour ce que j'en connais, est très
bien fait, mais faire le design d'une bibliothèque n'est pas faire le
design d'un langage bien qu'on puisse établir une certaine analogie.

Personnellement, j'aime beaucoup Ruby, il est facile de rapidement
écrire des programmes pas triviaux et je pressens que si je m'y
adonnais pendant un an je serais loin d'avoir fait le tour de ses
possibilités. Mais les GUI disponibles sont des liaisons avec des
bibliothèques écrites en C ou C++, Ruby/Gnome2/GTK2 me semble être ce
qui offre le plus de fonctionnalités.


Avatar
Charlie Gordon
"Emmanuel Delahaye" wrote in message
news:
Harpo wrote on 27/04/05 :
Il faut se dire qu'il faut se débrouiller avec, ce sont des fonctions
que l'on doit connaître lorsqu'on les utilise souvent, j'utilise plus
facilement memcpy lorsque je le peux.


memcpy() ne met pas de 0 final. strncat() le fait. Le jour où j'ai
découvert ça (en parcourant c.l.c, i y a 4 ou 5 ans), ma vie a changé !


Good for you !

Tu utilises strncat pour extraire des sous-chaines, c'est effectivement
astucieux.
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.

Chqrlie.


Avatar
Charlie Gordon
"Harpo" wrote in message
news:426feb41$0$20253$
Charlie Gordon wrote:


Je ne vois pas pourquoi on utiliserait strtok() quand strtok_r() est
disponble.


Ok, mais très généralement on s'en fout quand il s'agit de mémoire
allouée dans le tas sur lequel il n'y a pas de problème de concurrence.
C'est typiquement un faux problème, il suffit de connaître le
comportement de la fonction. Faire un malloc_r() serait plus séieux.

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 !

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!

Chqrlie.


Avatar
Charlie Gordon
"Emmanuel Delahaye" wrote in message
news:
Charlie Gordon wrote on 27/04/05 :
Essayez avec vos propres projets, ou ceux dont vous avez les sources sous la
main, c'est rare que strncpy ou strncat n'y soit pas utilisé de façon
erronée.


Je te laisse regarder...

http://mapage.noos.fr/emdel/clib.htm


fic.c :

void FIC_str_date (const char *const sfile
, char *const sdate
, size_t const size)
{
if (sdate && size)
{
FILE *f = fopen (sfile, "r");
*sdate = 0;

LIM_PTR (sdate, size);

if (f != NULL)
{
struct ftime ftimep;

getftime (fileno (f), &ftimep); /* WARNING non ANSI */
fclose (f);

sprintf (sdate
,"%02d-%02d-%04d"
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980
);

CHK_PTR (sdate, size);

}
else
{
strncat (sdate, "##/##/####", size - 1);
CHK_PTR (sdate, size);
}
}
}

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.

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

Je pense que la pédagogie serait mieux servie si tu implémentais et utilisais
une fonction de copie de chaine qui fasse exactement ce qui est nécessaire.


Chqrlie.


Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:42700131$0$26067$

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.


Exact, je ne l'ai pas posté quand j'ai vu que quelqu'un d'autre l'avait déjà
fait.

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.

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.

D'ailleurs elle ne parcourt deux fois que la moitié de la chaine, et on peut
facilement la modifier pour qu'elle laisse msg inchangé :

char *p=strchr(msg,':');
if (p) {
memcpy(var1, msg, p - msg);
var1[p - msg] = '';
strcpy(var2, p + 1);
}


Chqrlie.


Avatar
Antoine Leca
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... :-(


Antoine


Avatar
Richard Delorme

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.


Exact, je ne l'ai pas posté quand j'ai vu que quelqu'un d'autre l'avait déjà
fait.

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, ":");

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.

D'ailleurs elle ne parcourt deux fois que la moitié de la chaine, et on peut
facilement la modifier pour qu'elle laisse msg inchangé :

char *p=strchr(msg,':');
if (p) {
memcpy(var1, msg, p - msg);
var1[p - msg] = '';
strcpy(var2, p + 1);
}


Je n'ai qu'à recopier les critiques envers scanf de ce message :
http://groups.google.fr/groups?selmÔnkco%24ma3%241%40reader1.imaginet.fr

« Mais notez bien que [le code ci-dessus] ne vérifie aucunement que les
parties de msg de part et d'autre du ':' "tiennent" dans var1 et var2.
Il serait bien préférable de préciser la taille des tableaux var1 et
var2 pour éviter un débordement potentiel qui pourrait créer une faille
de sécurité ou un quelconque autre comportement indéfini.
Malheureusement [ce code] ne permet pas de préciser la taille des
tableaux de char.[...]
La seule solution dans le cas présent consiste à dimensionner var1 et
var2 à la même taille que msg... Mais cette taille n'est pas connue à
compile time...
Enfin autre problème : [ce code ne] permet [pas] de savoir combien de
champs ont été traités, ou si une erreur s'est produite.
[...] on ne peut pas savoir ce qui s'est réellement passé, et le contenu
de var1 et var2 est potentiellement quelconque, donc même en mesurer la
taille avec strlen() peut déclencher un comportement indéfini. »

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

--
Richard


Avatar
Yves ROMAN


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 :


#include <stdio.h>
#include <string.h>
#include <time.h>

/*** Adapter la taille et le nombre d'iteration a votre systeme ***/
#define TAILLE 50000
#define NB 10000L

char buffer[TAILLE+1] ;

void TestSscanf ( unsigned int position )
{
long nb ;
char res[2] ;
unsigned long duree ;
time_t debut, fin ;

(void)memset ( buffer , ' ' , TAILLE ) ;
buffer[TAILLE] = '' ;
buffer[0] = 'X' ;
if ( position < 1 )
position = 1 ;
else if ( position > TAILLE )
position = TAILLE ;
buffer[position] = '' ;


printf ( "debutn" ) ;
debut = time ( (time_t *)NULL ) ;
for ( nb = NB ; nb-- > 0 ; )
{
(void)sscanf ( buffer , "%1c" , res ) ;
}
fin = time ( (time_t *)NULL ) ;
duree = (unsigned long)difftime(fin,debut) ;
printf ( "finn%d duree sscanf = %lun" , position, duree ) ;
}

int main ( void )
{
TestSscanf ( 10 ) ;
TestSscanf ( TAILLE / 2 ) ;
TestSscanf ( TAILLE - 10 ) ;
return 0 ;
}