IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Petit entraînement : création d'une fonction de saisie de chaîne de caractères


Sujet :

C

  1. #1
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut Petit entraînement : création d'une fonction de saisie de chaîne de caractères
    Bonsoir,

    Pour m'entraîner un peu en C (il paraît que c'est en pratiquant qu'on apprend) j'ai essayé de créer une fonction lisant les caractères saisis aux claviers et les copiant dans une chaine prévue à cet effet. Je sais bien qu'il existe des fonctions pour ça, je crée celle-ci uniquement pour m'entraîner.

    Elle comprend deux paramètres : la chaîne cible (où on va écrire) et sa taille (pour éviter de déborder).

    Le but de la fonction est uniquement de saisir une chaîne de caractères, je ne m'intéresse pas pour l'instant à convertir la chaine saisie en entier ou en flottant.

    Voici le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     
    // lire_chaine.c
     
    #include <stdlib.h>
    #include <stdio.h>
    #include "lire_chaine.h"
     
    /*
    lireChaine
    Lit une chaine de caractères.
    Retourne EXIT_SUCCESS si tout se passe bien
    Retourne EXIT_FAILURE si la taille de la chaine saisie est supérieure
    à celle de la chaine cible.
     
    Paramètre 1 : la chaine cible
    Paramètre 2 : la taille de cette chaine
    */
     
    int lireChaine(char* chaine, long taille)
    {
        char buffer[300] = {0}; // Le buffer qui va recevoir la chaine tappée
        char caractereLu = 0; // La variable qui reçoit les caractères un a un
        long i = 0; // Pour les boucles
     
        /* Tant qu'on a pas entré de retour à la ligne et tant qu'on ne dépasse
        pas la taille de la chaine dans laquelle on veut écrire, on continue
        de recevoir les caractères entrants  */
        while ((caractereLu = getchar()) != '\n' && i <= taille)
        {
            buffer[i] = caractereLu;
            i++;
        }
     
        /* Si la taille de la chaine dans laquelle on veut écrire a été dépassée,
        on écrit une erreur et on retourne un échec */
        if (i-1 >= taille)
        {
            fprintf(stderr, "Erreur : la taille de la chaine saisie depasse celle de la chaine cible.\n");
            return EXIT_FAILURE;
        }
     
        /* Si on a pas dépassé la taille de la chaine dans laquelle on veut écrire,
        on ajoute le caractere NULL au buffer et on copie vers la chaine */
        buffer[i] = '\0';
        strcpy(chaine, buffer);
     
        /* Tout s'est bien passé, on retourne EXIT_SUCCESS */
        return EXIT_SUCCESS;
    }
    Je l'ai testé avec ce code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    // main.c
     
    #include <stdio.h>
    #include <stdlib.h>
     
    int main(int argc, char *argv[])
    {
        char chaine[20] = {0};
     
        printf("Entrez une chaine : ");
        fflush(stdout);
     
        if (lireChaine(chaine, 20) == EXIT_SUCCESS)
        {
            printf("La chaine est : \"%s\" et contient %d caracteres", chaine, strlen(chaine));
        }
     
     
     
       return EXIT_SUCCESS;
    }
    Ce qui me donne à l'écran :

    Entrez une chaine : Chaine de test
    La chaine est : "Chaine de test" et contient 14 caracteres
    Donc apparament, elle fonctionne bien.
    Pourriez-vous m'indiquer si la fonction que j'ai créée a des défauts ?
    Si oui, lesquels ?
    Comment pourrais-je l'améliorer ?

    Par avance, merci.

  2. #2
    Rédacteur
    Avatar de Franck.H
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Janvier 2004
    Messages
    6 951
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Service public

    Informations forums :
    Inscription : Janvier 2004
    Messages : 6 951
    Points : 12 462
    Points
    12 462
    Par défaut
    Salut !


    Ton code dans sa globalité me semble pas trop mal mais j'ai remarqué deux choses

    1. En générale, on utilise les constantes EXIT_SUCCESS et EXIT_FAILURE pour les fonctions de fermeture du programme genre return ou exit
    2. Si tu permet à ta fonction de déterminer la taille de la chaîne a récupérer, je n'aurais mis que cet argument. J'aurais en effet permis à la fonction de renvoyer une addresse allouée dynamiquement contenant la chaîne.

      L'effet, est que tu pourras te débarasser d'un buffer de 300 char statiques ce qui n'est pas rien et de pouvoir allouer une zone de la taille de la chaîne (en comptant bien sûr un espace en plus pour le zéro de fin de chaîne). En somme, ta fonction retourne l'adresse d'une chaîne, espace alloué dynamiquement donc contraint d'être libéré avec free et en cas d'erreur, elle retournerais simplement NULL !



    Voilà, je pense que c'est surtout cette modification que j'apporterais à ta fonction mais là encore, les avis peuvent diverger

  3. #3
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Bonsoir,

    Merci beaucoup des conseils

    En générale, on utilise les constantes EXIT_SUCCESS et EXIT_FAILURE pour les fonctions de fermeture du programme genre return ou exit
    D'accord, je ne savais pas.

    Si tu permet à ta fonction de déterminer la taille de la chaîne a récupérer, je n'aurais mis que cet argument. J'aurais en effet permis à la fonction de renvoyer une addresse allouée dynamiquement contenant la chaîne.

    L'effet, est que tu pourras te débarasser d'un buffer de 300 char statiques ce qui n'est pas rien et de pouvoir allouer une zone de la taille de la chaîne (en comptant bien sûr un espace en plus pour le zéro de fin de chaîne). En somme, ta fonction retourne l'adresse d'une chaîne, espace alloué dynamiquement donc contraint d'être libéré avec free et en cas d'erreur, elle retournerais simplement NULL
    Super ! En effet c'est une méthode plus intelligente.

    Et en ce qui concerne le stdin à "purger" comme je l'ai remarqué dans beaucoup de discussions, est-ce nécessaire ici ? Si oui, pourquoi ? Si non, pourquoi ?

  4. #4
    Rédacteur
    Avatar de Franck.H
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Janvier 2004
    Messages
    6 951
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : Service public

    Informations forums :
    Inscription : Janvier 2004
    Messages : 6 951
    Points : 12 462
    Points
    12 462
    Par défaut
    Citation Envoyé par odsen.s
    Et en ce qui concerne le stdin à "purger" comme je l'ai remarqué dans beaucoup de discussions, est-ce nécessaire ici ? Si oui, pourquoi ? Si non, pourquoi ?
    Tout dépend, ici pour ton exercice ce n'est pas vraiment nécessaire si tu te contente de récupérer les caractères écrits au clavier mais dans beaucoup de cas lors de traitement de saisies utilisateurs c'est plus que recommandable !

    Pourquoi ? Bin tout simplement pour éviter des caractères parasites qui sont en fait des caractères non lus (donc non extrait de stdin) donc toujours présent dans le tampon mais qui se mélangerons avec la prochaine saisie au clavier !

    Attention toutefois à ne pas purger stdin avec la fonction fflush mais utiliser plutôt cette technique: Comment vider le buffer clavier ?

  5. #5
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    D'accord, je comprends.
    Merci pour toutes ces infos, c'était très clair.
    Je pense que je vais pouvoir me débrouiller maintenant.

    Bonne soirée.

  6. #6
    Expert éminent sénior
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Points : 20 985
    Points
    20 985
    Par défaut
    Citation Envoyé par odsen.s
    j'ai essayé de créer une fonction lisant les caractères saisis aux claviers et les copiant dans une chaine prévue à cet effet.
    Bonne idée, ça peut résoudre pas mal de problèmes des fonctions existantes...
    Elle comprend deux paramètres : la chaîne cible (où on va écrire) et sa taille (pour éviter de déborder).
    Ca me va. Tu devrais aussi préciser si il y a un type retour et, éventuellement, la signification des valeurs retournées.
    Le but de la fonction est uniquement de saisir une chaîne de caractères.
    OK.
    Pourriez-vous m'indiquer si la fonction que j'ai créée a des défauts ?
    Si oui, lesquels ?
    Ton code commenté et partiellement corrigé (non testé)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // lire_chaine.c
     
    #include <stdlib.h>
    #include <stdio.h>
    /* -ed- header inconnu.
    #include "lire_chaine.h"
    */
    /* -ed- ajoute */
    #include <string.h>
     
    /*
    lireChaine
    Lit une chaine de caractères.
    Retourne EXIT_SUCCESS si tout se passe bien
    Retourne EXIT_FAILURE si la taille de la chaine saisie est supérieure
    à celle de la chaine cible.
     
    Paramètre 1 : la chaine cible
    Paramètre 2 : la taille de cette chaine
    */
     
    int lireChaine (char *chaine, long taille)
    {
       char buffer[300] = { 0 };    // Le buffer qui va recevoir la chaine tappée
       char caractereLu = 0;        // La variable qui reçoit les caractères un a un
       long i = 0;                  // Pour les boucles
     
       /* Tant qu'on a pas entré de retour à la ligne et tant qu'on ne dépasse
          pas la taille de la chaine dans laquelle on veut écrire, on continue
          de recevoir les caractères entrants  */
     
     
       /* -ed- beaucoup de problemes ici
     
          i risque d'atteindre taille, ce qui peut provoquer un debordement.
          la lecture de stdin s'arrette quand la destination est pleine.
          Cela signifie que les caracteres pendants ne sont pas lus. Qui va les
          lire ? Le prochaine appel de fgetc() sera non blocant...
          On retombe dans les travers bien connu des fonctions de saisies...
     
          Constatant que le tableau est plein et que le dernier caractere lu n'est
          pas '\n', il faudrait envisager une purge des carcteres restants, soit ici,
          soit par l'appelant.
     
          Enfin, que se passe-t-il su taille est > sizeof buffer ?
     
          Gros probleme de conception. En fait, ce tableau intermediaire
          (qui oblige a faire une copie : strcpy()) est inutile et dangereux.
     
        */
       while ((caractereLu = getchar ()) != '\n' && i <= taille)
       {
          buffer[i] = caractereLu;
          i++;
       }
     
       /* Si la taille de la chaine dans laquelle on veut écrire a été dépassée,
          on écrit une erreur et on retourne un échec */
       if (i - 1 >= taille)
       {
          fprintf (stderr,
                   "Erreur : la taille de la chaine saisie depasse celle de la chaine cible.\n");
          return EXIT_FAILURE;
       }
     
       /* Si on a pas dépassé la taille de la chaine dans laquelle on veut écrire,
          on ajoute le caractere NULL au buffer et on copie vers la chaine */
       buffer[i] = '\0';
     
       /* -ed- pas de prototype */
       strcpy (chaine, buffer);
     
       /* Tout s'est bien passé, on retourne EXIT_SUCCESS */
       return EXIT_SUCCESS;
    }
     
     
    #ifdef TEST
    #include <stdio.h>
    #include <stdlib.h>
     
    int main (int argc, char *argv[])
    {
       char chaine[20] = { 0 };
     
       printf ("Entrez une chaine : ");
       fflush (stdout);
     
    /* -ed-
        if (lireChaine(chaine, 20) == EXIT_SUCCESS)
     
    toujours preferer le code 'auto-demerdant' */
       if (lireChaine (chaine, sizeof chaine) == EXIT_SUCCESS)
       {
          printf ("La chaine est : \"%s\" et contient %d caracteres", chaine,
                  strlen (chaine));
       }
     
       return EXIT_SUCCESS;
    }
    #endif
    Comment pourrais-je l'améliorer ?
    En suprimmant le tableau intermédiaire. Tu passes l'adresse et la taille, c'est suffisant. Tu fais la saisie directement dans la chaine fournie.

  7. #7
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Bonjour,

    Merci de la réponse
    Voici le code modifié en fonction des remarques :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /*
    lireChaine
    Lit une chaine de caractères.
    Retourne 1 si tout se passe bien
    Retourne 0 si la taille de la chaine saisie est supérieure
    à celle de la chaine cible.
     
    Paramètre 1 : la chaine cible
    Paramètre 2 : la taille de cette chaine
    */
     
    int lireChaine(char* chaine, long taille)
    {
        // Création du buffer en fonction de la taille
        char* buffer = NULL;
        buffer = malloc (taille * sizeof(char));
     
     
        char caractereLu = 0; // La variable qui reçoit les caractères un a un
        long i = 0; // Pour les boucles
     
        /* Tant qu'on a pas entré de retour à la ligne et tant qu'on ne dépasse
        pas la taille de la chaine dans laquelle on veut écrire, on continue
        de recevoir les caractères entrants  */
        while ((caractereLu = getchar()) != '\n' && i <= taille)
        {
            buffer[i] = caractereLu;
            i++;
        }
     
        /* Si la taille de la chaine dans laquelle on veut écrire a été dépassée,
        on écrit une erreur et on retourne un échec */
        if (i-1 >= taille)
        {
            fprintf(stderr, "Erreur : la taille de la chaine saisie depasse celle de la chaine cible.\n");
     
            /* On vide le stdin */
            int c;
            while ((c = getchar()) != '\n' && c != EOF);
            return 0;
        }
     
        /* Si on a pas dépassé la taille de la chaine dans laquelle on veut écrire,
        on ajoute le caractere NULL au buffer et on copie vers la chaine */
        buffer[i] = '\0';
        strcpy(chaine, buffer);
     
        /* On libère l'espace alloué */
        free(buffer);
     
        /* Tout s'est bien passé, on retourne 1 */
        return 1;
    }
    La seule chose que je n'ai pas modifié : l'utilisation d'un tableau intermédiaire.
    Seulement cette fois-ci il créé dynamiquement avec malloc pour résoudre le problème de "beaucoup tros grand, et parfois trop petit".

    J'aurais bien aimé m'en passer (et comme tu me le dis, écrire directement dans la chaine cible) mais comment faire tout en ne dépassant pas ?
    Mon but était de tester le dépassement de taille avant d'écrire dans le tableau final. "Si ça dépasse, on écrit pas". D'où mon tableau intermédiaire.

    Chose à part : si je veux par la suite convertir cette chaîne en un entier ou flottant, vaut-il mieux utiliser sscanf(), ou strtol() et strtod() ? Quels sont les défauts et les avantages ?

    Bref, je suis ouvert à tout conseil
    Encore merci.

  8. #8
    Expert éminent sénior
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Points : 20 985
    Points
    20 985
    Par défaut
    Citation Envoyé par odsen.s
    Voici le code modifié en fonction des remarques :

    La seule chose que je n'ai pas modifié : l'utilisation d'un tableau intermédiaire.
    Seulement cette fois-ci il créé dynamiquement avec malloc pour résoudre le problème de "beaucoup tros grand, et parfois trop petit".

    J'aurais bien aimé m'en passer (et comme tu me le dis, écrire directement dans la chaine cible) mais comment faire tout en ne dépassant pas ?
    Mon but était de tester le dépassement de taille avant d'écrire dans le tableau final. "Si ça dépasse, on écrit pas". D'où mon tableau intermédiaire.
    C'est un peu mieux, mais c'est encore assez compliqué et il y a toujours un risque de débordement...

    - Je confirme que tu n'as absolument pas besoin de tableau intermédiaire (de plus, la gestion mémoire est erronée : taille insuffisante, pas de libération an cas d'erreur...)
    - Il suffit de compter les char lus et de s'arréter suffisament tôt pour ne pas saturer le tableau de destination et garder une place pour le 0 final...

    A ce moment là, on se pose la question suivante : Ai-je lu le '\n' ?
    - Si oui, c'est qu'il n'y a plus rien à lire, la ligne est complete, on retourne 1.
    - Si non, on purge les caractères non lus et on retourne 0.

    Ecris d'abord l'algorithme en pseudo-code avant de coder. Tu verras qu'on peut simplifier bien des choses...

    Ca tient en quelques lignes.

    Chose à part : si je veux par la suite convertir cette chaîne en un entier ou flottant, vaut-il mieux utiliser sscanf(), ou strtol() et strtod() ? Quels sont les défauts et les avantages ?
    sscanf() est pratique pour convertir plusieurs valeurs d'un coup. De plus, on a un compte rendu qui permet de savoir si la ou les conversions ont réussi.
    Plutôt adapté aux formats d'entrée fixes.

    strto*() permet la conversion avec une détéction de dépassement (errno/ERANGE). De plus, le 2ème paramètre permet un contrôle de saisie plus fin et l'analyse d'une ligne contenant un nombre indéterminé de valeurs (avec une boucle).

    Bref tout dépend de l'usage.

  9. #9
    Membre éclairé Avatar de homeostasie
    Homme Profil pro
    Inscrit en
    Mai 2005
    Messages
    939
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 939
    Points : 862
    Points
    862
    Par défaut
    Citation Envoyé par odsen.s
    La seule chose que je n'ai pas modifié : l'utilisation d'un tableau intermédiaire.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int lireChaine(char* chaine, long taille)
    {
      // Création du buffer en fonction de la taille
        char* buffer = NULL;
        buffer = malloc (taille * sizeof(char)); 
     
        ...
    }
    Au lieu d'utiliser et d'allouer dynamiquement "buffer", autant utiliser le tableau chaine et le paramètre taille comme t'a fait part Emmanuel. En effet, passer en paramètre un pointeur sur un tableau est l'un des grands avantages de l'utilisation des pointeurs (Fonctions avec transmission de paramètres par adresse). Tu passes l'adresse du début de ton tableau, ca te permet donc l'utiliser directement et d'éviter le tableau intermédiaire.

    De plus:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    char* buffer = NULL;
        buffer = malloc (taille * sizeof(char));
    Une petite astuce:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    char* buffer = NULL;
        buffer = malloc (taille * sizeof(*buffer));
    Si tu modifies le type de buffer, alors la taille de l'allocation dynamique se fera par la même occasion.

    Dans ton cas tu peux aussi l'écrire en une ligne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    char* buffer = malloc (taille * sizeof(*buffer));
    Tu pourrais aussi tester le retour de malloc pour savoir si l'allocation a bien réussi.

    Citation Envoyé par odsen.s
    Chose à part : si je veux par la suite convertir cette chaîne en un entier ou flottant, vaut-il mieux utiliser sscanf(), ou strtol() et strtod()
    Tu as aussi strtof() pour les flottants et strtoul() pour le entiers non signés!

  10. #10
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Bonjour,

    Merci à tous pour vos conseils.

    C'est un peu mieux, mais c'est encore assez compliqué et il y a toujours un risque de débordement...
    .........

    Ecris d'abord l'algorithme en pseudo-code avant de coder. Tu verras qu'on peut simplifier bien des choses...

    Ca tient en quelques lignes.
    J'ai fait quelques essais selon ta méthode Emmanuel, mais je n'aboutis à rien de correct, la fonction ne remplit pas son rôle. Je m'y prends mal.

    J'ai donc essayé de poursuivre le code que j'avais déja commencé, en écrivant directement dans la chaine cible (on enlève le buffer pour de bon).
    Ca donne ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    int lireChaine(char* chaine, long taille)
    {
        char caractereLu = 0; // La variable qui reçoit les caractères un a un
        long i = 0; // Pour les boucles
     
        /* Tant qu'on a pas entré de retour à la ligne et tant qu'on ne dépasse
        pas la taille de la chaine dans laquelle on veut écrire, on continue
        de recevoir les caractères entrants  */
        while ((caractereLu = getchar()) != '\n' && i <= taille)
        {
            chaine[i] = caractereLu;
            i++;
        }
     
        /* Si la taille de la chaine dans laquelle on veut écrire a été dépassée,
        on écrit une erreur et on retourne un échec */
        if (i-1 >= taille)
        {
            fprintf(stderr, "Erreur : la taille de la chaine saisie depasse celle de la chaine cible.\n");
     
            /* On vide le stdin */
            while ((caractereLu = getchar()) != '\n' && caractereLu != EOF);
            return 0;
        }
     
        /* Si on a pas dépassé la taille de la chaine dans laquelle on veut écrire,
        on ajoute le caractere NULL à la fin */
        chaine[i] = '\0';
     
        /* Tout s'est bien passé, on retourne 1 */
        return 1;
    }
    Bien sûr, j'aimerais essayer de coder une fonction qui ressemble plus à ce que tu me conseille de faire (car dans mon code je pense qu'il y a encore risque de débordement), mais je ne comprends pas bien le raisonnement suivant :

    - Il suffit de compter les char lus et de s'arréter suffisament tôt pour ne pas saturer le tableau de destination et garder une place pour le 0 final...

    A ce moment là, on se pose la question suivante : Ai-je lu le '\n' ?
    - Si oui, c'est qu'il n'y a plus rien à lire, la ligne est complete, on retourne 1.
    - Si non, on purge les caractères non lus et on retourne 0.
    Si on lit les caractères lus sans saturer, je me pose une question : la personne qui remplit la chaîne au clavier devra appuyer sur "entrée" jusqu'à ce qu'elle remplisse le tableau de destination ? Ou faut-il arrêter la boucle dès que le retour à la ligne est lu ?

    Bref, quelques explications me feraient beaucoup de bien.
    Je dois sans doute mal comprendre le fonctionnement au niveau de getchar() et sdtin.

    Merci.

  11. #11
    Expert éminent sénior
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Points : 20 985
    Points
    20 985
    Par défaut
    Citation Envoyé par odsen.s
    Je dois sans doute mal comprendre le fonctionnement au niveau de getchar() et sdtin.
    Je pense que tout est là :

    http://emmanuel-delahaye.developpez.com/notes.htm#fgetc

  12. #12
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    633
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2004
    Messages : 633
    Points : 711
    Points
    711
    Par défaut
    Bonjour,
    Citation Envoyé par odsen.s
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    int lireChaine(char* chaine, long taille)
    {
        char caractereLu = 0; // La variable qui reçoit les caractères un a un
        long i = 0; // Pour les boucles
    
        /* Tant qu'on a pas entré de retour à la ligne et tant qu'on ne dépasse
        pas la taille de la chaine dans laquelle on veut écrire, on continue
        de recevoir les caractères entrants  */
        while ((caractereLu = getchar()) != '\n' && i <= taille)
        {
            chaine[i] = caractereLu;
            i++;
        }
    
        /* Si la taille de la chaine dans laquelle on veut écrire a été dépassée,
        on écrit une erreur et on retourne un échec */
        if (i-1 >= taille)
        {
            fprintf(stderr, "Erreur : la taille de la chaine saisie depasse celle de la chaine cible.\n");
    
            /* On vide le stdin */
            while ((caractereLu = getchar()) != '\n' && caractereLu != EOF);
            return 0;
        }
    
        /* Si on a pas dépassé la taille de la chaine dans laquelle on veut écrire,
        on ajoute le caractere NULL à la fin */
        chaine[i] = '\0';
    
        /* Tout s'est bien passé, on retourne 1 */
        return 1;
    }
    Il y a un problème de longueur maximale:
    - une chaîne de longueur réservée taille ne peut être indicée que jusqu'à taille - 1, et compte tenu du 0 (zéro) final nécessaire pour un comportement sain, ne peut contenir que taille - 2 caractères avant ce 0 final.

    - telle qu'elle est, ta boucle de lecture conduit tout droit à un "access violation" si tu lis jusqu'à taille caractères, car
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            chaine[i] = caractereLu;
    avec i = taille déborde de la mémoire réservée pour la chaîne.

    Il faut donc ajuster ta boucle de lecture, ce qui aura de plus le mérite de t'éviter le test sur la taille une fois sorti de la boucle.
    - Tu as bien vu que tu as des risques de débordement, puisque tu es obligé de faire ce test

  13. #13
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Bonsoir,

    J'ai été lire les explications et pense avoir bien compris. Je n'arrive cependant toujours pas à comprendre comment créer un programme sur cette base :

    - Il suffit de compter les char lus et de s'arréter suffisament tôt pour ne pas saturer le tableau de destination et garder une place pour le 0 final...

    A ce moment là, on se pose la question suivante : Ai-je lu le '\n' ?
    - Si oui, c'est qu'il n'y a plus rien à lire, la ligne est complete, on retourne 1.
    - Si non, on purge les caractères non lus et on retourne 0.
    Peux-tu me donner la structure du programme de manière plus détaillée ?
    Un pseudo code m'aiderait vraiment, je n'arrive pas à construire le mien car je ne saisis pas bien le raisonnement cité.

    Il y a un problème de longueur maximale:
    - une chaîne de longueur réservée taille ne peut être indicée que jusqu'à taille - 1, et compte tenu du 0 (zéro) final nécessaire pour un comportement sain, ne peut contenir que taille - 2 caractères avant ce 0 final.
    Ok, merci

  14. #14
    Expert éminent sénior
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Points : 20 985
    Points
    20 985
    Par défaut
    Citation Envoyé par odsen.s
    Peux-tu me donner la structure du programme de manière plus détaillée ?
    Un pseudo code m'aiderait vraiment, je n'arrive pas à construire le mien car je ne saisis pas bien le raisonnement cité.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
    BEGIN
     i <- 0
     terminated <- 0
     DO
    ; - Il suffit de compter les char lus et de s'arréter suffisament tôt pour ne pas 
    ; saturer le tableau de destination et garder une place pour le 0 final...
      c <- getchar()
      IF i < SIZE(tab) - 1
       tab[i] <- c
       INC i
       IF c = EOL
        terminated <- 1
       END IF
      ELSE
       BREAK
      END IF 
     WHILE NOT terminated
     
    ; A ce moment là, on se pose la question suivante : Ai-je lu le '\n' ?
     IF terminated
    ; - Si oui, c'est qu'il n'y a plus rien à lire, la ligne est complete, on retourne 1.
       return <- 1
     ELSE
    ; - Si non, on purge les caractères non lus et on retourne 0.
      DO
        c <- getchar()
      WHILE c <> EOL
      return <- 0
     END IF
     RETURN return
    END
    En codant ça, je me suis aperçu qu'on pouvait faire plus simple (la purge à la volée...)

  15. #15
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Bonsoir,

    Merci pour ton code Emmanuel, il m'a permis de mieux voir la situation.
    En le recopiant en C, j'ai eu quelques soucis (la fonction ne fonctionnait pas exactement comme prévue).
    Mais je me suis inspiré du code et ai créé cette nouvelle fonction :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    int lireChaine(char* chaine, long taille)
    {
        long i = 0; // Pour les boucles
        char caractereLu = 0;
     
        // Boucle infinie, à revoir si problèmes
        while (1)
        {
            // On lit le caractère le plus ancien de stdin
            caractereLu = getchar();
     
            /* Si le caractère lu est un retour à la ligne, on
            considère que la ligne est complètement lue. On ajoute alors
            le caractère NULL à la chaîne et on renvoie 1*/
            if(caractereLu == '\n')
            {
                chaine[i] = '\0';
                return 1;
            }
     
            /* Si le caractère n'est pas le retour à la ligne, on vérifie que son
            ajout est possible (pas de débordement) et on l'ajoute à la suite de
            la chaîne cible */
            else if (i < taille -1)
            {
                chaine[i] = caractereLu;
                i++;
            }
     
            /* Sinon, c'est que l'on déborde, on n'ajoute pas, on purge les
            caractères de stdin et on retourne 0*/
            else
            {
                while (getchar() != '\n');
                return 0;
            }
        }
    }
    D'après mes essais, ça fonctionne à merveille
    Le dépassement à l'air bien géré.

    J'attends vos commentaires par rapport à cette fonction.
    Ce qui me chiffone : est-ce correct en C d'utiliser une boucle "infinie" ("tant que 1 est vrai"), n'y a-t'il pas de risques à procéder de cette manière ?
    La fonction présente-t-elle d'autres défauts ?

    Merci.

  16. #16
    Expert éminent sénior
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Points : 20 985
    Points
    20 985
    Par défaut
    Citation Envoyé par odsen.s
    Mais je me suis inspiré du code et ai créé cette nouvelle fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    int lireChaine(char* chaine, long taille)
    {
        long i = 0; // Pour les boucles
        char caractereLu = 0;
     
        // Boucle infinie, à revoir si problèmes
        while (1)
        {
            // On lit le caractère le plus ancien de stdin
            caractereLu = getchar();
     
            /* Si le caractère lu est un retour à la ligne, on
            considère que la ligne est complètement lue. On ajoute alors
            le caractère NULL à la chaîne et on renvoie 1*/
            if(caractereLu == '\n')
            {
                chaine[i] = '\0';
                return 1;
            }
     
            /* Si le caractère n'est pas le retour à la ligne, on vérifie que son
            ajout est possible (pas de débordement) et on l'ajoute à la suite de
            la chaîne cible */
            else if (i < taille -1)
            {
                chaine[i] = caractereLu;
                i++;
            }
     
            /* Sinon, c'est que l'on déborde, on n'ajoute pas, on purge les
            caractères de stdin et on retourne 0*/
            else
            {
                while (getchar() != '\n');
                return 0;
            }
        }
    }
    D'après mes essais, ça fonctionne à merveille
    Le dépassement à l'air bien géré.

    J'attends vos commentaires par rapport à cette fonction.
    Ce qui me chiffone : est-ce correct en C d'utiliser une boucle "infinie" ("tant que 1 est vrai"), n'y a-t'il pas de risques à procéder de cette manière ?
    La fonction présente-t-elle d'autres défauts ?
    A vu de nez, ça parait correct et dans l'esprit de ce j'avais senti en écrivant l'algo, à savoir 'purger à la volée', c'est à dire dans une seule et même boucle. La boucle infinie est tout à fait correcte (du moment qu'on en sort, mais j'aurais préféré des break et un return unique. Détail).

    La façon 'geek' de la coder est for(;, plutôt que while(1), ça use moins le clavier...

    Quelques points de détails quand même :

    - Plutôt que long, le bon type pour les tailles, et donc les indices, est size_t.

    http://emmanuel-delahaye.developpez....tes.htm#size_t

    - Le bon type pour le caractère lu n'est pas char mais int. Sinon, on ne peut detecter EOF (important si l'entrée est redirigée depuis un fichier).
    - Il serait sage de s'assurer que la taille est supérieure a 0... (et que chaine est <> NULL) avant d'écrire quoique ce soit dans la zone pointée par chaine...
    - J'avais pa vu la 2 ème boucle... OK, c'est une façon de faire simple et correcte.
    - Dans les deux cas de lecture, il faut penser à tester aussi EOF

    J'ai fait un test unitaire. J'ai trouvé un petit bug. Corrigé.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    #include <stdio.h>
    #define TEST_DETAIL 0
     
    static void purge (FILE *fp)
    {
       int caractereLu;
       while ((caractereLu = fgetc (fp)) != '\n' && caractereLu != EOF)
       {
    #if TEST_DETAIL
       printf ("DUT.purge : c = %d\n", caractereLu);
    #endif
       }
    }
     
    int lireLigne (FILE *fp, char *chaine, size_t taille)
    {
       int ret = EOF;
       size_t i = 0;                // Pour les boucles
       int caractereLu;             // Boucle infinie, à revoir si problèmes
       for (;;)
       {
          // On lit le caractère le plus ancien de stdin
          caractereLu = fgetc (fp);
          /* Si le caractère lu est un retour à la ligne,
             on considère que la ligne est complètement lue.
             On ajoute alors le caractère NULL à la chaîne et on renvoie 1
           */
    #if TEST_DETAIL
       printf ("DUT.fgetc : c = %d\n", caractereLu);
    #endif
          if (caractereLu == '\n' || caractereLu == EOF)
          {
             if (taille > 0)
             {
                chaine[i] = '\0';
     
                if (caractereLu != EOF)
                {
                   ret = 1;
                }
                break;
             }
             else
             {
                purge (fp);
                ret = 0;
                break;
             }
          }
          /* Si le caractère n'est pas le retour à la ligne,
             on vérifie que son ajout est possible (pas de débordement) e
             t on l'ajoute à la suite de la chaîne cible
           */
          else if (taille > 0 && i < taille - 1)
          {
             chaine[i] = caractereLu;
             i++;
          }
          /* Sinon, c'est que l'on déborde, on n'ajoute pas,
             on purge les caractères de stdin et on retourne 0
           */
          else
          {
             /* manquait */
             chaine[i] = '\0';
     
             purge (fp);
             ret = 0;
             break;
          }
       }
     
    #if TEST_DETAIL
       printf ("DUT : chaine = '%s' ret=%d\n", chaine, ret);
    #endif
     
       return ret;
    }
     
    #ifdef TEST
    #include <string.h>
     
    #define BREAK 1
     
    int main (void)
    {
    #define FIC "lirechaine.txt"
    FILE *fp = fopen (FIC, "r");
       if (fp != NULL)
       {
          static struct test
          {
             int t;
             size_t sz;
             char const *s;
             int ret;
          }
          const a[] = {
                         /* *INDENT-OFF* */
                       /* test
                          |   size
                          |   |  text
                          |   |  |      ret */
                         {10, 0, NULL , 0}, /* "\n" */
                         {11, 0, NULL , 0}, /* "a\n" */
                         {20, 1, ""   , 0},
                         {21, 2, "a"  , 0},
                         {22, 3, "ab" , 0},
                         {30, 4, "abc", 1},
                         {31, 5, "abc", 1},
                         {32, 6, "abc", 1},
                         {33, 7, "abc", 1},
                         /* *INDENT-ON* */
          };
          size_t i;
          for (i = 0; i < sizeof a / sizeof *a; i++)
          {
             struct test const *const p = a + i;
     
             /* D.U.T */
             char line[4] = "???";
             int ret = lireLigne (fp, line, p->sz);
     
             if (ret == EOF)
             {
                break;
             }
     
             if (ret != p->ret)
             {
                printf ("ERR at test %d : ret = %d (expected %d)\n", p->t, ret,
                        p->ret);
    #if BREAK
                break;
    #endif
             }
     
             if (p->sz > 0 && strcmp (line, p->s) != 0)
             {
                printf ("ERR at test %d : line = '%s' (expected '%s')\n", p->t,
                        line, p->s);
    #if BREAK
                break;
    #endif
             }
          }
     
          if (i == sizeof a / sizeof *a)
          {
             puts ("PASSED");
          }
     
          fclose (fp);
       }
       else
       {
          perror (FIC);
       }
       return 0;
    }
    #endif
    avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    abc
     
    abc
    abc
    abc
    abc
    abc
    abc
    abc
    abc

  17. #17
    Membre actif
    Avatar de odsen.s
    Profil pro
    Étudiant
    Inscrit en
    Octobre 2006
    Messages
    269
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2006
    Messages : 269
    Points : 243
    Points
    243
    Par défaut
    Quelques modifications, grâce à tes commentaires :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
     
    /*
    Retourne 1 si tout se passe bien
    Retourne 2 si la taille de la chaine saisie est supérieure à celle de la chaine cible.
    Retourne 3 si chaine ne pointe sur rien
    Retourne 4 si la taille de la chaine n'est pas > 0*/
    int lireChaine(char* chaine, size_t taille)
    {
        long i = 0; // Pour les boucles
        int caractereLu = 0;
     
        // Si le pointeur ne contient aucune adresse, on retourne une erreur    
        if (chaine == NULL)
            return 3;
     
        // Si la taille n'est pas supérieure à zéro, on retourne une erreur
        if (taille <= 0)
            return 4;         
     
        // Boucle infinie
        while (1)
        {
            // On lit le caractère le plus ancien de stdin
            caractereLu = getchar();
     
            /* Si le caractère lu est un retour à la ligne, on
            considère que la ligne est complètement lue. On ajoute alors
            le caractère NULL à la chaîne et on renvoie 1*/
            if(caractereLu == '\n')
            {
                chaine[i] = '\0';
                return 1;
            }
     
            /* Si le caractère n'est pas le retour à la ligne, on vérifie que son
            ajout est possible (pas de débordement) et on l'ajoute à la suite de
            la chaîne cible */
            else if (i < taille -1)
            {
                chaine[i] = caractereLu;
                i++;
            }
     
            /* Sinon, c'est que l'on déborde, on n'ajoute pas, on purge les
            caractères de stdin et on retourne 0*/
            else
            {
                while (caractereLu = getchar() != '\n' && caractereLu != EOF);
                return 2;
            }
        }
    }
    Le code tient toujours la route ?
    La façon dont je renvoie les erreurs peut se faire de manière plus intelligente, non ?

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 21/03/2011, 12h54
  2. Création d'une interface de saisie assistée
    Par ppipof2000 dans le forum WinDev
    Réponses: 9
    Dernier message: 16/01/2007, 12h13
  3. Problème pour la création d'une fonction
    Par jipé95 dans le forum C
    Réponses: 5
    Dernier message: 10/12/2006, 15h28
  4. Création d'une fonction sans paramètre?
    Par falcon dans le forum Oracle
    Réponses: 3
    Dernier message: 13/12/2004, 12h32
  5. Réponses: 14
    Dernier message: 09/04/2004, 14h44

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo