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
Emmanuel Delahaye
Harpo wrote on 28/04/05 :
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.


Personne n'a dit que ça ne servait à rien. Par contre ils sont
insuffisants. Je ne connais pas de moyen automatique de detecter un UB.

--
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
Charlie Gordon
"Richard Delorme" wrote in message
news:4271150a$0$26070$

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++;
}


Bien vu !
Que reproches-tu à :

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

voire :

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

Chqrlie.

PS: OK je sors.


Avatar
Antoine Leca
Richard Delorme en el mensaje
news:4271150a$0$26070$
char *var1, *var2;
var1 = msg;
if ((var2 = strchr(msg, ':')) != NULL) {
*var2 = '';
var2++;
}


else
# undefined behaviour at line <machin> when using var2


;-)



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.



Mais quand on détecte sans agir...

En fait, les cas d'erreurs (quelqu'ils soient) nécessite un traitement qui
dépend de l'énoncé (qui reste inconnu ;-)). sscanf() met tout dans le même
panier (sauf les éventuels dépassement de taille du second argument), ce qui
peut selon les cas être ou pas un problème. Au contraire, le basique
strchr() donne le contrôle le plus précis sur les cas d'erreur, alors
peut-être que le traitement sera toujours le même (ce qui génère
immanquablement du code spaghetti, avec deux fois plus de lignes pour gérer
les cas anormaux que de lignes « normales »).


Antoine



Avatar
Harpo
Emmanuel Delahaye wrote:

Harpo wrote on 28/04/05 :
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.


Personne n'a dit que ça ne servait à rien. Par contre ils sont
insuffisants. Je ne connais pas de moyen automatique de detecter un
UB.


J'ai aussi dit que les tests exhaustifs étaient une vue de l'esprit mais
que l'on pouvait s'en approcher.
Très généralement on finit par des tests, c'est aussi pour cela qu'ils
sont importants. Bien sûr il faut des bons programmeurs qui programment
bien mais qu'est-ce qui permet de valider cela ?


Avatar
Charlie Gordon
"Emmanuel Delahaye" wrote in message
news:
(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!


Bien sûr que si ;-) mais comme tu le fais remarquer, ces tests arrivent un peu
tard.

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


tu utilises bien getftime() qui est spécifique de MS/DOS.

Il y a des méthodes simples pour éviter l'UB dans ces fonctions.
Tu dois juste décider quelle est la sémantique de tes API dans le cas ou le
buffer passé est trop court.
Tu commences par calculer dans un buffer local d'une taille suffisante la chaine
résultat. Pour FIC_str_date, la taille maxi est 3 + la taille maxi d'un entier
(DEC_INTEGER_MAX ?), que tu peux raisonnablement limiter à 64 bits, sachant
qu'en pratique ces entiers sont dans une plage connue. Même chose pour
FIC_str_time, ipv4_num2str (calcul différent, mais taille maxi connue). Ensuite
tu testes si la chaine construite peut être copiée dans le tableau destination,
et suivant la sémantique que tu auras choisie, tu tronques et/ou tu retournes
une erreur.

Concernant sprintf(), il y a d'autres cas qui me chagrinent :

dans FIC_str_date, pourquoi le format utilise-t-il des - dans un cas et des /
dans l'autre ?

dans FIC_str_size, pour lequel la méthode ci-dessus s'applique aussi, la chaîne
calculée pour la taille du fichier a un format surprenant : 3 groupes de 3
chiffres éventuellement complétés à gauche par des 0, suivis d'une espace dans
le cas général et la chaine fixe "??? ??? ???" pour une taille nulle ou une
erreur. Ce n'est pas très cohérent, mais il y a pire : de nos jours il n'est
pas rare de voir des fichiers de plus d'un milliard d'octets, et de plus le
calcul fait dans sprintf() donne une erreur de conversion pour les fichiers de
plus de 1 million d'octets :

sprintf (ssize
,"%03lu %03lu %03lu "
,(ulong) (dw / 1000000UL)
,(ulong) (dw / 1000UL)
,(ulong) (dw % 1000UL)
);

La solution générique consiste à formater le nombre en %lu dans un buffer local
et à parcourir la chaine résultante de droite à gauche en recopiant vers la
destination en insérant les séparateurs de milliers appropriés, qui dépendent du
la localisation.
Enfin cette fonction ne retourne aucun code permettant de tester une éventuelle
erreur alors que le contenu du buffer destination serait indéterminé par exemple
dans le cas où sfile == NULL.

Dans ipv4_num2str(), tu refuses de formater l'adresse IP si le buffer
destination a une taille inférieure
à IPV4_STR_SIZE qui vaut sizeof("255.255.255.255"). Or de nombreuses adresses
IP prennent moins de caractères ;-)

Dans ipv4_str2num(), tu utilises le même format pour sscanf et sprintf, c'est un
peu risqué car ces deux fonctions n'ont pas toujours la même sémantique pour les
formats : par exemple %03u.%03u.%03u.%03u n'aurait pas le même sens. Enfin tu
considères que le parse est correct si au moins un champ de l'adresse IP a été
traité, alors que les autres seront nuls, ce qui n'est pas la sémantique
habituelle des adresses IP abrégées quand elles sont reconnues (127.1 ->
127.0.0.1). Il serait plus conforme d'écrire :

err = (n != 4);

Si ces discussions sont perçues comme une pollution du forum, je te ferai une
critique plus complète de la clib en privé.

Chqrlie.

PS: pour les courageux qui lisent mes posts jusqu'au bout, voici un petit test
d'acuité visuelle :
Pour quelles raisons ED ne doit-il pas utiliser le code suivant pour sa
conversion de dates :

#define MAX_DATE_SIZE sizeof("??/??/????")

char buf[MAX_DATE_SIZE];
sprintf(buf, "%02d/%02d/%04d",
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980);


Avatar
Targeur fou
Charlie Gordon wrote:
"Emmanuel Delahaye" wrote in message


[coupé]


PS: pour les courageux qui lisent mes posts jusqu'au bout, voici un
petit test

d'acuité visuelle :
Pour quelles raisons ED ne doit-il pas utiliser le code suivant pour
sa

conversion de dates :


C'est pas le sizeof qui est correct..., C'est peut être au niveau du
format dans sprintf, les champs de la structure ftime étant des champs
de bits unsigned, c'est un format "%02u/%02u/%04u" qu'il faudrait, j'ai
bon ?

#define MAX_DATE_SIZE sizeof("??/??/????")

char buf[MAX_DATE_SIZE];
sprintf(buf, "%02d/%02d/%04d",
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980);


Regis

Avatar
Charlie Gordon
"Harpo" wrote in message
news:4271195b$0$1255$
Charlie Gordon wrote:

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.


Hélas, ça grouille de partout, ça se propage comme les virus : par infection
d'un porteur plus ou moins sain qui les reproduit comme ses propres cellules.
Dans le cas de l'informatique, on appelle le porteur "programmeur" ou
"développeur", qui est rarement immunisé contre les constructions infectieuses
qui lui tombent sous les yeux, et qu'il reproduit ensuite avec le copier-coller
ou la réexpression avec mutation maligne possible. On a aussi des facteurs
extérieurs qui favorisent la prolifération : la pression du chef de projet, la
non utilisation des préservatifs (-Wall -Werror)...

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.


Le problème, c'est que le coût des bugs croit exponentiellement avec le temps
qu'on met à les trouver :

1: bug corrigé au moment de la frappe, par réflexe du programmeur bien formé,
attentif et éveillé :

10: bug corrigé car détecté par le compilateur (helas trop souvent le
compilateur n'est pas configuré pour signaler les cas suspects ou le programmeur
ignore les warnings : solution -Wall -W -Werror).

100: bug corrigé lors d'une relecture dans le cadre d'une revue de code (rares
sont les sociétés qui procèdent à ce genre de démarche)

1000: bug corrigé après détection d'un problème par des tests (pareillement,
bien peu de sociétés procèdent à des tests unitaires ou systématiques, mais le
concept de test exhaustif est en revanche bien ancré chez les chef de projets et
toute la chaîne hiérarchique qui les commande).

10000: bug corrigé après problème rencontré avant la commercialisation du
produit (lors d'une demo, d'un beta-test...)

100000: bug corrigé après le lancement commercial du produit, avec mise-à-jour
chez le client et dégats collatéraux éventuels.

1000000+: bug corrigé après la disparition de l'éditeur du logiciel, sans les
sources qui ont disparu, mais avec une pression énorme des utilisateurs du
logiciel devenu indispensable au fonctionnement de l'entreprise (coût supporté
entièrement par le client)

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.


Détrompe-toi, on trouve de tout dans l'embarqué.

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.


Bien sûr, mais il y a surtout énormément plus de code dans le TGV, avec donc
d'autant plus de sources de problèmes.
On y a beaucoup recours à la preuve de processus, avec des systèmes de
génération de code et tout...
Mais c'est surtout l'expérience qui permet de réduire les problèmes : nombreux
sont les TGV (ou les Airbus) qui circulent, et donc les problèmes qui se font
jour sont corrigés au fur et à mesure... Alors que pour Ariane V ça a produit un
beau feu d'artifice.

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.


Je ne dis pas que les tests ne servent à rien, loin de là ! Mais il est
illusoire de s'imaginer qu'ils permettent de trouver tous les bugs. Or le
management de projets informatiques semble le croire, alors que les personnels
qui produisent et conduisent les tests en question sont bien souvent encore
moins qualifiés ou motivés que ceux qui produisent l'architecture ou le code des
systèmes. Quand ça se voit trop, l'argument massue est le manque de moyens, et
on obtient alors d'augmenter le nombre d'intervenants, ce qui est une approche
simpliste et souvent fatale du problème.

Chqrlie.


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

Charlie Gordon wrote:
"Emmanuel Delahaye" wrote in message


[coupé]


PS: pour les courageux qui lisent mes posts jusqu'au bout, voici un
petit test d'acuité visuelle :
Pour quelles raisons ED ne doit-il pas utiliser le code suivant pour
sa conversion de dates :


C'est pas le sizeof qui est correct..., C'est peut être au niveau du
format dans sprintf, les champs de la structure ftime étant des champs
de bits unsigned, c'est un format "%02u/%02u/%04u" qu'il faudrait, j'ai
bon ?

#define MAX_DATE_SIZE sizeof("??/??/????")

char buf[MAX_DATE_SIZE];
sprintf(buf, "%02d/%02d/%04d",
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980);



Bonne remarque : les champs sont effectivement unsigned :

#include <dos.h>

int getftime(int handle, struct ftime *ptr);

Description
Get the timestamp for the given file handle. The return structure is as follows:
struct ftime {
unsigned ft_tsec:5; /* 0-29, double to get real seconds */
unsigned ft_min:6; /* 0-59 */
unsigned ft_hour:5; /* 0-23 */
unsigned ft_day:5; /* 1-31 */
unsigned ft_month:4; /* 1-12 */
unsigned ft_year:7; /* since 1980 */
}

Mais il y a des problèmes plus graves dans ce code :

#define MAX_DATE_SIZE sizeof("??/??/????")
char buf[MAX_DATE_SIZE];
sprintf(buf, "%02u/%02u/%04u",
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980);

Chqrlie.

Targeur Fou : 1 point


Avatar
Antoine Leca
En <news:,
Targeur fou va escriure:
Charlie Gordon wrote:
PS: pour les courageux qui lisent mes posts jusqu'au bout,



J'avoue que je n'étais pas allé jusque là. Donc merci Régis pour avoir
ressorti la prose de Charles ;-).


voici un petit test d'acuité visuelle :
Pour quelles raisons ED ne doit-il pas utiliser le code suivant pour
sa conversion de dates :


C'est pas le sizeof qui est correct..., C'est peut être au niveau du
format dans sprintf, les champs de la structure ftime étant des champs
de bits unsigned, c'est un format "%02u/%02u/%04u" qu'il faudrait,
j'ai bon ?


En supposant que la structure ftime est bien celle de MS-DOS (5 bits, 4
bits, 7 bits), tous inférieurs à 16, donc les valeurs seront promues en int
et non pas en unsigned (à moins d'utiliser un compilateur pré-ANSI, mais
alors il n'y a plus de souci entre %d et %u pour des valeurs positives). Et
bien sûr l'addition avec la constante entière 1980 < 32640 ne change rien à
cela. Donc non, c'est bien %d qu'il faut utiliser; ou alors tu programmes
défensivement et tu ajoutes 0u ou 1980u à chaque membre.


Je dirais que les valeurs 0 pour ft_day et ft_month ne signifient pas le
jour ou le mois numéro 0 :^), mais plutôt l'absence de l'information; à ce
titre il faudrait les traiter à part, à mon sens.

Ou alors c'est le syndrome 9/11 = 8601...


Antoine


Avatar
Antoine Leca
En <news:d4svcg$pda$, Charlie Gordon va escriure:

#define MAX_DATE_SIZE sizeof("??/??/????")
char buf[MAX_DATE_SIZE];
sprintf(buf, "%02u/%02u/%04u",
,ftimep.ft_day
,ftimep.ft_month
,ftimep.ft_year + 1980);


Si tu vas par là...

sprintf(buf, "%02u/%02u/%04u",
ftimep.ft_day + 0u,
ftimep.ft_month + 0u,
ftimep.ft_year + 1980u);


Mais il y a des problèmes plus graves dans ce code :


L'utilisation d'une structure étiquettée ftime avec un identificateur ftimep
qui suggère un pointeur ?

(En maintenance cela vaut 3 points en moins: 1 point pour le non-respect des
conventions qui disent le contraire « Ne cachez pas vos pointeurs », plus 2
points rajoutés par le mainteneur qui s'est fait prendre et a changé le .
en -> en pensant qu'il avait affaire à un pointeur ;-)).


Ou alors tu penses au sizeof «juste limite» ?


Antoine