Forum Programmation.c Hack de vfscanf(3), 4 questions

Posté par  .
Étiquettes : aucune
0
2
jan.
2006
Bonjour :)

Pour les besoins d'un projet, j'ai eu envie de bidouiller vfscanf pour l'adapter à mes besoins. J'ai quasi fini mon bidouillage mais j'ai quatre questions :

* de base mon snscanf ne travaille que sur des buffers, y aurait-il un moyen portable d'encapsuler des buffers dans des FILE * (en dehors de l'extension GNU fmemopen() pas portable du tout) pour que je puisse le généraliser aux fichiers ?

* J'ai ajouté la lecture de données binaires, mais je suis pas très content de ma fonction de conversion... Auriez vous des idées pour optimiser cette chose (particulièrement la boucle moche octet par octet) ? (testée sous Linusque x86 et OS X sur PearPC):


static unsigned long long _fetch_int(const u_char *b, size_t w, int flags)
{
    long long ret = 1;
int sys_endian = 0; /* 0 == big endian, little endian otherwise */

if ( (ret = 1) && (*(char *) & ret) == 1) sys_endian = 1;

if (flags & BIG) sys_endian = 0; else if (flags & LITTLE) sys_endian = 1;

if (flags & TRUNK) {
if (
(sys_endian == 1 && (flags & HIBYTES)) ||
(sys_endian == 0 && (flags & LOBYTES))
) {
/* should begin from the MSB */
[...] gros tas de if pour se placer à la fin du buffer [...]
b -= w;
}
}

ret = 0;

/* fetch bytes, storing them using the given endianness */
if (sys_endian) {
while (w --) ret += (long long) b[w] << w * 8;
} else {
size_t i = 0;
while (w --) ret += (long long) b[w] << (i ++ * 8);
}

return ret;
}


* Enfin, le meilleur pour la fin: l'équivalent en floating point ne marche pas... quand je caste b[w] en long double, j'ai gcc qui se roule en position foetale et couine "error: invalid operands to binary <<"... J'ai aussi un "invalid storage class" pour _fetch_float() (la même chose qu'au dessus mais avec des casts en fait :) ) si je lui fait renvoyer un long double...

Et de surcroît, dès que je caste mes binaires en float/double ils deviennent tout corrompu de l'intérieur, alors que si je me contente de les copier en tant qu'entier ça passe !

En fait:
memcpy(buf, & monfloat, sizeof(monfloat));
snscanf(buf, sizeof(buf), "%bf", & monfloat); /* foire */
alors que:
snscanf(buf, sizeof(buf), "%bi", & monfloat); /* marche */

et c'est le même code path aux casts près ø_ø

Le code source du bousin complet est là: http://mammouth.tuxfamily.org/vfscanf.c

Merci d'avoir tout lu, et merci d'avance pour vos réponses :)
  • # Hack de vfscanf(3), quelques réponses

    Posté par  . Évalué à 3.

    Enfin, le meilleur pour la fin: l'équivalent en floating point ne marche pas... quand je caste b[w] en long double, j'ai gcc qui se roule en position foetale et couine "error: invalid operands to binary <<
    Je dirai plutôt qu'il te tape sur les doigts pour avoir essayé une chose infâme. En effet, la norme impose que les deux arguments d'un opérateur de décalage soient des entiers:
    6.5.7 Bitwise shift operators
    Syntax
    1 shift-expression:
    additive-expression
    shift-expression << additive-expression
    shift-expression >> additive-expression
    Constraints
    2 Each of the operands shall have integer type.


    Et de surcroît, dès que je caste mes binaires en float/double ils deviennent tout corrompu de l'intérieur, alors que si je me contente de les copier en tant qu'entier ça passe !

    En fait:
    memcpy(buf, & monfloat, sizeof(monfloat));
    snscanf(buf, sizeof(buf), "%bf", & monfloat); /* foire */
    alors que:
    snscanf(buf, sizeof(buf), "%bi", & monfloat); /* marche */
    Tu peux être plus précis ? Tu obtiens quoi comme résultat, précisément ? Et en quoi ça n'est pas ce que tu veux ?

    while (w --) ret += (long long) b[w] << w * 8;
    J'ai un doute, là... Tu ne serais pas, par hasard, en train de supposer qu'un unsigned char fait 8 bits ?
    Tu définis ta fonction comme retournant un unsigned long long et ensuite tu définis ret comme un long long... Ca tombe bien, vu que le code de ta fonction fait que ret pourra contenir une valeur négative ! (bah oui, s'il y a débordement : overflow sur un entier signé, ça peut faire des dégâts collatéraux) Et la conversion d'un entier signé contenant une valeur négative en un entier non-signé se fait modulo le max de l'entier non signé +1 (cf. 6.3.1.3.-2)

    D'où:
    unsigned long long ret = 0;
    /* ... */
    while (w --)
    {
        ret += ((unsigned long long) b[w]) << (w * CHAR_BIT);
    }
    en castant vers un type non signé (c'est plus sûr et évite les comportements indéfinis de certaines limites), et en (sur)parenthésant afin de bien voir ce qu'on fait (toujours faire attention aux règles de priorité des opérateurs...)

    J'ai aussi un "invalid storage class" pour _fetch_float() (la même chose qu'au dessus mais avec des casts en fait :) ) si je lui fait renvoyer un long double...
    Cf. 6.3.1.4-2; je présume que le domaine de valeurs de long long déborde celle de long double sur ton environnement... et donc, quand tu retournes le contenu de ret, il n'est pas garanti que la conversion se fasse (je n'ai pas trouvé si la norme spécifie que cette conversion doit être ok ou non. Je suppose que c'est undefined...) Ca ne serait pas plus simple de définir ret comme un long double dans ce cas ? Ca éviterait des prises de tête à calculer les écarts entre les bornes de <limits.h>...
    • [^] # Re: Hack de vfscanf(3), quelques réponses

      Posté par  . Évalué à 1.

      Merci pour ce commentaire éclairé (dommage qu'on ne puisse pas plusser plusieurs fois), j'ai corrigé tout ça :)

      Par contre le problème avec les floats reste entier... Voilà ce qui se passe quand je fais:


      printf("float, system endianness : %f (0x%x)\n", f, (int) f);
      memcpy(float_buffer, (char *) & f, sizeof(f));
      f = 0;
      r = snscanf(float_buffer, sizeof(long_buffer), "%bf", & f);
      printf("returns %i, i = %f (0x%x)\n", r, f, (int) f);


      Sur ma Slack x86, le programme de test donne ça :
      float, system endianness : 1337.123413 (0x539)
      returns 4, i = 4079200000.000000 (0x80000000)

      Sur Mac OS X, PearPC, ça donne:
      float, system endianness : 1337.123413 (0x539)
      returns 4, i = 1151804416.000000 (0x44a72400)

      Voilà ce qui se passe dans snscanf pour les floats binaires:

      if ((flags & SUPPRESS) == 0) {
      double res; /* d'origine */

      if (flags & BINARY) {
      res = _fetch_float(b, width, flags);
      b += width; bufsize -= width;
      } else {
      *p = '\0';
      res = strtod(buf, (char **) NULL);
      }

      if (flags & LONGDBL)
      *va_arg(ap, long double *) = res;
      else if (flags & LONG)
      *va_arg(ap, double *) = res;
      else
      *va_arg(ap, float *) = res;
      nassigned++;
      }


      Et la fonction de conversion, clone de celle utilisée pour les int:

      static long double _fetch_float(const u_char *b, size_t w, int flags)
      {
      long double ret = 1;
      int sys_endian = 0; /* 0 == big endian, little endian otherwise */

      if (*(char *) & ret == 1) sys_endian = 1;

      if (flags & BIG) sys_endian = 0; else if (flags & LITTLE) sys_endian = 1;

      if (flags & TRUNK) {
      if (
      (sys_endian == 1 && (flags & HIBYTES)) ||
      (sys_endian == 0 && (flags & LOBYTES))
      ) {
      /* should begin from the MSB */
      if (flags & LONGDBL) b += sizeof(long double);
      else if (flags & LONG) b += sizeof(double);
      else b += sizeof(float);
      b -= w;
      }
      }

      ret = 0;

      /* fetch bytes, storing them using the given endianness */
      if (sys_endian) {
      while (w --) ret += ((u_quad_t) b[w]) << (w * CHAR_BIT);
      } else {
      size_t i = 0;
      while (w --) ret += ((u_quad_t) b[w]) << (i ++ * CHAR_BIT);
      }

      return ret;
      }


      Mais le plus étrange, c'est que le code suivant marche:
      r = snscanf(float_buffer, sizeof(long_buffer), "%bi", & f);
      Donc en fait en utilisant le code path des entiers, ça passe même sous Mac (mais bon ça marche que sur les archis où sizeof(int) == sizeof(float)).

      Alors, peut être que c'est les shifts qui passent pas, je vais essayer avec une union, mais bon, ça me trouble que _fetch_int() marche et pas ça :)

      J'ai mis à jour le source sur http://mammouth.tuxfamily.org/vfscanf.c si tu souhaites voir la chose en intégralité.
      • [^] # Re: Hack de vfscanf(3), quelques réponses

        Posté par  . Évalué à 2.

        J'ai beau lire et relire ton code, autant je suis d'accord pour le traitement pour les entiers (bon, modulo le fait que je ne l'ai pas testé, je suis d'accord sur l'algo), autant je ne comprends pas comment tu veux pouvoir appliquer le même algo à des nombres flottants (quelque soit le type C, d'ailleurs).
        Les représentations machine des entiers permettent de prendre des blocs, avec décalages par bits et produits par des puissances de 2, c'est du classique. Mais les représentation machine des flottants... c'est d'un autre niveau, et je ne vois pas d'où tu peux sortir un
        while (w --) ret += ((u_quad_t) b[w]) << (w * CHAR_BIT);
        pour traiter des flottants...
        Je te conseille de regarder plus avant comment le C définit sa représentation des nombres flottants ([1] 6.2.5, 6.2.6., annexe F, et la norme IEC/IEEE qui va bien)... tu verras que c'est très libre comme définition, et que donc un traitement 'binaire' des nombres flottants est loin d'être facile (et je ne parle même pas de portabilité)... Et regarde aussi [2], c'est un doc que j'ai trouvé il y a peu, mais qui est très intéresant sur le calcul flottant (et qui parle assez simplement de la norme IEEE). Ou sinon wikipedia, il doit aussi y avoir de l'info, je suppose...

        A vrai dire, je ne sais pas comment c'est représenté en machine, par exemple. dans quel ordres sont mis (ou peuvent être mis) le signe, l'exposant ou la mantisse, ni quelle marge il y a pour les types C associés : la permière page de l'annexe F dit que float et double sont associés exactement à des types IEC, et que long double peut être un parmi plusieurs types IEC, mais je en sais pas comment ces dits types sont définis eux-mêmes - je te laisse le soin de creuser la question... Et, en derniers recours, c'est la doc de ta plate-forme (voire de ton processeur) qui dit comment tu dois faire.

        Pour en revenir à ton application, fonctionnellement, pourquoi as-tu besoin de faire de tels traitements ? Ton appli transfère des informations entre différentes machines ? Tu veux parser des fichiers binaires ?

        [1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
        [2] http://cch.loria.fr/documentation/IEEE754/ACM/goldberg.pdf
        • [^] # Re: Hack de vfscanf(3), quelques réponses

          Posté par  . Évalué à 1.

          OK, j'ai implémenté ça en utilisant la doc sur IEEE754 et ça marche au poil :)

          Apparemment ça marchait sur 32 bits par coïncidence, le single precision étant bien aligné... mais c'est plus valable en 64 bits.

          Merci beaucoup pour ton aide :)

          > Pour en revenir à ton application, fonctionnellement, pourquoi as-tu besoin
          > de faire de tels traitements ? Ton appli transfère des informations entre
          > différentes machines ? Tu veux parser des fichiers binaires ?

          En fait c'est pour mon serveur idiot: je compte utiliser le sscanf modifié pour lire le buffer du socket confortablement, d'où nécessité de gérer les formats binaires de base :)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.