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

1 2 3 4 5
Avatar
Richard Delorme
sscanf(msg, "%[^:]:%s", var1, var2);







[...]

Bien sûr il manquait encore un %...


Où ça ?
Dans ma première réponse (corrigée donc je ne comprends pas le
«encore»), il manquait effectivement un %, mais il s'agit d'une erreur
d'inattention que détecte les compilateurs et non d'un bug sérieux.

Mais notez bien que la ligne

sscanf(msg, "%[^:]:%[^:]", var1, var2);

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.


Si var1 et var2 ont la même taille que msg, ce qui est trivial à
implémenter, le risque de débordement est nul.

Malheureusement la syntaxe de sscanf ne
permet pas de préciser la taille des tableaux de char que explicitement dans la
chaine de format, et avec un nombre qui n'est pas sizeof(var1) !
Donc si les tableaux var1 ou var2 changent de taille ou sont d'une taille
variable ou paramétrique, ca devient particulièrement acrobatique puisqu'il faut
fabriquer la chaine de sscanf() avec printf(), ce qui empêche d'ailleurs le
compilateur de faire des vérifications simples de cohérence entre la chaine de
format de sscanf() et les paramètres qui lui sont passés.
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...


Comment peut-on savoir, à partir du message initial, que la taille de
msg n'est pas connu à la compilation ? Quelle soit connue ou non quel
est le problème ? En quoi une autre technique que sscanf résoud ce
problème simplement ?

Enfin autre problème : le résultat de sscanf() permet de savoir combien de
champs ont été traités, ou si une erreur s'est produite.
Comme ce résultat n'est pas stocké, 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.


La question initiale portait sur le type de format à utiliser dans
sscanf pas sur le test de retour de sscanf. Il est évident qu'il faut
tester le retour de sscanf. Ce test est trivial et on peut supposer que
l'OP le fait, mais son problème est ailleurs.

Conclusion : NE PAS UTILISER SCANF !


AMHA, pour traiter les chaines de caractères, ne pas utiliser le langage
C est une réponse encore meilleure. Mais on a peut-être pas le choix.

--
Richard






Avatar
Charlie Gordon
"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 :

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.

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

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

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

Si tu veux une fonction qui lise une ligne d'un fichier en limitant à
la taille du buffer, et consommant le reste y compris le n, il
suffit de l'écrire, voici le code de test :

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

char *fgetline(char *buf, int size, FILE *fp)
{
int c;
char *p, *p_last;

if (!buf || size < 1)
return NULL;

p = buf;
p_last = buf + size - 1;

while ((c = getc(fp)) != EOF && c != 'n') {
if (p < p_last) {
*p++ = c;
}
}
*p = '';
if (c == EOF && p == buf)
return NULL;
else
return buf;
}

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

int main(int argc, char **argv)
{
char tamponSaisie[LG_SAISIE_STDIN+1] = { '' };
int lines = 0;

if (argc >= 2 && !strcmp(argv[1], "fgetline")) {
while (fgetline(tamponSaisie, sizeof(tamponSaisie), stdin)) {
lines++;
}
} else
if (argc >= 2 && !strcmp(argv[1], "fgets")) {
while (fgets(tamponSaisie, sizeof(tamponSaisie), stdin)) {
lines++;
}
} else
if (argc >= 2 && !strcmp(argv[1], "scanf")) {
for (;;) {
int rc = scanf("%"STR(LG_SAISIE_STDIN)"[^n]%*[^n]",tamponSaisie);
if (rc == EOF)
break;
getchar();
lines++;
}
} else {
printf("usage: test [ fgetline | fgets | scanf ]n");
return 1;
}
printf("%d linesn", lines);
return 0;
}

--
Chqrlie

PS: je persiste : NE PAS UTILISER SCANF


Avatar
Charlie Gordon
"Antoine Leca" wrote in message
news:d4nslq$g4e$
Charlie Gordon écrivit dans news:d4nkco$ma3$
Mais notez bien que la ligne

sscanf(msg, "%[^:]:%[^:]", var1, var2);


Attention aux n qui traînent en «fin» de msg: ils auraient été un butoir
pour %s, mais %[^n] va passer outre, ce qui peut être un souci.


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.


Pas sûr.
Le contrôle de taille se fait avant, au niveau de l'acquisition de msg. Si
tu as dimensionné var1 et var2 de la même taille que msg, je ne vois pas
comment tu peux avoir un débordement, sauf en présence de bogue dans
l'implémentation de sscanf.


Oui, c'est ce que j'écris en conclusion de mon post.

Maintenant, si tu es obligé (par circonstance extérieure) d'avoir une taille
donnée pour var1 ou var2 (inférieure à celle de msg), il faut effectivement
imposer des limites:

#define ch(x) cx(x) /* truc horrible pour convertir la valeur */
#define cx(x) #x /* d'une constante en morceau de chaîne */

char var1[TAILLE_DE_VAR1], var2[TAILLE_DE_VAR2];

if( 2 != sscanf(msg, "%" ch(TAILLE_DE_VAR1) "[^:]:"
"%" ch(TAILLE_DE_VAR2) "[^:]",
var1, var2) ) /*...*/

ce qui renforce les raisons de vérifier la valeur retournée (à quoi sert de
poser des limites si elles ne sont pas vérifiées ?)


Non seulement ta proposition est d'une lourdeur affreuse, mais elle est erronée
à plusieurs titres :
- la taille à mentionner dans le format doit être strictement inférieure à la
taille du buffer.
- tes macros ne fonctionnent (presque) que si TAILLE_DE_VAR1 est elle-même une
macro s'expansant à une constante numérique décimale isolée.

exemples de définitions incompatibles avec ton approche :
#define TAILLE_DE_VAR1 +50
#define TAILLE_DE_VAR1 0x50
#define TAILLE_DE_VAR1 (50)
#define TAILLE_DE_VAR1 (49+1)
enum { TAILLE_DE_VAR1 = 50 };
int TAILLE_DE_VAR1 = 50; // en variable locale de main(), C99

Conclusion : NE PAS UTILISER SCANF !


Dommage. ;-)


Mais salutaire !

Antoine (qui se *précipite* sur strtok() pour faire ce genre de découpage
:-)) )


Ou plutôt strchr() :-)


Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:426f9d6e$0$26070$
Conclusion : NE PAS UTILISER SCANF !


AMHA, pour traiter les chaines de caractères, ne pas utiliser le langage
C est une réponse encore meilleure. Mais on a peut-être pas le choix.


OK pour les remarques qui n'étaient pas directement à ton attention, et sans
doute pas dans le bon ordre, mais même si on a pas le choix du langage et qu'on
doive traiter des chaines de caractères en C (*) scanf() est une mauvaise
solution, tout comme strtok(), strncpy, strncat() et d'autres.

Chqrlie.

(*) et si bien sûr on veut faire du code robuste, mais sinon pourquoi faire du C
?


Avatar
Harpo
Charlie Gordon wrote:

C'est trop merdique, utiliser les fonctions strxxx.


Sauf bien sûr strncpy, strncat et strtok qui ont leur propre lot de
problèmes merdiques.


Pour strtok, je savais, elle est quand même limite utilisable quand on
sait ce qu'on fait, pour strncpy et strncat, je ne vois pas ce que vous
leur reprochez.


Avatar
Harpo
Targeur fou wrote:


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 ?


Le problème, c'est que le type qui a un problème dans ton code risque de
passer une bonne demi-heure à vérifier si ce n'est pas ça qui fait
planter le programme, après avoir planché sur la doc, même si ton code
est bon il lui restera peut-être un léger doute.
Autant printf est simple et permet d'éviter des buffer overflow, autant
scanf est une source d'erreurs et/ou de temps perdu à des conneries.
J'ai déjà essayé, j'ai laissé tomber, je laisse ça aux gurus du C.

Avatar
Emmanuel Delahaye
Harpo wrote on 27/04/05 :
Charlie Gordon wrote:

C'est trop merdique, utiliser les fonctions strxxx.


Sauf bien sûr strncpy, strncat et strtok qui ont leur propre lot de
problèmes merdiques.


Pour strtok, je savais, elle est quand même limite utilisable quand on
sait ce qu'on fait, pour strncpy et strncat, je ne vois pas ce que vous
leur reprochez.


- strncpy() ne met pas le 0 final quand il sature (chaine mal formée)

- strncat() est bien, à condition d'être bien révéillé. (la chaine de
destination doit être initialisée..., ne pas se tromper dans les
tailles passées en paramètre, laisser une place pour le 0 etc.). Je
m'en sert tout le temps pour faire des extractions en une ligne, ça me
va.

--
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
Charlie Gordon wrote on 27/04/05 :
OK pour les remarques qui n'étaient pas directement à ton attention, et sans
doute pas dans le bon ordre, mais même si on a pas le choix du langage et
qu'on doive traiter des chaines de caractères en C (*) scanf() est une
mauvaise solution, tout comme strtok(), strncpy, strncat() et d'autres.


strncat() est utilisable si on a un cerveau en état de marche.

--
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
Charlie Gordon
"Harpo" wrote in message
news:426fc39c$0$25036$
Charlie Gordon wrote:

C'est trop merdique, utiliser les fonctions strxxx.


Sauf bien sûr strncpy, strncat et strtok qui ont leur propre lot de
problèmes merdiques.


Pour strtok, je savais, elle est quand même limite utilisable quand on
sait ce qu'on fait, pour strncpy et strncat, je ne vois pas ce que vous
leur reprochez.


Je ne vois pas pourquoi on utiliserait strtok() quand strtok_r() est disponble.
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. Leur
utilisation est très souvent incorrecte, source d'inefficacité, d'erreurs ou de
debordements de tableaux (si si!) et donc à déconseiller.

Chqrlie.



Avatar
Charlie Gordon
"Emmanuel Delahaye" wrote in message
news:
Harpo wrote on 27/04/05 :
Charlie Gordon wrote:

C'est trop merdique, utiliser les fonctions strxxx.


Sauf bien sûr strncpy, strncat et strtok qui ont leur propre lot de
problèmes merdiques.


Pour strtok, je savais, elle est quand même limite utilisable quand on
sait ce qu'on fait, pour strncpy et strncat, je ne vois pas ce que vous
leur reprochez.


- strncpy() ne met pas le 0 final quand il sature (chaine mal formée)


en revanche, il initialise à 0 le reste du buffer quand il ne sature pas, ce qui
est rarement utile

- strncat() est bien, à condition d'être bien révéillé. (la chaine de
destination doit être initialisée..., ne pas se tromper dans les
tailles passées en paramètre, laisser une place pour le 0 etc.). Je
m'en sert tout le temps pour faire des extractions en une ligne, ça me
va.


attention : pour strncat le parametre numerique est le nombre maximum de char à
prendre dans la source, pas la taille du tableau de destination. Ce qui
signifie qu'il faut s'assurer autrement contre la possibilité d'un débordement
de tableau, y compris par le final.
Donc c'est trompeur.


Chqrlie.




1 2 3 4 5