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 :

Les chaines de caractères en C [Tutoriel]


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Rédacteur

    Avatar de gege2061
    Femme Profil pro
    Administrateur de base de données
    Inscrit en
    Juin 2004
    Messages
    5 840
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur de base de données

    Informations forums :
    Inscription : Juin 2004
    Messages : 5 840
    Par défaut Les chaines de caractères en C
    http://nicolasj.developpez.com/articles/libc/string/

    Après un rappel sur la structure des chaines de caractères en C et un aperçu des fonctions de la bibliothèque standard servant à manipuler ces chaines, je vous propose de créer notre propre bibliothèque de manipulation des chaines de caractères.
    Vous pouvez laisser un commentaire sur cet article à la suite.

  2. #2
    Expert éminent
    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
    Par défaut
    Citation Envoyé par gege2061 Voir le message
    Les chaînes de caractères sont en fait stockées dans un tableau de caractères
    Il n'y a pas de 'caractères'. C'est un tableau d'entiers de type char.

    "Les chaînes de caractères sont en fait stockées dans un tableau de char"

    de code ASCII
    Il n'y a pas de 'code ASCII ' en C. une chaine est terminée par un caractère valant 0 (ou '\0' en octal ou '\x0' en hexadécimal, si on aime la complication).

    Le fait qu'il ait la même valeur que le NUL du charset ASCII est un détail d'implémentation qui ne nous intéresse pas dans une théorie générale sur le langage C.

    \0 n'existe pas en C. C'est '\0'.

    toute modification de la chaîne s3 se conclura par une erreur de segmentation
    Non. Ça se traduira par un comportement indéterminé.

    Ca fait déjà beaucoup d'imprécisions... J'ai pas trop envie de continuer...

    Je propose :

    "<...> dont la fin est marquée par un caractère nul, de valeur 0 et représenté par le caractère '\0' ou '\x0' ou la valeur 0 directement.

    Nota : le chiffre 0 (zéro) est le caractère '0'. Sa valeur dépend du charset utilisé (en ASCII, 48 décimal)"

  3. #3
    Rédacteur

    Avatar de gege2061
    Femme Profil pro
    Administrateur de base de données
    Inscrit en
    Juin 2004
    Messages
    5 840
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur de base de données

    Informations forums :
    Inscription : Juin 2004
    Messages : 5 840
    Par défaut
    Corrigé selon tes conseils.


  4. #4
    Expert éminent
    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
    Par défaut
    Citation Envoyé par gege2061 Voir le message
    Corrigé selon tes conseils.

    OK.
    II-N. strtok
    Tu parles de 'ct', mais le paramètre est 't'...

    III-A. Modifier la case d'une chaîne de caractère
    ces fonctions retournant un espace alloué, j'aurais mis un suffixe _dyn ...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    char *str_tolower_dyn (char const *s);
    char *str_toupper_dyn (char const *s)
    ;

    ou un rappel à strdup() ...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    char *str_dup_tolower (char const *s);
    char *str_dup_toupper (char const *s);
    avec un rappel à 'free()'...

    j'aurais aussi prévu le cas plus simple de la chaine modifiable :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    char *str_tolower (char *s);
    char *str_toupper (char *s);

  5. #5
    Membre émérite
    Avatar de ol9245
    Homme Profil pro
    Chercheur
    Inscrit en
    Avril 2007
    Messages
    985
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 63
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Avril 2007
    Messages : 985
    Billets dans le blog
    1
    Par défaut des mallocs à l'intérieur d'une fonction : est-ce bien raisonnable ?
    Bonjour,

    Je passais par ici par hasard. Je vois bien que l'exemple date un peu...
    Une remarque sur les exemples de code donnés, comme par exemple celui-ci :
    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
    char *str_tolower (const char *ct)
    {
       char *s = NULL;
    
       if (ct != NULL)
       {
          int i;
          s = malloc (sizeof (*s) * (strlen (ct) + 1));
          if (s != NULL)
          {
             for (i = 0; ct[i]; i++)
             {
                s[i] = tolower (ct[i]);
             }
             s[i] = '\0';
          }
       }
       return s;
    }
    Je crois que c'est une mauvaise idée de montrer à des débutants des exemples de fonctions structurées comme celle-ci. Une fonction est responsable de l'utilisation qu'elle fait de la mémoire. Donc si une fonction alloue de la mémooire avec un malloc, elle doit la libérer avec un free abant de sortir.

    En conséquence, une fonction comme celle-ci ne peut pas renvoyer son résultat sous forme d'une chaine de caractère qu'elle a allouée sur le tas. C'est une mauvaise pratique. La responsabilité d'allouer de la mémoire revient à l'appellant de la fonction, pas à la fonction elle-même.

    La solution la plus simple est de faire la modification sur la chaine en place. Sil l'appelant veut garder l'original, libre à lui de faire une copie avant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void to_upper(char *c)
    {
    	for (; *c; ++c)
    		if (*c >= 'a' && *c <= 'z')
    			*c += 'A' - 'a';
    }
    Une autre solution, pas très satisfaisante, est de prendre deux arguments, l'un en entrée, l'autre en sortie. Un peu moche.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void to_upper(const char *in, char *out)
    {
    	for (; *in; ++in, ++out)
    		*out = *in + (*in >= 'a' && *in <= 'z' ? 'A' - 'a' : 0);
    }
    Voilà. C'est juste mon grain de sel...

  6. #6
    Expert éminent
    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
    Par défaut
    Citation Envoyé par ol9245 Voir le message
    Bonjour,

    Je passais par ici par hasard. Je vois bien que l'exemple date un peu...
    Une remarque sur les exemples de code donnés, comme par exemple celui-ci :
    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
    char *str_tolower (const char *ct)
    {
       char *s = NULL;
    
       if (ct != NULL)
       {
          int i;
          s = malloc (sizeof (*s) * (strlen (ct) + 1));
          if (s != NULL)
          {
             for (i = 0; ct[i]; i++)
             {
                s[i] = tolower (ct[i]);
             }
             s[i] = '\0';
          }
       }
       return s;
    }
    Je crois que c'est une mauvaise idée de montrer à des débutants des exemples de fonctions structurées comme celle-ci. Une fonction est responsable de l'utilisation qu'elle fait de la mémoire.
    Comme malloc() ? Ou fopen () ?

    J'ai bien insisté que le fait que la fonction allouait un bloc et qu'il fallait le libérer après usage. C'est la seule façon de faire des copies de chaines réentrantes, ce qui permet le multi-tâche ...

    Donc si une fonction alloue de la mémoire avec un malloc, elle doit la libérer avec un free avant de sortir.
    Dans ce cas, une variable locale devrait suffire ...

    En conséquence, une fonction comme celle-ci ne peut pas renvoyer son résultat sous forme d'une chaine de caractère qu'elle a allouée sur le tas. C'est une mauvaise pratique. La responsabilité d'allouer de la mémoire revient à l’appelant de la fonction, pas à la fonction elle-même.
    Voilà une position bien dogmatique. Il faudra en parler à celui qui a écrit fopen() ...

    Dans la pratique évidemment qu'on passe son temps à allouer ici et à libérer là. Certes ça demande quelques efforts de programmation et des outils de vérification (j'ai même fini par en écrire un, voir ma bibliothèque CLIB), mais c'est le seul moyen d'être efficace et de ne pas passer son temps à recopier des données qui peuvent parfois être longues ...

    Même si tu utilises un système plus symétrique qui ressemble à de la programmation objet (ADT), il y a quand même un fonction de création de l'objet et une fonction de destruction de celui-ci. Le fait que l'on contrôle manuellement la destruction permet de créer des objet dynamiques persistants (le temps de leur utilisation et dans n'importe quel tâche).

    Sur ce, moi je suis musicien, maintenant, alors ces problèmes là, je m'en tape un peu, là ...

  7. #7
    Membre très actif
    Avatar de sambia39
    Homme Profil pro
    No Comment
    Inscrit en
    Mai 2010
    Messages
    548
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Loiret (Centre)

    Informations professionnelles :
    Activité : No Comment
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Mai 2010
    Messages : 548
    Par défaut
    Bonsoir, La discussion date cependant je peut-être apporté quelque précision sans trop lancé de débats.
    Citation Envoyé par Emmanuel Delahaye Voir le message

    Non. Ça se traduira par un comportement indéterminé.
    Je pense qu'il n'y a pas de comportement indéterminé en soi, mais il y a bien un segment défaut justifier .
    Je m’explique. Ici s3 const char *s3 = "Développez"; est un pointeur constant sur une chaîne constante. Lors de la phase de compilation toute modification sur s3 sera interdite et la compilation échouera, car l’erreur a été décelé, mais dans le cas contraire "char *s3 = "Développez" le compilateur placera la chaîne constante dans un segment de données en lecture seule et toute tentative de modification à travers ce pointeur aboutira systématiquement à une erreur de segmentation donc pour une variable pointeur sur une chaîne constante on aura systématiquement une erreur de segmentation et non un comportement indéterminé.

    Cas compilation avec const
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    #include <stdio.h>
    #include <stdlib.h>
     
    int main( void ){
     
        const char *ptr = "Bonjour";
        ptr[2] = 'Z';                     
        return EXIT_SUCCESS;
    }
    Code Gcc : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    root@mv:~# gcc *.c -Wall -Werror -g -o Deb 
    source.c: In function 'main':
    source.c:7:9: error: assignment of read-only location '*(ptr + 2u)'
      ptr[2] = 'Z';

    Cas compilation sans const
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>
    #include <stdlib.h>
    
    int main( void ){
        
        char *ptr = "Bonjour";
        ptr[2] = 'Z';                     <<< Thread 1: address access protected (fault address: 0x400566)
        return EXIT_SUCCESS;
    }

    Code Gcc : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    root@mv:~# gcc *.c -Wall -Werror -g -o Deb 
    root@mv:~# ./Deb 
    Segmentation fault


    à bientôt.

  8. #8
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par sambia39 Voir le message
    Je pense qu'il n'y a pas de comportement indéterminé en soi, mais il y a bien un segment défaut justifier .
    [...]
    mais dans le cas contraire "char *s3 = "Développez" le compilateur placera la chaîne constante dans un segment de données en lecture seule et toute tentative de modification à travers ce pointeur aboutira systématiquement à une erreur de segmentation donc pour une variable pointeur sur une chaîne constante on aura systématiquement une erreur de segmentation et non un comportement indéterminé.
    C'est bien un comportement indéterminé https://www.securecoding.cert.org/co...+qualification

    Il n'y a pas obligatoirement d'erreur de segmentation. Par exemple, j'ai constaté que sur certains MCU, si tu essayes d'écrire à une adresse qui pointe vers de la mémoire flash, ça n'a juste pas d'effet.

  9. #9
    Expert confirmé

    Avatar de fearyourself
    Homme Profil pro
    Ingénieur Informaticien Senior
    Inscrit en
    Décembre 2005
    Messages
    5 121
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : Ingénieur Informaticien Senior
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2005
    Messages : 5 121
    Par défaut
    Citation Envoyé par sambia39 Voir le message
    mais dans le cas contraire "char *s3 = "Développez" le compilateur placera la chaîne constante dans un segment de données en lecture seule
    Je pense que ceci n'est pas vrai. Je ne vois nul part dans la norme du langage qu'une chaîne définie par char* doit se trouver en mémoire en lecture seule. Je sais que c'est souvent ce qui se passe et pour résoudre le souci, on fait souvent char s3[] = "Développez" si on veut modifier la chaîne mais je ne suis pas sûr que la norme le force vu que le C est utilisé sur des architectures qui n'ont pas de section lecture seule.

    Mais j'ai un doute donc je pose la question Si j'ai raison, alors il vaut mieux dire "souvent" que toujours.

  10. #10
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Concrêtement, ce n'est pas char* qui peut y être placée, ca c'est une variable sans importance pour la question.
    C'est la chaine litérale. Et encore, la norme ne l'impose pas. Je crois qu'elle n'impose même pas l'existence d'une mémoire dédiée à ce type d'objet.
    Elle pourrait tout aussi bien être dans le tas, ou en bas de la pile.

  11. #11
    Membre Expert
    Homme Profil pro
    sans emploi
    Inscrit en
    Janvier 2014
    Messages
    539
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : sans emploi
    Secteur : Conseil

    Informations forums :
    Inscription : Janvier 2014
    Messages : 539
    Par défaut
    La norme précise qu'il s'agit d'un UB si on tente de modifier les chaînes littérales. Ce qui est requis est qu'une telle chaîne soit stockée dans un tableau statique de la bonne longueur (pas plus, pas moins) → norme C11 6.4.5 String literals.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    char *ptr="hello world"; // ptr pointe sur le premier caractère du littéral
    *ptr='H'; // UB !
     
    char arr[]="hello world"; // on copie le littéral dans le tableau
    arr[0]='H'; // OK
    Dans le code précédent, la norme n'oblige en rien à avoir le littéral "hello world" en read only, ni même à n'avoir qu'une version de ce littéral ni même plusieurs.
    Le fait est qu'une optimisation courante est d'avoir des chaînes littérales en read only (plus contraignant qu'un const) simplement pour pouvoir gagner de la place et fusionner plusieurs chaînes dans un même tableau. gcc proposait un option il y a quelques années pour «rendre» ces chaînes écrivables (?) mais cette option a été enlevée. La tendance est de forcer une plus grande const correctness→-Wwrite-strings (pour autant que ce soit utile et cohérent en C). Du coup on voit souvent :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    const char *ptr="hello world";
    // à la place de
    char *ptr="hello world";

  12. #12
    Expert confirmé

    Avatar de fearyourself
    Homme Profil pro
    Ingénieur Informaticien Senior
    Inscrit en
    Décembre 2005
    Messages
    5 121
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : Ingénieur Informaticien Senior
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2005
    Messages : 5 121
    Par défaut
    picodev

Discussions similaires

  1. question sur les chaine de caractères
    Par amy0o0 dans le forum C
    Réponses: 11
    Dernier message: 14/10/2006, 14h14
  2. [CR 10] Traitement sur les chaines de caractères
    Par sylviefrfr dans le forum SAP Crystal Reports
    Réponses: 1
    Dernier message: 11/09/2006, 09h16
  3. question sur les chaines de caractères
    Par pierrOPSG dans le forum C
    Réponses: 5
    Dernier message: 13/04/2006, 18h55
  4. les chaines de caractères
    Par mrtatou dans le forum C
    Réponses: 4
    Dernier message: 25/01/2006, 14h18
  5. xsl : test sur les chaine de caractère
    Par yos dans le forum XSL/XSLT/XPATH
    Réponses: 1
    Dernier message: 13/07/2005, 15h43

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