Catégorie : Programmation     Tags :      

    Retrouvez cet article dans : Linux Magazine 87

    Les chaînes de caractères sont un type de variables essentiel dans la réalisation de logiciels. En C, elles sont vues comme des tableaux de caractères. Elles réunissent à elles seules deux des bêtes noires du programmeur : la gestion de la mémoire qui est laissée à ses bons soins et la manipulation des pointeurs. La bibliothèque GLib propose une alternative : les GString.

    Introduction

    Lib, bibliothèque d’utilitaires pour le programmeur en C, propose des outils pour vous faciliter la vie avec les chaînes de caractères. Il en existe de deux sortes. L’une d’elle consiste à reprendre et compléter les fonctions existantes. Ainsi, à la place de strdup() ou printf(), vous allez trouver g_strdup() et g_printf() qui font la même chose que leurs homologues de la bibliothèque standard libc. En plus de celles-ci, vous trouverez des fonctions comme g_strdup_printf() qui outre faire comme sprintf(), alloue la mémoire nécessaire à cet effet, ce qui la rend plus sûre, sans pour autant la brider comme snprintf(). L’intérêt de ces fonctions est d’une part de fournir une base sur laquelle peut compter le programmeur soucieux d’écrire du code portable. Si glib existe sur une plate-forme, les appels à glib seront des ennuis en moins pour celui qui voudra faire fonctionner le logiciel sur celle-ci.
    D’autre part, ces fonctions étendent le jeu de fonctions du programmeur qui, pour réaliser une tâche sur une chaîne de caractères, n’hésitera pas à se servir de glib et se concentrera sur de vrais problèmes, pas sur l’implémentation d’une fonction basique de traitement de chaînes de caractères. Cependant, ce n’est pas sur ces fonctions que nous allons nous pencher dans cet article. En effet, outre ces fonctions, glib propose un nouveau type, GString, et l’accompagne de nombreuses fonctions relatives à ce nouveau type de chaînes de caractères. Dans les sources de glib, nous allons nous intéresser aux fichiers glib/gstring.c et glib/gstring.h.

    Remarques:

     Cet article est basé sur la version 2.12.2 de glib.

    Le type GString

    Voici ce que vous pouvez lire dans gstring.h :

    01 typedef struct _GString                GString;        
    et
    01 struct _GString
    02 {
    03   gchar  *str;
    04   gsize len;    
    05   gsize allocated_len;
    06 };

    Comprenez : ce type stocke votre chaîne de caractères dans son champ str. En d’autres termes, pour afficher la chaîne contenue dans la variable c, vous pouvez faire ceci : printf(«%s\n», c->str);. Ce type stocke également la longueur de la chaîne. c->len et strlen(c->str) renvoient donc le même résultat. L’intérêt est qu’utiliser c->len est bien plus rapide ! Enfin, contrairement à vos chaînes de caractères déclarées avec char*, nous connaissons la taille de la mémoire allouée pour la chaîne. Alors qu’avec votre char* vous êtes quasi obligé de postuler que la mémoire allouée est égale à la longueur de la chaîne (à moins de vous compliquer la vie). Le type GString permet une gestion de la mémoire plus fine, sans devoir modifier la taille de l’espace mémoire à chaque changement de taille de la chaîne de caractères. C’est tout l’intérêt du champ allocated_len.

    /img-articles/lm/87/art-3/fig-1.jpg

     Fig. 1 : Gstring

    Créer une nouvelle chaîne de caractères

    g_string_new()

    Lorsque vous souhaitez créer une nouvelle chaîne de caractères, vous avez en général une chaîne, appelons-la « texte », à y stocker. Voici le moyen le plus simple d’y arriver :

    01 GString *c;
    02 c = g_string_new(«texte»);

    Voici ce que fait glib dans l’ombre de cette fonction :

    01 GString*
    02 g_string_new (const gchar *init)
    03 {
    04   GString *string;
    05
    06   if (init == NULL || *init == ‘\0’)
    07     string = g_string_sized_new (2);
    08   else
    09     {
    10       gint len;
    11
    12       len = strlen (init);
    13       string = g_string_sized_new (len + 2);
    14
    15       g_string_append_len (string, init, len);
    16     }
    17
    18   return string;
    19 }

    La lecture de ce code nous montre que cette fonction n’est rien d’autre qu’un appel à g_string_sized_new() précédée d’un calcul de la longueur initiale de la chaîne. Soit notre chaîne initiale est nulle ou vide (ligne 6), auquel cas la longueur de la chaîne sera 2 (ligne 7), soit nous avons affaire à un vrai contenu, de longueur len calculée ligne 12. Dans ce second cas, un caractère supplémentaire est nécessaire. Pour des raisons que nous allons voir plus loin, mais qui reviennent à souhaiter disposer d’espace libre en fin de chaîne pour pouvoir l’étendre, nous ne demandons pas un caractère supplémentaire, mais deux. Enfin, la fonction g_string_append_len() ligne 15 parle d’elle-même : elle ajoute notre chaîne passée en argument à la chaîne nouvellement créée, ce qui revient à l’initialiser avec notre contenu.

    g_string_sized_new()

    Cette fonction est le cœur de la création d’une nouvelle chaîne. Voici son contenu :

    01 GString*
    02 g_string_sized_new (gsize dfl_size)    
    03 {
    04   GString *string = g_slice_new (GString);
    05
    06   string->allocated_len = 0;
    07   string->len   = 0;
    08   string->str   = NULL;
    09
    10   g_string_maybe_expand (string, MAX (dfl_size, 2));
    11   string->str[0] = 0;
    12
    13   return string;
    14 }

    Le début est facile à comprendre : nous allouons un peu de mémoire pour le type GString (ligne 4) avec le gestionnaire de mémoire de glib (récemment apparu et rendant obsolète les chunks). Ce GString est initialisé à vide lignes 6 à 8. Il s’agit ensuite d’allouer de la mémoire comme demandé en argument avec dfl_size. Au lieu de faire appel à un simple malloc() ou g_malloc(), au lieu d’utiliser un g_slice_new(), nous faisons appel à une fonction bizarre : g_string_maybe_expand() que nous allons voir ci-après. Remarquons qu’une fois l’espace alloué, la chaîne est initialisée avec un contenu vide (ligne 11).

    g_string_maybe_expand()

    Mais qu’est-ce donc bien que cette chose qui n’est rien d’autre qu’une fonction interne ?

    01 static void
    02 g_string_maybe_expand (GString* string,
    03                        gsize    len)
    04 {
    05  if (string->len + len >= string->allocated_len)
    06    {
    07      string->allocated_len = nearest_power (1, string->len + len + 1);
    08      string->str = g_realloc (string->str, string->allocated_len);
    09    }
    10 }

    Vous disposez d’une chaîne de caractères string et souhaitez qu’elle ait la taille len. Cette fonction va effectuer pour vous un premier test pour savoir si vous avez réellement besoin d’étendre l’espace alloué à votre chaîne de caractères. Vu la nature du test, qui additionne string->len et len, vous en déduisez que len n’est pas la taille souhaitée comme nous le pensions plus haut, mais la taille de l’espace supplémentaire dont vous avez besoin. Ceci devrait donc être très utile pour les fonctions de concaténation de chaînes. Pour la création d’une nouvelle, peu importe puisque string->len est nul. De plus, string->allocated_len étant également nul, le test est toujours positif pour une nouvelle chaîne, ce qui nous amène à la suite.

    Pour étendre une chaîne de caractères, nous utilisons g_realloc() (ligne 8) qui est gourmande en temps d’exécution. Pour minimiser le redimensionnement des chaînes de caractères, nous préférons prévoir assez de place pour de futures demandes de redimensionnements. Aussi, string->allocated_len ne prend pas la valeur de string->len + len + 1, mais une valeur plus grande, calculée par la sombre fonction nearest_power(). En voici le code:

    01 static inline gsize
    02 nearest_power (gsize base, gsize num)
    03 {
    04   if (num > MY_MAXSIZE / 2)
    05     {
    06       return MY_MAXSIZE;
    07     }
    08   else
    09     {
    10       gsize n = base;
    11
    12       while (n < num)
    13         n <<= 1;
    14
    15       return n;
    16     }
    17 }

    Contrairement au reste du code, celui-ci n’est pas très facile à lire. Pourtant, il est simple. Le cœur de cette fonction se trouve aux lignes 12 et 13. Tant que n est inférieur à la valeur num fournie en argument, n est multiplié par deux. C’est cette opération ligne 13 qui rend le code illisible. Mais n <<= 1 est équivalent à n = n << 1. Ceci est un décalage de n d’un bit vers la gauche, ce qui – faites l’opération – revient à le multiplier par deux. Si nous revenons à la ligne 10, nous avons donc une valeur base qui est multipliée par deux et remultipliée par deux et ainsi de suite jusqu’à dépasser la valeur num. Pour éviter le débordement de tampon, nous allons vous raconter une histoire. Dans une mare se trouvent quelques nénuphars. Chaque jour, le nombre de ces nénuphars double. Un beau jour, disons le jour J, la mare est remplie. Question : quel jour la mare était-elle à moitié remplie ?

    Ici, nous devons éviter de faire déborder la mare, ce qui arrive le jour où la multiplication par deux fait déborder le tampon. Si une multiplication par deux est supérieure à la taille maximale autorisée, MY_MAXSIZE dans le code (ligne 4), cela signifie que la valeur seuil num est déjà supérieure à la moitié de MY_MAXSIZE au début de la fonction. Il est très simple de détecter ce cas (ligne 4) et, dans ce cas, de retourner la valeur maximale autorisée MY_MAXSIZE (ligne 6). Quant à la mare, vous avez maintenant trouvé : elle était à moitié remplie avant que le nombre de nénuphars ne double pour remplir complètement la mare. La réponse est donc au jour J-1.

    L’idée qui se cache derrière cette fonction est que si une chaîne a pu grandir pour atteindre une certaine taille, il est probable qu’elle grandisse encore. Par contre, il est beaucoup moins probable qu’elle ne double de taille. Comme il faut choisir une valeur pour la nouvelle taille, et que glib est une bibliothèque généraliste, il faut bien trancher sur une valeur entre un peu plus et le double. Les développeurs de glib ont choisi le double, ce qui laisse une bonne marge pour la plupart des applications.

    Supprimer une chaîne de caractères

    La fonction g_string_free() permet de libérer la mémoire allouée pour une chaîne ainsi que pour le type Gstring :

     01 gchar*
    02 g_string_free (GString *string,
    03                gboolean free_segment)
    04 {
    05   gchar *segment;
    06
    07   g_return_val_if_fail (string != NULL, NULL);
    08
    09   if (free_segment)
    10     {
    11       g_free (string->str);
    12       segment = NULL;
    13     }
    14   else
    15     segment = string->str;
    16
    17   g_slice_free (GString, string);
    18
    19   return segment;
    20 }

    Cette fonction est plutôt simple à comprendre. Un premier test ligne 7 nous fait sortir si la chaîne de caractère n’est rien d’autre que NULL. Ensuite, tout dépend du comportement attendu de cette fonction via son second paramètre. Souhaitez-vous libérer la mémoire allouée pour la chaîne contenue dans le GString (test ligne 9) ? Alors faites-le ligne 11 et la valeur qui sera retournée ligne 19 sera NULL (ligne 12). Sinon, comme string, et par conséquent string->str doivent disparaître, nous sauvons ce pointeur dans la variable segment qui sera renvoyée ligne 19. Puis, nous libérons la mémoire allouée au type GString ligne 17 et renvoyons ce que nous avons à renvoyer ligne 19.

    Modifier la chaîne de caractères

    Remplacer son contenu

    Remplacer du contenu signifie initialiser le GString avec une nouvelle chaîne.

    01 GString*
    02 g_string_assign (GString     *string,
    03                  const gchar *rval)
    04 {
    05   g_return_val_if_fail (string != NULL, NULL);
    06   g_return_val_if_fail (rval != NULL, string);
    07
    08   /* Make sure assigning to itself doesn’t corrupt the string.  */
    09   if (string->str != rval)
    10     {
    11       /* Assigning from substring should be ok since g_string_truncate
    12          does not realloc.  */
    13       g_string_truncate (string, 0);
    14       g_string_append (string, rval);
    15     }
    16
    17   return string;
    18 }

    Après deux vérifications sur les paramètres (lignes 5 et 6), suivies d’une troisième vérification pour ne pas remplacer la chaîne par elle-même (ligne 8), nous utilisons des fonctions déjà existantes. La fonction g_string_truncate() supprime la fin de la chaîne pour ne laisser que le début. Ligne 13, nous indiquons que le début a une longueur nulle. Cela revient donc à vider la chaîne. Ligne 14, nous faisons appel à g_string_append() que nous allons voir plus loin, pour ajouter du contenu en fin de chaîne. Comme la nôtre est maintenant vide, cela correspond à ce que nous voulons faire.
    Jetons un coup d’œil à g_string_truncate() :

    01 GString*
    02 g_string_truncate (GString *string,
    03                    gsize    len)
    04 {
    05   g_return_val_if_fail (string != NULL, NULL);
    06
    07   string->len = MIN (len, string->len);
    08   string->str[string->len] = 0;
    09
    10   return string;
    11 }

    Comme la fonction précédente, celle-ci commence par un test d’argument (ligne 5). Puis la longueur de la chaîne est diminuée comme souhaité (ligne 7). Vous constatez que rien n’est fait si celle-ci était déjà plus petite. Enfin, un caractère nul est inséré à la position indiquée par la taille de la chaîne pour marquer la fin de celle-ci (ligne 8). Comme string->len est initialisé ligne 7 à une valeur ne pouvant pas être plus grande que la valeur actuelle, nous sommes protégés du débordement de tampon ligne 8.

    Ajouter du contenu à la fin

    C’est ici que nous allons voir le code de g_string_append(). Mais vous allez être déçu :

    01 GString*
    02 g_string_append (GString     *string,
    03                  const gchar *val)
    04 {
    05   g_return_val_if_fail (string != NULL, NULL);
    06   g_return_val_if_fail (val != NULL, string);
    07
    08   return g_string_insert_len (string, -1, val, -1);
    09 }

    Cette fonction n’est rien d’autre qu’un appel à la fonction g_string_insert_len() (ligne 8) après vérification des arguments. Il existe également une fonction g_string_append_len() dont la différence avec g_string_append() est de limiter la chaîne de caractères à concaténer à un certain nombre de caractères. Cette fonction fait également appel à g_string_insert_len() :

    01 GString*
    02 g_string_append_len (GString     *string,
    03                      const gchar *val,
    04                      gssize       len)
    05 {
    06   g_return_val_if_fail (string != NULL, NULL);
    07   g_return_val_if_fail (val != NULL, string);
    08
    09   return g_string_insert_len (string, -1, val, len);
    10 }

    Voyons cette fameuse g_string_insert_len() :

    01 GString*
    02 g_string_insert_len (GString     *string,
    03                      gssize       pos,
    04                      const gchar *val,
    05                      gssize       len)

    Cette fonction insère dans la chaîne string, à la position pos, les len premiers caractères d’une une chaîne val.

    06 {
    07   g_return_val_if_fail (string != NULL, NULL);
    08   g_return_val_if_fail (val != NULL, string);
    09
    10   if (len < 0)
    11     len = strlen (val);
    12
    13   if (pos < 0)
    14     pos = string->len;
    15   else
    16     g_return_val_if_fail (pos <= string->len, string);
    17

    Après les vérifications d’usage dans glib (lignes 7 et 8), il se peut que des arguments aient été placés à -1 pour que notre fonction leur attribue une valeur automatiquement. C’est le cas pour len ligne 11 dont -1 veut dire toute la chaîne, soit strlen(val). C’est également le cas pour pos ligne 14 qui, à -1, signifie en fin de chaîne, donc à string->len. Une dernière vérification est effectuée ligne 16 pour le cas où l’insertion (pos) aurait été demandée au-delà de la chaîne string (longueur string->pos).

    18   /* Check whether val represents a substring of string.  This test
    19      probably violates chapter and verse of the C standards, since
    20      «>=» and «<=» are only valid when val really is a substring.
    21      In practice, it will work on modern archs.  */
    22   if (val >= string->str && val <= string->str + string->len)
    23     {

    La ligne 22 n’est effectivement pas conforme à la littérature sur le C : c’est mal de comparer des pointeurs ! Et vous, comment auriez-vous fait ? Le code suivant est découpé en trois parties : if, partie 1 ; else, partie 2 ; fin, partie 3. Nous exécutons le cas 1 si la chaîne à insérer se trouve déjà dans la chaîne qui va subir l’insertion. Le principe de cette partie 1 est de faire un peu de place pour la chaîne à insérer, ce que vous lisez ligne 33 (sauf si la chaîne est à insérer à la fin, quand pos et string->len sont égaux), puis d’y copier la chaîne à insérer dedans. Vous lisez ligne 29 un commentaire intéressant. La variable var est certes initialisée par les bons soins de l’appel à la fonction. Cependant, relisez le code de la fonction g_string_maybe_expand() : elle fait appel à g_realloc(). Autrement dit, puisque val pointait à l’intérieur de string->str, il est possible que la chaîne ait été déplacée et que val ne pointe plus sur rien. C’est tout l’intérêt de la variable offset qui retient la position du début de val dans string->str (ligne 24). Ainsi, val peut être initialisée à nouveau (ligne 28).

    24   gsize offset = val - string->str;
    25   gsize precount = 0;
    26
    27   g_string_maybe_expand (string, len);
    28   val = string->str + offset;
    29   /* At this point, val is valid again.  */
    30
    31   /* Open up space where we are going to insert.  */
    32   if (pos < string->len)
    33     g_memmove (string->str + pos + len, string->str + pos, string->len - pos);
    34

    Maintenant que le trou est creusé, nous pouvons le remplir. Question : que se passe-t-il si nous avons découpé notre chaîne val en deux en creusant notre trou ? Nous devons simplement prendre la partie du début et la recopier (lignes 36 à 39) où precount contient le nombre de caractères entre offset et pos si leur différence est bien positive (sinon, offset, le début de la chaîne à copier, est après l’endroit où la copier, ce qui ne nous intéresse plus dans les lignes 36 à 40). Ligne 39, nous copions donc le début de la chaîne, soit precount caractères.

    35       /* Move the source part before the gap, if any.  */
    36       if (offset < pos)
    37         {
    38           precount = MIN (len, pos - offset);
    39           memcpy (string->str + pos, val, precount);
    40         }
    41

    Il ne reste plus qu’à faire de même pour la fin de la chaîne, si nous n’avons pas déjà tout copié (dans ce cas, precount serait égale à len). La copie est effectuée ligne 44.

    42       /* Move the source part after the gap, if any.  */
    43       if (len > precount)
    44         memcpy (string->str + pos + precount,
    45                 val + /* Already moved: */ precount + /* Space opened up: */ len,
    46                 len - precount);
    47     }
    48   else
    49     {

    Dans cette deuxième partie, c’est beaucoup plus simple, car nous ne coupons pas la chaîne à insérer en deux. Il suffit donc d’étendre le GString (ligne 50), de creuser le trou (ligne 56) et d’insérer la chaîne par un simple memcpy() (ligne 62), à moins que nous n’ayons qu’un seul caractère à insérer (ligne 60).

    50       g_string_maybe_expand (string, len);
    51
    52       /* If we aren’t appending at the end, move a hunk
    53        * of the old string to the end, opening up space
    54        */
    55       if (pos < string->len)
    56         g_memmove (string->str + pos + len, string->str + pos, string->len - pos);
    57
    58       /* insert the new string */
    59       if (len == 1)
    60         string->str[pos] = *val;
    61       else
    62         memcpy (string->str + pos, val, len);
    63     }
    64

    Il ne reste plus qu’à faire un peu de maintenance, en modifiant la longueur du GString (ligne 65) et en s’assurant qu’il y a bien un caractère nul à la fin de la chaîne (ligne 67).

    65   string->len += len;
    66
    67   string->str[string->len] = 0;
    68
    69   return string;
    70 }

    Ajouter du contenu au début

    Nous allons retrouver, via les deux fonctions suivantes, des appels à g_string_insert_len() avec pos placé à nul. Nous n’irons donc pas plus loin dans les explications.

    01 GString*
    02 g_string_prepend (GString     *string,
    03                   const gchar *val)
    04 {
    05   g_return_val_if_fail (string != NULL, NULL);
    06   g_return_val_if_fail (val != NULL, string);
    07
    08   return g_string_insert_len (string, 0, val, -1);
    09 }
    01 GString*
    02 g_string_prepend_len (GString          *string,
    03                       const gchar *val,
    04                       gssize       len)
    05 {
    06   g_return_val_if_fail (string != NULL, NULL);
    07   g_return_val_if_fail (val != NULL, string);
    08
    09   return g_string_insert_len (string, 0, val, len);
    10 }

    g_string_printf()

    Que serait une gestion des chaînes de caractères sans l’équivalent de sprintf(), mais sécurisée ? Glib propose la fonction suivante :

    01 void
    02 g_string_printf (GString *string,
    03                  const gchar *fmt,
    04                  ...)
    05 {
    06   va_list args;
    07
    08   g_string_truncate (string, 0);
    09
    10   va_start (args, fmt);
    11   g_string_append_printf_internal (string, fmt, args);
    12   va_end (args);
    13 }

    Cette fonction, comme d’autres que nous avons pu voir, est frustrante : après avoir effacé l’ancienne chaîne (ligne 8), nous avons une simple gestion du nombre variable d’arguments (lignes 10 et 12) et un appel à g_string_append_printf_internal(). Cette dernière n’est pas mieux :

    01 static void
    02 g_string_append_printf_internal (GString     *string,
    03                                  const gchar *fmt,
    04                                  va_list      args)
    05 {
    06   gchar *buffer;
    07   gint length;
    08
    09   length = g_vasprintf (&buffer, fmt, args);
    10   g_string_append_len (string, buffer, length);
    11   g_free (buffer);
    12 }

    Vous devinez que, ligne 9, g_vasprintf() fait tout le travail d’allocation mémoire et de remplissage de la chaîne buffer, et renvoie la longueur dans length. Comme notre chaîne a été vidée dans g_string_printf(), l’appel à g_string_append_len() ligne 10 ne fait rien d’autre que de mettre le contenu de buffer dans notre chaîne string. Il ne reste plus qu’à libérer la mémoire pointée par buffer. Résultat : nous n’avons vu que des trivialités. Intéressons-nous donc à g_vasprintf() que vous trouvez dans un autre fichier source : glib/gprintf.c :

    01 gint
    02 g_vasprintf (gchar      **string,
    03              gchar const *format,
    04              va_list      args)
    05 {
    06   gint len;
    07   g_return_val_if_fail (string != NULL, -1);
    08
    09 #if !defined(HAVE_GOOD_PRINTF)
    10
    11   len = _g_gnulib_vasprintf (string, format, args);
    12   if (len < 0)
    13     *string = NULL;
    14
    15 #elif defined (HAVE_VASPRINTF)
    16
    17   len = vasprintf (string, format, args);
    18   if (len < 0)
    19     *string = NULL;
    20   else if (!g_mem_is_system_malloc ())
    21     {
    22       /* vasprintf returns malloc-allocated memory */
    23       gchar *string1 = g_strndup (*string, len);
    24       free (*string);
    25       *string = string1;
    26     }
    27
    28 #else
    29
    30   {
    31     va_list args2;
    32
    33     G_VA_COPY (args2, args);
    34
    35     *string = g_new (gchar, g_printf_string_upper_bound (format, args));
    36
    37     len = _g_vsprintf (*string, format, args2);
    38     va_end (args2);
    39   }
    40 #endif
    41
    42   return len;
    43 }

    Cette fonction contient en réalité trois contenus différents.

    • Soit vous disposez de _g_gnulib_vasprintf(). Dans ce cas, comme le code vous le montre lignes 11 à 13, cette fonction fait tout le travail. Il n’y a pas de raison de s’en priver.
    • Soit vous disposez de vasprintf(). Cette fonction effectue également tout le travail. Cependant, par souci de compatibilité avec glib, l’allocation de mémoire doit se faire avec une fonction de la famille de g_malloc() et non de malloc(). C’est pour cela que, lignes 23 à 25, la chaîne est dupliquée avec g_strndup() et libérée avec free(). C’est le duplicata qui sera renvoyé.
    • Soit vous ne disposez de rien du tout, et c’est là que nous allons apprendre à faire cela nous-même.

    Le principe est en réalité tout simple. Nous effectuons un appel à vsnprintf() sur un octet de mémoire. Cette fonction, avec un seul octet à sa disposition, ne peut que sortir en erreur. La valeur renvoyée est cependant la taille nécessaire pour la chaîne résultat. Mais où se trouve donc cet appel à vsnprintf() ? Il se cache dans g_printf_string_upper_bound() ligne 35. Voici son code, extrait de glib/gmessages.c :

    01 gsize
    02 g_printf_string_upper_bound (const gchar *format,
    03                              va_list      args)
    04 {
    05   gchar c;
    06   return _g_vsnprintf (&c, 1, format, args) + 1;
    07 }

    Sur notre machine, la fonction _g_vsnprintf() est définie ainsi (dans glib/gprintfint.h) :

    01 #define _g_vsnprintf _g_gnulib_vsnprintf

    Nous finissons le jeu de piste dans le fichier glib/gnulib/printf.c :

    01 int _g_gnulib_vsnprintf (char *string, size_t n, char const *format, va_list args)
    02 {
    03   char *result;
    04   size_t length;
    05
    06   result = vasnprintf (NULL, &length, format, args);
    07   if (result == NULL)
    08     return -1;
    09
    10   if (n > 0)
    11     {
    12       memcpy (string, result, MIN(length + 1, n));
    13       string[n - 1] = 0;
    14     }
    15
    16   free (result);
    17
    18   return length;
    19 }

    La fonction vasnprintf() est également une fonction de glib, mais son origine est GNUlib (http://www.gnu.org/software/gnulib/ : The GNU Portability Library). Nous vous invitons à parcourir le code qui la définit, dans glib/gnulib/vasnprintf.c. Nous ne le recopions pas ici à cause de sa longueur : 1038 lignes dans glib-2.12.2 (que vous pouvez comparer aux 930 lignes de glib/gstring.c de la gestion des GString). Nous nous contentons de considérer la fonction précédente qui renvoie comme prévu la taille nécessaire à l’opération dans length. Dans notre cas, ligne 10, n vaut 1 donc la ligne 12 n’a aucun intérêt : la ligne 13 met un caractère nul dans notre chaîne d’un seul octet. C’est surtout le retour de la variable length qui nous intéresse ligne 18. Revenons à notre fonction g_vasprintf(). Nous avons donc obtenu la longueur prévue pour la chaîne à créer. Sur la même ligne 35, nous allouons cet espace avec g_new(). Nous en avons presque terminé ligne 37 où nous remplissons la chaîne avec _g_vsnprintf(). Comme avec les autres fonctions, nous renvoyons la longueur de la chaîne de caractères comme résultat de la fonction (ligne 42).

    Passer une chaîne de caractères en majuscules

    Nous allons finir cet article avec une fonction qui va nous entraîner dans un nouveau jeu de piste, bien plus amusant que les précédents. En effet, son code est simple, mais fait appel à de nombreux éléments de glib. En voici le contenu :

    g_string_ascii_up()

     01 GString*
    02 g_string_ascii_up (GString *string)
    03 {
    04   gchar *s;
    05   gint n;
    06
    07   g_return_val_if_fail (string != NULL, NULL);
    08
    09   n = string->len;
    10   s = string->str;
    11
    12   while (n)
    13     {
    14       *s = g_ascii_toupper (*s);
    15       s++;
    16       n--;
    17     }
    18
    19   return string;
    20 }

    Il s’agit de mettre une chaîne GString en majuscules. L’équivalent en minuscules existe, s’appelle g_string_ascii_down() et s’écrit de la même manière.
    Dans la fonction ci-dessus, après le test classique ligne 7, nous parcourons toute la chaîne (boucle lignes 12 à 17) pour exécuter g_ascii_toupper() sur chaque caractère. Prêt pour le jeu de piste ? Alors cherchez le code de g_ascii_toupper()...

    g_ascii_toupper()

    Il se trouve dans glib/gstrfuncs.c.

    01 gchar
    02 g_ascii_toupper (gchar c)
    03 {
    04   return g_ascii_islower (c) ? c - ‘a’ + ‘A’ : c;
    05 }

    Facile à comprendre : le caractère est-il en minuscules (g_ascii_islower()) ? Si oui, nous renvoyons sa version en majuscules, soit le caractère moins la différence entre un caractère minuscule et sa version majuscule (c - ‘a’ + ‘A’).
    Sinon, nous pouvons renvoyer le caractère tel quel. Continuons le jeu de piste avec g_ascii_islower()...

    g_ascii_islower()

    Avez-vous trouvé le code de cette fonction ? Il est dans glib/gstrfuncs.h :

    01 #define g_ascii_islower(c) \
    02   ((g_ascii_table[(guchar) (c)] & G_ASCII_LOWER) != 0)

    Le jeu de piste continue avec g_ascii_table[], guchar et G_ASCII_LOWER. Mais nous pouvons déjà deviner un peu...

    Le tableau g_ascii_table[] (reconnaissable à ses crochets) contient, pour chaque caractère donné en indice, les caractéristiques du caractère sous forme d’un champ de bits. G_ASCII_LOWER serait l’un de ces bits.

    GAsciiType

    Un peu plus haut dans le fichier glib/gstrfuncs.h, vous pouvez lire ceci :

    01 typedef enum {
    02   G_ASCII_ALNUM  = 1 << 0,
    03   G_ASCII_ALPHA  = 1 << 1,
    04   G_ASCII_CNTRL  = 1 << 2,
    05   G_ASCII_DIGIT  = 1 << 3,
    06   G_ASCII_GRAPH  = 1 << 4,
    07   G_ASCII_LOWER  = 1 << 5,
    08   G_ASCII_PRINT  = 1 << 6,
    09   G_ASCII_PUNCT  = 1 << 7,
    10   G_ASCII_SPACE  = 1 << 8,
    11   G_ASCII_UPPER  = 1 << 9,
    12   G_ASCII_XDIGIT = 1 << 10
    13 } GAsciiType;

    Nous avons donc bien un champ de bits, où nous retrouvons ligne 7 celui que nous cherchions. Quant au tableau g_ascii_table[], il est à la ligne suivante :

    01 GLIB_VAR const guint16 * const g_ascii_table;

    Cette déclaration du tableau ne nous en apprend pas tant que ça. Le jeu de piste n’est pas fini !

    g_ascii_table

    C’est dans gstrfuncs.c que nous trouvons notre définition du tableau :

    01 static const guint16 ascii_table_data[256] = {
    02   0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004,
    03   0x004, 0x104, 0x104, 0x004, 0x104, 0x104, 0x004, 0x004,
    04   0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004,
    05   0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004,
    06   0x140, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0,
    07   0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0,
    08   0x459, 0x459, 0x459, 0x459, 0x459, 0x459, 0x459, 0x459,
    09   0x459, 0x459, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0,
    10   0x0d0, 0x653, 0x653, 0x653, 0x653, 0x653, 0x653, 0x253,
    11   0x253, 0x253, 0x253, 0x253, 0x253, 0x253, 0x253, 0x253,
    12   0x253, 0x253, 0x253, 0x253, 0x253, 0x253, 0x253, 0x253,
    13   0x253, 0x253, 0x253, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x0d0,
    14   0x0d0, 0x473, 0x473, 0x473, 0x473, 0x473, 0x473, 0x073,
    15   0x073, 0x073, 0x073, 0x073, 0x073, 0x073, 0x073, 0x073,
    16   0x073, 0x073, 0x073, 0x073, 0x073, 0x073, 0x073, 0x073,
    17   0x073, 0x073, 0x073, 0x0d0, 0x0d0, 0x0d0, 0x0d0, 0x004
    18   /* the upper 128 are all zeroes */
    19 };
    20
    21 const guint16 * const g_ascii_table = ascii_table_data;

    Il est aussi illisible que compréhensible : nous avons bien des champs de bits lignes 1 à 19. La ligne 21 fait pointer g_ascii_table sur le tableau ascii_table_data[].

    guchar

    Dans notre jeu de piste, il ne manque plus que le mot clé guchar. A son utilisation plus haut, vous lisiez g_ascii_table[(guchar) (c)]. Cela ressemble fortement à un cast. Ce terme serait donc un type. C’est dans le fichier glib/gtypes.h que nous en trouvons la preuve :

     01 typedef unsigned char   guchar;

    Conclusion

    Le jeu de piste est fini. En guise de conclusion, voyons les quelques manières de gagner dans un jeu de piste comme le précédent. Votre premier outil n’est rien d’autre que la commande grep. Glib étant bien structurée, grep fonctionne bien car tout ou presque se trouve dans le répertoire glib/. Voici un exemple avec g_ascii_table :

    $ grep g_ascii_table *
    glib.symbols:g_ascii_table
    gstrfuncs.c:const guint16 * const g_ascii_table = ascii_table_data;
    gstrfuncs.h:GLIB_VAR const guint16 * const g_ascii_table;
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_ALNUM) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_ALPHA) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_CNTRL) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_DIGIT) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_GRAPH) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_LOWER) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_PRINT) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_PUNCT) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_SPACE) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_UPPER) != 0)
    gstrfuncs.h:  ((g_ascii_table[(guchar) (c)] & G_ASCII_XDIGIT) != 0)

    Cette recherche venant de ce que nous lisions dans gstrfuncs.h (en particulier la ligne contenant G_ASCII_LOWER), c’est ailleurs que nous cherchons. Or, il se trouve que gstrfuncs.c semble correspondre. Un coup d’œil dans ce fichier et c’est trouvé.
    Lorsque grep ne suffit plus, lorsqu’il faut rechercher ailleurs que dans le répertoire courant, quand par exemple vasnprintf() est introuvable, vous pouvez tenter votre chance avec la commande find :

    /glib-2.12.2/glib$ find . -type f -exec grep vasnprintf {} \; -print
    [...]
    #define vasnprintf       _g_gnulib_vasnprintf
    ./gnulib/g-gnulib.h
    extern char * vasnprintf (char *resultbuf, size_t *lengthp, const char *format, va_list args)
    ./gnulib/vasnprintf.h
    #include «vasnprintf.h»
    vasnprintf (char *resultbuf, size_t *lengthp, const char *format, va_list args)
    ./gnulib/vasnprintf.c
    [...]

    Le résultat est assez explicite dès que vous avez noté que le nom du fichier apparaît après la ou les lignes contenant le motif recherché. Vous pouvez donc, ici, chercher dans glib/gnulib/vasnprintf.c.
    Lorsque vous ne trouvez toujours rien, il vous reste encore quelques solutions. Commencez avec les pages de manuel. En existe-t-il une pour ce que vous cherchez ? Sinon, lancez la commande find précédente dans le répertoire /usr/include.
    Cela donne parfois la solution alors que vous ne vous y attendez pas. Et sinon, il ne vous reste plus qu’à vous connecter à internet et à demander à votre moteur de recherche favori s’il peut quelque chose pour vous.
    Le mois prochain : les tables de hachage de type GHashTable...

    Références:

    • Le site de GTK+ : http://www.gtk.org/
    • C en action, O’Reilly, chapitre 8.

    Retrouvez cet article dans : Linux Magazine 87

    Posté par Yves Mettier (Yves Mettier) | Signature : Yves Mettier | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.