Bien programmer en langage C
Vous souhaitez réagir à ce message ? Créez un compte en quelques clics ou connectez-vous pour continuer.
Le Deal du moment :
Manga Chainsaw Man : où acheter le Tome 17 ...
Voir le deal
19.99 €

Saisie de chaines de caractères

Aller en bas

Saisie de chaines de caractères Empty Saisie de chaines de caractères

Message  -ed- Lun 26 Mai 2008 - 17:37

Je suis en train d'écrire mes propres fonctions de saisie pour m'exercer et pour ne plus m'embêter avec scanf() et fgets(). J'aimerais savoir si avez des suggestions ou si vous voyez des erreurs avant que j'attaque la lecture de nombres (mes quelques essais ont eu l'air de marcher).

L'interface est correcte, bien que j'aurais mis un size_t pour la taille...

A l'aide de ce test unitaire, j'ai essayé de mettre le code en défaut, mais je n'y suis pas arrivé :
Code:

#include <stdio.h>

/*
 * readString
 *
 * Lit (nbCar-1) caractères sur stdin et les stocke à l'adresse chaine
 * Le buffer de stdin est vide après l'appel de la fonction
 *
 * Retour :
 * - en cas d'erreur de lecture ou si nbCar <= 1 : -1
 * - si la chaîne a été tronquée : 0
 * - si la lecture s'est passée correctement : 1
 */

int readString (char chaine[], unsigned int nbCar)
{
  unsigned int i;
  int carTemp;
  int valRetour;

  if (nbCar <= 1)
  {
      valRetour = -1;
  }
  else
  {
      i = 0;
      carTemp = getchar ();

/* lecture de stdin et copie dans la chaîne */
      while (carTemp != '\n' && carTemp != EOF && i < nbCar - 1)
      {
        chaine[i] = carTemp;
        carTemp = getchar ();
        i++;
      }

/* marquage de la fin de la chaîne */
      chaine[i] = 0;

/* si il y a eu une erreur de lecture */
      if (carTemp == EOF)
      {
        valRetour = -1;
      }
/* si des caractères on été laissés dans le buffer */
      else if (carTemp != '\n')
      {
        valRetour = 0;

        carTemp = getchar ();

        while (carTemp != '\n' && carTemp != EOF)
        {
            carTemp = getchar ();
        }
      }
/* si tout s'est passé normalement */
      else
      {
        valRetour = 1;
      }
  }
  return valRetour;
}

#if TEST

#include "ed/inc/sys.h"

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

/*

* - en cas d'erreur de lecture ou si nbCar <= 1 : -1
* - si la chaîne a été tronquée : 0
* - si la lecture s'est passée correctement : 1

*/
static char const *s_ret (int ret)
{
  char const *s;
  switch (ret)
  {
  case -1:
      s = "en cas d'erreur de lecture ou si nbCar <= 1";
      break;
  case 0:
      s = "la chaine a ete tronquee";
      break;
  case 1:
      s = "la lecture s'est passee correctement";
      break;
  default:
      s = "erreur inconnue";
  }
  return s;
}

int main (void)
{
  char s[8];

  LIM_STR (s);
  do
  {
      printf ("> ");
      fflush (stdout);
      {
        int ret = readString (s, sizeof s);
        CHK_STR (s);
        printf ("'%s'\n", s);
        printf ("ret = %d (%s)\n", ret, s_ret (ret));
      }
  }
  while (strcmp (s, "quit") != 0);

  return 0;
}
#endif
Par contre, le cas de la chaine valant NULL devrait être traité (ça peut arriver, et il est bon que le cas soit traité proprement.).

Pour les codes retournés, il est d'usage d'utiliser 0 pour OK et 1 à N pour KO. Ca permet de nommer la variable 'err' et non 'ret' comme ici...

Le codage est correct.

Voilà à quoi ressemble le reste :
Code:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

/*
 * readString
 *
 * Lit (nbCar-1) caractères sur stdin et les stocke à l'adresse chaine
 * Le buffer de stdin est vide après l'appel de la fonction
 *
 *
 * Retour :
 * - si la lecture s'est passée correctement : 0
 * - si la chaîne a été tronquée : 1
 * - si erreur de lecture, si nbCar <= 1 ou si chaine == NULL : 2
 */
unsigned int readString (char chaine[], size_t nbCar)
{
  unsigned int err;

/* si la saisie est impossible */
  if (nbCar <= 1 || chaine == NULL)
  {
      err = 2;
  }
/* si elle est possible */
  else
  {
      int carTemp;
      unsigned int i = 0;

/* lecture de stdin et copie dans la chaîne */
      while ((carTemp = fgetc (stdin)) != '\n'
            && carTemp != EOF && i < nbCar - 1)
      {
        chaine[i] = carTemp;
        ++i;
      }

/* marquage de la fin de la chaîne */
      chaine[i] = '\0';

/* si il y a eu une erreur de lecture */
      if (carTemp == EOF)
      {
        err = 2;
      }
/* si des caractères on été laissés dans le buffer */
      else if (carTemp != '\n')
      {
        err = 1;

        while ((carTemp = fgetc (stdin)) != '\n' && carTemp != EOF)
        {
        }
      }
/* si tout s'est passé normalement */
      else
      {
        err = 0;
      }
  }

  return err;
}

/*
 * readInteger
 *
 * Lit 10 caractères maximum sur stdin et stocke le nombre entier correspondant
 * à l'adresse p_nb
 * Le buffer de stdin est vide après l'appel de la fonction
 *
 *
 * Retour :
 * - si la lecture s'est passée correctement : 0
 * - si le nombre dépasse la capacité d'un long ou si plus
 * de 10 caractères sont lus : 1
 * (dans le premier cas *p_nb est inchangé,
 * dans le deuxième *p_nb est borné à LONG_MIN ou LONG_MAX)
 * - si erreur de lecture ou si p_nb == NULL : 2
 */
unsigned int readLong (long *p_nb)
{
  unsigned int err;

  if (p_nb == NULL)
  {
      err = 2;
  }
  else
  {
      char chaineTemp[11];
      char *p_carInv;
      unsigned int resLecture;

      resLecture = readString (chaineTemp, sizeof chaineTemp);

/* si lecture impossible */
      if (resLecture == 1)
      {
        err = 1;
      }
/* si chaîne tronquée */
      else if (resLecture == 2)
      {
        err = 2;
      }
      else
      {
        long resConversion;

        errno = 0;
        resConversion = strtol (chaineTemp, &p_carInv, 10);

/* si débordement pendant la conversion */
        if (errno == ERANGE)
        {
            *p_nb = resConversion;
            err = 1;
        }
/* si saisie réussie */
        else if (*p_carInv == '\0')
        {
            *p_nb = resConversion;
            err = 0;
        }
/* si conversion impossible */
        else
        {
            err = 2;
        }
      }
  }
  return err;
}

/*
 * readDouble
 *
 * Lit 127 caractères maximum sur stdin et stocke le nombre flottant
 * correspondant à l'adresse p_nb
 * Le buffer de stdin est vide après l'appel de la fonction
 *
 *
 * Retour :
 * - si la lecture s'est passée correctement : 0
 * - si le nombre dépasse la capacité d'un double ou si plus
 * de 127 caractères sont lus : 1
 * (dans le premier cas *p_nb est inchangé,
 * dans le deuxième *p_nb est borné à +/- HUGE_VAL)
 * - si erreur de lecture ou si p_nb == NULL : 2
 */
unsigned int readDouble (double *p_nb)
{
  unsigned int err;

  if (p_nb == NULL)
  {
      err = 2;
  }
  else
  {
      char chaineTemp[128];
      char *p_carInv;
      unsigned int resLecture;

      resLecture = readString (chaineTemp, sizeof chaineTemp);

/* si lecture impossible */
      if (resLecture == 1)
      {
        err = 1;
      }
/* si chaîne tronquée */
      else if (resLecture == 2)
      {
        err = 2;
      }
      else
      {
        double resConversion;

        errno = 0;
        resConversion = strtod (chaineTemp, &p_carInv);

/* si débordement pendant la conversion */
        if (errno == ERANGE)
        {
            *p_nb = resConversion;
            err = 1;
        }
/* si saisie réussie */
        else if (*p_carInv == '\0')
        {
            *p_nb = resConversion;
            err = 0;
        }
/* si conversion impossible */
        else
        {
            err = 2;
        }
      }
  }
  return err;
}
Les nombres de caractères de chaineTemp[] sont "magiques", est-ce qu'il y a des macros définies qui permettent de faire ça de façon portable ou il faut les créer soi-même ?

Et effectivement, ça entraine un bug : la saisie d'entier ne prend pas -2147483648 (LONG_MIN chez moi...).

Il m'a été donné cette macro :
Code:

/* (c) Eric Sosman 2001 */
#define BIG_ENOUGH (1 + (sizeof(long) * CHAR_BIT + 2) / 3 + 1)
pour dimensionner la taille d'un tableau de chaine décimale représentant un entier.

A y réfléchir plus sérieusement, 'BIG-ENOUGH' est probablement un nom 'gag'...

Disons DECIMAL_SIZE, par exemple...

Pour les double, c'est plus délicat. Tu as mis 128 'au jugé', c'est assez bien vu, mais insuffisant. En fait, sur ma machine, ça dépasse les 300.

En effet, les limites sont :

<...>
Saisie de reel : -1 pour quitter
2.22507e-308 - 1.79769e+308

Tant qu'on saisie en mode scientifique, ça va. Mais si on saisie en mode virgule fixe, on peut s'attendre à mettre 308 chiffres, plus le signe, la virgule...

Les macros qui donnent les limites des réels (décimal, mantisse, exposant) sont dans <float.h>

À quoi est-ce que ça sert de nommer la variable de retour err ? C'est une convention ?

err comme erreur. C'est une convention personnelle proche de la norme :

0 = OK
<> 0 = erreur.

Je ne comprends pas bien le retour de strtod() :
The C book a écrit:On overflow, returns ±HUGE_VAL according to the sign of the result; on underflow, returns zero. In either case, errno is set to ERANGE.
n1124 a écrit:
If the result underflows (7.12.1), the functions return a value whose magnitude is no greater
than the smallest normalized positive number in the return type; whether errno acquires
the value ERANGE is implementation-defined.

Ca veut dire à peu près la même chose. L'important est que errno vaille ERANGE.

Voici le test que j'ai ajouté à ton fichier (à compiler avec -DTEST pour qu'il soit actif) :
Code:

#ifdef TEST

#include <float.h>
#include <math.h>
#include <limits.h>

int main (void)
{
  puts ("Saisie de chaine : \"\" pour quitter");
  {
      char s[4];

      do
      {
        unsigned err = readString (s, sizeof s);

        printf ("err=%d s='%s'\n", err, s);
      }
      while (*s != 0);
  }

  puts ("Saisie d'entier : -1 pour quitter");
  printf ("%ld - %ld\n", LONG_MIN, LONG_MAX);
  {
      long n;
      do
      {
        unsigned err = readLong (&n);
        printf ("err=%d n=%ld\n", err, n);
      }
      while (n != -1);
  }

  puts ("Saisie de reel : -1 pour quitter");
  printf ("%g - %g\n", DBL_MIN, DBL_MAX);
  {
      double n;
      do
      {
        unsigned err = readDouble (&n);
        printf ("err=%d n=%g\n", err, n);
      }
      while (fabs (n - -1) >= DBL_EPSILON);
  }

  return 0;
}

#endif
-ed-
-ed-
Admin
Admin

Messages : 290
Date d'inscription : 26/05/2008
Age : 67
Localisation : Paris 14eme arrondissement (75, France)

http://bien-programmer.fr

Revenir en haut Aller en bas

Revenir en haut

- Sujets similaires

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum
Ne ratez plus aucun deal !
Abonnez-vous pour recevoir par notification une sélection des meilleurs deals chaque jour.
IgnorerAutoriser