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

Notes sur le langage C

Notes sur le langage C


prcdentsommairesuivant

XXXVIII. Du bon usage de qsort()

XXXVIII-A. Description de la fonction qsort()

XXXVIII-A-1. Introduction

La fonction qsort() implmente un algorithme de tri non spcifi qui permet de trier tout ou partie de n'importe quel tableau de donnes, du moment qu'il existe un critre de tri dans les donnes. Elle s'appuie sur une fonction utilisateur qui se charge d'exprimer le critre de tri.

C'est l'implmentation qui dcide quel algorithme est utilis. En gnral, l'algorithme est choisi pour sa performance. Il n'est pas rare que ce soit un Quick Sort.

XXXVIII-A-2. Interface

Le prototype de la fonction qsort() est

 
Sélectionnez
void qsort (void *tableau
            , size_t nb_elem
            , size_t taille_elem
            , int (*compare) (void const *a, void const *b));

Les paramtres sont :

  • void *tableau : adresse du premier lment du tableau trier. La partie trier doit tre modifiable
  • size_t nb_elem : nombre d'lments du tableau trier ( ne pas confondre avec sa taille) : Par exemple, tout le tableau : sizeof tab / sizeof *tab
  • size_t taille_elem : taille d'un lment du tableau : sizeof *tab
  • int (*compare) (void const *a, void const *b) : adresse de la fonction de comparaison (pointeur de fonction). Cette fonction est fournie par l'utilisateur
XXXVIII-A-2-a. Interface de la fonction de comparaison

Cette fonction dispose de 2 paramtres :

  • void const *a : adresse d'un des lments du tableau en cours d'valuation par l'algorithme. Il est qualifi 'const', car la fonction de comparaison ne doit en aucun cas en modifier le contenu sous peine de comportement indfini.
  • void const *b : adresse d'un autre lment du tableau en cours d'valuation par l'algorithme. Il est qualifi 'const', car la fonction de comparaison ne doit en aucun cas en modifier le contenu sous peine de comportement indfini.

de plus, elle doit retourner un int qui prend la valeur suivante (tri croissant) :

  • 0 si le critre de a est gal au critre de b
  • < 0 si le critre de a est infrieur au critre de b
  • > 0 si le critre de a est suprieur au critre de b

XXXVIII-A-3. Comportement

La fonction qsort() effectue le tri du tableau selon le critre indiqu. La valeur rtourne par la fonction de comparaison permet l'algorithme de prendre les dcisions qui s'imposent.

XXXVIII-A-3-a. Comportement de la fonction de comparaison

La fonction de comparaison reoit l'adresse des 2 lments en cours d'valuation. Par l'intrmdaire de pointeurs locaux typs et initialiss avec ces paramtres, elle doit evaluer le critre de tri et retourner un int de la valeur rsultant de l'valuation. Pour un entier, une simple diffrence suffit. Pour une chaine, str[n]cmp() a t conue pour a. Pour un rel, c'est plus dlicat, car la notion d'galit est soumise l'apprciation d'un EPSILON qui dpend de la prcision recherche.

XXXVIII-B. Usage de la fonction qsort()

Afin de ddramatiser l'usage de qsort(), qui fait parfois un peu peur, je fournis ici quelques exemples d'utilisation.

Attention, je suppose que les bases du C sont connues (tableaux, pointeurs, fonctions, I/O)

Ces exemples ne sont pas des suites de validation d'algorithmes de tri ! Ce ne sont que de simples exemples.

XXXVIII-B-1. Tri d'un tableau d'entiers

Soit un tableau de 5 entiers trier : 4 , 6, -3, 4, 7,

 
Sélectionnez
#include <stdio.h>
#include <stdlib.h>
 
/* affichage du tableau */
static void aff (int *a, size_t n)
{
   size_t i;
   for (i = 0; i < n; i++)
   {
      printf ("=", a[i]);
   }
   printf ("\n");
}
 
/* fonction utilisateur de comparaison fournie a qsort() */
static int compare (void const *a, void const *b)
{
   /* definir des pointeurs type's et initialise's
      avec les parametres */
   int const *pa = a;
   int const *pb = b;
 
   /* evaluer et retourner l'etat de l'evaluation (tri croissant) */
   return *pa - *pb;
}
 
int main (void)
{
/* tableau a trier */
   int tab[] = { 4, 6, -3, 4, 7 };
 
/* affichage du tableau dans l'etat */
   aff (tab, sizeof tab / sizeof *tab);
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, compare);
 
/* affichage du tableau apres le tri */
   aff (tab, sizeof tab / sizeof *tab);
 
   return 0;
}

Ce qui donne :

 
Sélectionnez
  4  6 -3  4  7
 -3  4  4  6  7
 
Press ENTER to continue.

XXXVIII-B-2. Tri d'un tableau de chaines

Il s'agit maintenant de trier un tableau de chaines constantes. Ici, il est particulirement important de bien comprendre le sens de 'const'. Le tableau est modifiable, mais il est compos de pointeurs sur des chaines non modifiables. Ce que va faire qsort(), c'est de rorganiser le tableau de pointeurs de faon ce lorsqu'on lira le tableau en squence, les chaines pointes apparaissent comme 'tries'. Ce concept est assez subtil, car le tri est indirect. En effet, si on regardait la valeur des pointeurs dans le tableau, ils ne seraient probablement pas tris. De mme, les chaines en mmoire non modifiable sont, par dfinition, rstes leur place.

Dans la fonction de comparaison, comme d'habitude, on rcupre les adresse des lments en cours d'valuation. Ici, les lments sont de type char const *. Leur adresse est donc de type char const * *. Mais comme on a pas le droit de modifier les donnes pointes, on doit ajouter un 'const' pour tre conforme aux paramtres : char const * const *

 
Sélectionnez
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
/* affichage du tableau */
static void aff (char const **a, size_t n)
{
   size_t i;
   for (i = 0; i < n; i++)
   {
      printf ("%s\n", a[i]);
   }
   printf ("\n");
}
 
/* fonction utilisateur de comparaison fournie a qsort() */
static int compare (void const *a, void const *b)
{
   /* definir des pointeurs type's et initialise's
      avec les parametres */
   char const *const *pa = a;
   char const *const *pb = b;
 
   /* evaluer et retourner l'etat de l'evaluation (tri croissant) */
   return strcmp (*pa, *pb);
}
 
int main (void)
{
/* tableau a trier (tableau de pointeurs sur char const) */
   char const *tab[] = { "world", "hello", "wild" };
 
/* affichage du tableau dans l'etat */
   aff (tab, sizeof tab / sizeof *tab);
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, compare);
 
/* affichage du tableau apres le tri */
   aff (tab, sizeof tab / sizeof *tab);
 
   return 0;
}

malgr tout, le tri a eu lieu :

 
Sélectionnez
world
hello
wild
 
hello
wild
world
 
 
Press ENTER to continue.

XXXVIII-B-3. Tri d'un tableau de structures

Soit une structure comprenant {nom, prenom, age}. On cre un tableau de 5 lments de ce type que l'on remplit " la main", puis, on fait un tri par prenom, par age dcroissant, puis par age croissant.

 
Sélectionnez
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct fiche
{
   char nom[11];
   char prenom[11];
   int age;
};
 
/* affichage du tableau */
static void aff (struct fiche const *a, size_t n)
{
   size_t i;
   for (i = 0; i < n; i++)
   {
      /* pointeur intermediaire pour alleger l'ecriture */
      struct fiche const *p = a + i;
      printf ("%-10s %-10s %d ans\n", p->nom, p->prenom, p->age);
   }
   printf ("\n");
}
 
/* fonction utilisateur de comparaison fournie a qsort() */
static int compare_prenom (void const *a, void const *b)
{
   /* definir des pointeurs type's et initialise's
      avec les parametres */
   struct fiche const *pa = a;
   struct fiche const *pb = b;
 
   /* evaluer et retourner l'etat de l'evaluation (tri croissant) */
   return strcmp (pa->prenom, pb->prenom);
}
 
/* fonction utilisateur de comparaison fournie a qsort() */
static int compare_age (void const *a, void const *b)
{
   struct fiche const *pa = a;
   struct fiche const *pb = b;
 
   return pa->age - pb->age;
}
 
/* fonction utilisateur de comparaison fournie a qsort() */
static int compare_age_dec (void const *a, void const *b)
{
   struct fiche const *pa = a;
   struct fiche const *pb = b;
 
   return pb->age - pa->age;
}
 
int main (void)
{
/* tableau a trier (tableau de pointeurs sur char const) */
   struct fiche tab[] = {
      {"Simpson", "Homer", 36},
      {"Bouvier", "Marge", 34},
      {"Simpson", "Bart", 10},
      {"Simpson", "Lisa", 8},
      {"Simpson", "Maggie", 2},
   };
 
/* affichage du tableau dans l'etat */
   aff (tab, sizeof tab / sizeof *tab);
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, compare_prenom);
 
/* affichage du tableau apres le tri */
   aff (tab, sizeof tab / sizeof *tab);
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, compare_age);
 
/* affichage du tableau apres le tri */
   aff (tab, sizeof tab / sizeof *tab);
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, compare_age_dec);
 
/* affichage du tableau apres le tri */
   aff (tab, sizeof tab / sizeof *tab);
 
   return 0;
}
 
Sélectionnez
Simpson    Homer      36 ans
Bouvier    Marge      34 ans
Simpson    Bart       10 ans
Simpson    Lisa       8 ans
Simpson    Maggie     2 ans
 
Simpson    Bart       10 ans
Simpson    Homer      36 ans
Simpson    Lisa       8 ans
Simpson    Maggie     2 ans
Bouvier    Marge      34 ans
 
Simpson    Maggie     2 ans
Simpson    Lisa       8 ans
Simpson    Bart       10 ans
Bouvier    Marge      34 ans
Simpson    Homer      36 ans
 
Simpson    Homer      36 ans
Bouvier    Marge      34 ans
Simpson    Bart       10 ans
Simpson    Lisa       8 ans
Simpson    Maggie     2 ans
 
 
Press ENTER to continue.

XXXVIII-B-4. Tri de nombres rels

Pour trier les nombres rels (float, double), il y a quelques prcautions prendre, car la notion d'galit n'existe pas. Il faut travailler par plages :

 
Sélectionnez
#include <stdio.h>
#include <time.h>
 
double random_range (double min, double max)
{
   return min + ((max - min) * (rand () / (double) RAND_MAX));
}
 
static int cmp (void const *a, void const *b)
{
   int ret = 0;
   double const *pa = a;
   double const *pb = b;
   double diff = *pa - *pb;
   if (diff > 0)
   {
      ret = 1;
   }
   else if (diff < 0)
   {
      ret = -1;
   }
   else
   {
      ret = 0;
   }
 
   return ret;
}
 
int main (void)
{
   double tab[100];
   int i;
   srand ((int) time (NULL));
   for (i = 0; i < sizeof tab / sizeof *tab; i++)
   {
      tab[i] = random_range (0, 1);
   }
 
   qsort (tab, sizeof tab / sizeof *tab, sizeof *tab, cmp);
 
   for (i = 0; i < sizeof tab / sizeof *tab; i++)
   {
      printf ("%8.4f", tab[i]);
   }
 
   return 0;
}

XXXIX. Les identificateurs rservs

Le document de dfinition du langage C a rserv un certain nombre d'identificateurs rservs. Ils peuvent tre utiliss par les implmenteurs (ceux qui crivent les compilateurs) ou pour des extensions du langage.

Ces identificateurs peuvent apparatre dans les interfaces publiques ou pour raliser certaines parties des fichiers d'entte (macros, paramtres...). Ils sont choisis de faon ne pas interfrer avec les identificateurs des utilisateurs, sous rserve, bien sr, que ceux-ci ne les utilisent pas, d'o l'intrt de cet article.

Les identificateurs rservs sont

 
Sélectionnez
 +---------------------+----------------+---------------------+----------------+
 | Les identificateurs |                | Exemple             | Exemple        |
 | commenant par      | suivi de       | valide              | Reserv        |
 +---------------------+----------------+---------------------+----------------+
 | "_"                 | "_A-Z"         | _123                | _ABC           |
 +---------------------+----------------+---------------------+----------------+
 | "is"                | "a-z"          | is_abc              | isabc          |
 +---------------------+----------------+---------------------+----------------+
 | "mem"               | "a-z"          |                     |                |
 +---------------------+----------------+---------------------+----------------+
 | "str"               | "a-z"          |                     |                |
 +---------------------+----------------+---------------------+----------------+
 | "to"                | "a-z           |                     |                |
 +---------------------+----------------+---------------------+----------------+
 | "wcs"               | "a-z"          |                     |                |
 +---------------------+----------------+---------------------+----------------+
 | "E"                 | "A-Z" ou "0-9" | Eabc                | E123 EABC      |
 +---------------------+----------------+---------------------+----------------+
 | "LC_"               | "A-Z"          | LC_abc LC_123 LCABC | LC_ABC         |
 +---------------------+----------------+---------------------+----------------+
 | "SIG"               | "_A-Z"         | SIGabc              | SIGABC SIG_ABC |
 +---------------------+----------------+---------------------+----------------+

XL. Bien grer la porte des objets et des fonctions

Le langage C offre par nature un contrle assez fin de la porte des objets et des fonctions. Cette caractristique est souvent mal connue, pourtant elle apporte un bnfice certain, notamment sur le plan de l'organisation du code (conception dtaille).

XL-A. Fonctions

Par dfaut, la porte d'une fonction est globale.

 
Sélectionnez
int function (int a, char *b)
{
}

Elle est visible d'un autre module une simple dclaration:

 
Sélectionnez
int function ();

ou mieux, un prototype:

 
Sélectionnez
int function (int a, char *b);

Il est possible cependant de rduire la porte de la fonction l'unit de compilation dans laquelle elle a t dfinie, en ajoutant le qualificateur static.

 
Sélectionnez
static int function (int a, char *b)
{
}

Cette pratique, lorsqu'elle est possible, apporte diffrents avantages :

  • Une conomie d'identificateurs. La porte de celui-ci tant limite une unit de compilation, il est possible de le rutiliser pour une autre fonction qualifie 'static' dans une autre unit de compilation.
  • Une meilleure optimisation. Certains compilateurs sont capables d'"inliner" une telle fonction dans certaines conditions, ce qui diminue le temps d'excution au prix d'une augmentation de la taille (le code de la fonction est recopi autant de fois que ncessaire).
  • Une meilleure organisation du code. Etant donn que ces fonctions sont forcment appeles par une fonction de l'unit de compilation dans laquelle elles ont t dfinies, le code se trouve naturellement organis en blocs fonctionnels cohrents. De plus, comme priori, ces fonctions n'ont pas besoin de prototypes spars, cela favorise une organisation du fichier source selon le principe 'Dfinir avant d'utiliser' (Top-down)

On vitera cependant de multiplier les codes identiques, et les principes de factorisation du code restent en vigueur.

 
Sélectionnez
+--------------------+    +--------------------+
| Bloc fonctionnel A |    | Bloc Fonctionnel B |
+--------------------+    +--------------------+
               |               |
               v               v
             +--------------------+
             |       Outils       |
             +--------------------+

XL-B. Objets

La porte d'un objet est rgie selon plusieurs critres.

XL-B-1. Dfinition hors d'un bloc

La porte par dfaut est globale. Elle peut tre rduite l'unit de compilation en ajoutant le qualificateur static.

XL-B-2. Dfinition dans un bloc

Si deux objets ont le mme nom, l'objet de porte infrieure masque les objets de porte suprieure. Pour cette raison, qui entraine un comportement confus, on vite de donner le mme nom des objets dont les portes sont imbriques.

La porte est celle du bloc et des blocs inclus.

XL-B-3. Masquage (Shadowing)

 
Sélectionnez
/* objet de portee globale */
int x;
 
int f (void)
{
   /* la globale x est masque'e par une locale du meme nom */
   int x = 0;
 
   /* la globale x n'est pas modifie'e. */
   x++;
}
 
int main (void)
{
   /* la globale x est modifie'e */
   x = 2;
 
   f();
 
   /* la globale x vaut toujours 2 */
 
   return 0;
}

XLI. Du bon usage de assert()

assert() est une macro qui permet de 'poser un pige'. On s'en sert en phase de mise au point pour vrifier si la conception et la ralisation sont correctes.

Le paramtre est une expression. Si elle retourne 0 (expression fausse), le programme s'arrte et un message indiquant le lieu et la cause est affich.

Il est d'usage qu'en mode production (release), la macro globale NDEBUG soit dfinie, ce qui fait que les macros assert(), bien que toujours prsentes dans le source, ne gnrent plus aucun code de vrification. En consquence, cette macro ne doit videmment pas tre utilise pour dtecter des erreurs d'utilisation ou de systme.

XLI-A. Exemple d'utilisation

 
Sélectionnez
#include <stdio.h>
 
static void afficher (int const t[], size_t n)
{
   size_t i;
   for (i = 0; i <= n; i++)
   {
      printf ("M", t[i]);
   }
   printf ("\n");
}
 
 
int main (void)
{
   int tab[] = {1, 2, 3, 4};
 
   afficher (tab, sizeof tab / sizeof *tab);
   return 0;
}

Ce code parait correct, mais l'excution, on constate :

 
Sélectionnez
1   2   3   4   2
 
Press ENTER to continue.

Pour vrifier le comportement, je pose un pige qui vrifie la validit de l'index.

  • il doit tre >=0 (toujours vrai, vu le type size_t)
  • il doit tre < n

Je vais donc ajouter un pige :

 
Sélectionnez
assert (i < n);

Avant l'accs en lecture au tableau.

Pour tre valide, la conception du pige doit se faire sans lire le code tester, mais en se basant uniquement sur l'interface et le comportement prsum.

 
Sélectionnez
#include <stdio.h>
#include <assert.h>
 
static void afficher (int const t[], size_t n)
{
   size_t i;
   for (i = 0; i <= n; i++)
   {
      /* ajout du piege */
      assert (i < n);
      printf ("M", t[i]);
   }
   printf ("\n");
}
 
int main (void)
{
   int tab[] = {1, 2, 3, 4};
 
   afficher (tab, sizeof tab / sizeof *tab);
   return 0;
}

Ce qui provoque bien sr :

 
Sélectionnez
1   2   3   4Assertion failed: i < n, file main.c, line 10
 
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
 
Press ENTER to continue.

Ce qui signifie que i a dpass la valeur maximale qu'autorise le langage C. La cause est videmment le <= au lieu de < dans l'expression du for(), ce qui entraine une action corrective immdiate :

 
Sélectionnez
#include <stdio.h>
#include <assert.h>
 
static void afficher (int const t[], size_t n)
{
   size_t i;
   /* correction */
   for (i = 0; i < n; i++)
   {
      /* ajout du piege */
      assert (i < n);
      printf ("M", t[i]);
   }
   printf ("\n");
}
 
int main (void)
{
   int tab[] = {1, 2, 3, 4};
 
   afficher (tab, sizeof tab / sizeof *tab);
   return 0;
}

L'excution et, prsent, conforme aux attentes :

 
Sélectionnez
1   2   3   4
 
Press ENTER to continue.

XLII. Comportement indfini

Le langage C est dfini par un document unique et reconnu sur le plan international (ISO) par tous les intervenants, que ce soit les dveloppeurs de compilateurs (les 'implmenteurs') les dveloppeurs d'applications (les 'utilisateurs') ou les diffrents formateurs.

Ce document de rfrence dfinit un certain nombre d'lments (obligations, interdictions).

Les autres lments sont soit laisss l'apprciation des implmenteurs (implementation defined ou dfini par l'implmentation) qui doivent accompagner leur production (compilateur etc.) d'un document prcisant les comportement de tel ou tels lments, soit non dfinis du tout. Dans ce dernier cas, le comportement est dit indfini ou indtermin. (Undefined Behaviour ou UB)

Quelques exemples :

 
Sélectionnez
#include <stdio.h>
 
int main (void)
{
   int i = 0;
 
   printf ("i = %d\n", i);
   return 0;
}

Ce code est conforme la spcification du langage, aucune zone n'a t laisse dans l'ombre. Le comportement est dtermin. Il est garanti d'crire

 
Sélectionnez
i = 0

Par contre, voici 2 cas de comportement indtermin :

  • Absence de prototype pour printf()
 
Sélectionnez
int main (void)
{
   int i = 0;
 
   printf ("i = %d\n", i);
   return 0;
}
  • Lecture d'une valeur non initialise
 
Sélectionnez
#include <stdio.h>
 
int main (void)
{
   int i;
 
   printf ("i = %d\n", i);
   return 0;
}

Les consquences d'un UB ne sont pas prvisibles. En effet, a va du crash au comportement d'apparence conforme. Il est donc impossible de compter sur la simple vrification du comportement pour garantir qu'un code est correct. Il faut avant tout qu'il soit exempt de tout UB.

Le compilateur et ses warnings (ou un outil d'analyse spcialis comme Lint) peut nous aider dbusquer certains UB. Ici, il est probable qu'une 'utilisation de variable non initialise' ou qu'un 'appel de fonction sans prototypes' soient detects (mais a dpend du compilateur et de ses reglages). Mais il est des cas o le compilateur ne voit rien. Le seul recours est alors l'oeil exerc du programmeur expriment.

La chasse aux UB est donc ouverte en permanence. C'est la principale source de bugs dans un programme C. Il convient donc, d'une part, de bien connaitre le langage et ses limites de dfinition et, d'autre part, d'tre extrmement vigilant lors de l'criture et de la relecture du code. Lorsqu'elle est possible, la relecture croise est une bonne mthode de dtection des UB.

Exercice : trouver le UB :

 
Sélectionnez
#include <stdio.h>
 
int main (void)
{
   int num = 12;
   char num_text[] = "";
 
   sprintf (num_text, "%d", num);
   printf ("Voici num_text : %s\n", num_text);
   return 0;
}

XLIII. Les item-lists

XLIII-A. Introduction

Qui n'a jamais t confront ce problme :

Comment faire le lien entre des constantes symboliques et leur reprsentation textuelle ?

Le but des item-lists est de rsoudre de faon la plus automatique possible ce genre de problme.

XLIII-B. Mise en oeuvre

Le principe est de sparer les informations de bases constantes (par exemple, la correspondance entre caractre et chaine morse) dans un fichier indpendant (ni.c, ni.h, on y reviendra), mais que l'on peut inclure (#include) de faon raliser une gnration automatique de code en fonction de la demande.

Je prends un exemple plus parlant.

Je veux crer une srie de constantes qui reprsentent des fruits

 
Sélectionnez
enum fruits
{ BANANE, ORANGE, POMME, FRAISE, KIWI };

Ca peut servir dfinir une masse de fruit :

 
Sélectionnez
struct dosage_fruit
{
   enum fruits fruit;
   int masse;
};

puis des recettes de fruits mixs :

 
Sélectionnez
struct dosage_fruit mix_energie[] =
{
   {BANANE, 100},
   {ORANGE, 150},
   {FRAISE, 80},
};
 
struct dosage_fruit mix_forme[] =
{
   {BANANE, 50},
   {ORANGE, 50},
   {POMME, 100},
   {KIWI, 50},
   {FRAISE, 50},
};
 
// etc.

Si on veut afficher la recette, il faut un moyen simple pour convertir la constante fruit en une chaine imprimable.

On va donc utiliser un tableau de chaines construit sur le mme modle que le enum (mme ordre, c'est primordial, car l'enum sert d'indice au tableau):

 
Sélectionnez
static char const *chaines_fruits[] =
{
   "banane",
   "orange",
   "pomme",
   "fraise",
   "kiwi",
};

ce qui permet maintenant d'afficher la composition :

 
Sélectionnez
void afficher_composition (char const *nom, struct dosage_fruit const *a,
						   size_t n)
{
   size_t i;
   printf ("%s\n", nom);
   for (i = 0; i &lt; n; i++)
   {
      struct dosage_fruit const *p = a + i;
      printf ("%d g de %s\n", p->masse, chaines_fruits[p->fruit]);
   }
   printf ("\n");
}

que l'on appelle comme ceci :

 
Sélectionnez
#define N(a) (sizeof(a) / sizeof *(a))
 
{
   afficher_composition ("Mix energie", mix_energie, N(mix_energie));
   afficher_composition ("Mix forme", mix_forme, N(mix_forme));
 
   etc.

Ce qui donne

 
Sélectionnez
Mix energie
100 g de banane
150 g de orange
80 g de fraise
 
Mix forme
50 g de banane
50 g de orange
100 g de pomme
50 g de kiwi
50 g de fraise
 
 
Press ENTER to continue.

(je laisse au programmeur malin le soin d'crire "d'orange" au lieu de "de orange"...)

Maintenant, patatras, nouvelle recette la carte :

 
Sélectionnez
struct dosage_fruit mix_tropical[] =
{
   {BANANE, 50},
   {ORANGE, 80},
   {MANGUE, 100},
   {ANANAS, 50},
};

Quelles sont les consquences sur le programme :

2 modifications :

  • l'enum :
     
    Sélectionnez
    enum fruits
    { BANANE, ORANGE, POMME, FRAISE, KIWI, MANGUE, ANANAS };
    
  • la liste des chaines 'associes' (manuellement, pour le moment)
     
    Sélectionnez
    static char const *chaines_fruits[] =
    {
       "banane",
       "orange",
       "pomme",
       "fraise",
       "kiwi",
       "mangue",
       "ananas",
    };
    

Si j'inverse ou que j'en oubli une, c'est la catastrophe. Idem, et c'est beaucoup plus sournois, si j'oublie une ','.

Donc, aprs quelques sueurs froides, a marche, et on obtient bien :

 
Sélectionnez
Mix energie
100 g de banane
150 g de orange
80 g de fraise
 
Mix forme
50 g de banane
50 g de orange
100 g de pomme
50 g de kiwi
50 g de fraise
 
Mix tropical
50 g de banane
80 g de orange
100 g de mangue
50 g de ananas
 
 
Press ENTER to continue.

Mais c'est dj beaucoup de stress dans un petit programme comme a. Dans l'industrie, la moyenne, c'est 1 000 000 de lignes... Pas question de se stresser comme a si, pour ajouter une valeur dans la liste, il faut modifier dans 10 fichiers diffrents... (Lesquels ? On n'est pas des robots...)

Par contre, on peut utiliser des techniques de programmations qui font que la maintenance est centralise en un seul fichier. On le modifie, on recompile tout le projet, et les modifications sont automatiquement reportes dans tout le code.

Pour a, on va faire travailler la machine partir d'un fichier unique, pour qu'elle produise ce qu'on veut. C'est toute la puissance qu'offre le prprocesseur bien maitris.

Je vais montrer les diffrentes tapes pour expliquer le principe, mais dans la pratique, on ne fait que la dernire videmment.

Dans notre exemple, Les deux lments "synchroniser" sont :

 
Sélectionnez
enum fruits
{ BANANE, ORANGE, POMME, FRAISE, KIWI, MANGUE, ANANAS };

et la liste des chaines 'associes' (manuellement, pour le moment)

 
Sélectionnez
static char const *chaines_fruits[] =
{
   "banane",
   "orange",
   "pomme",
   "fraise",
   "kiwi",
   "mangue",
   "ananas",
};

Si on observe bien ces deux lments, on constate qu'ils se ressemblent. Pour tre plus parlant, on va les rorganiser en colonnes :

 
Sélectionnez
enum fruits
{
   BANANE,
   ORANGE,
   POMME,
   FRAISE,
   KIWI,
   MANGUE,
   ANANAS
};

et la liste des chaines 'associes' (manuellement, pour le moment)

 
Sélectionnez
static char const *chaines_fruits[] =
{
   "banane",
   "orange",
   "pomme",
   "fraise",
   "kiwi",
   "mangue",
   "ananas",
};

On voit que le schma est similaire :

  • une entte
  • une liste (chaque lment est termin par une ,)
  • une fin particulire.

On voit que la liste des enum prsente une petite irrgularit : le dernier lment n'est pas termin par une ','. C'est normal (et obligatoire) en C90 (en C99, la virgule est accepte).

Petite astuce : on ajoute un lment la liste, qui sert terminer la liste sans virgule. Il ne fait pas partie de la liste. C'est soit un 'dummy' (inutile), soit, comme ici o les valeurs sont automatiques, une constante qui exprime le nombre d'lments de la liste, ce qui, priori, n'est pas compltement inutile....

Modification :

 
Sélectionnez
enum fruits
{
   BANANE,
   ORANGE,
   POMME,
   FRAISE,
   KIWI,
   MANGUE,
   ANANAS,
   NB_FRUITS
};

Afin de faciliter la maintenance, il faudrait disposer d'une liste 'double' comme ceci :

 
Sélectionnez
BANANE "banane"
ORANGE "orange"
POMME  "pomme"
FRAISE "fraise"
KIWI   "kiwi"
MANGUE "mangue"
ANANAS "ananas"

Comment faire comprendre a un programme C ?

C'est la qu'intervient la puissance du prprocesseur.

Il suffit d'crire une liste de macros avec deux paramtres. Chaque macro reprsentant un groupe cohrent d'informations ou 'item' :

 
Sélectionnez
ITEM (BANANE, "banane")
ITEM (ORANGE, "orange")
ITEM (POMME , "pomme" )
ITEM (FRAISE, "fraise")
ITEM (KIWI  , "kiwi"  )
ITEM (MANGUE, "mangue")
ITEM (ANANAS, "ananas")

Il suffit ensuite d'crire la dfinition de ITEM qui correspond l'usage qu'on en fait :

 
Sélectionnez
enum fruits
{
#define ITEM(id, chaine)\
   id,
 
ITEM (BANANE, "banane")
ITEM (ORANGE, "orange")
ITEM (POMME , "pomme" )
ITEM (FRAISE, "fraise")
ITEM (KIWI  , "kiwi"  )
ITEM (MANGUE, "mangue")
ITEM (ANANAS, "ananas")
 
#undef ITEM
 
   NB_FRUITS
};

ce qui va produire automatiquement la bonne liste.

On fait pareil avec l'autre liste :

 
Sélectionnez
static char const *chaines_fruits[] =
{
#define ITEM(id, chaine)\
   chaine,
 
ITEM (BANANE, "banane")
ITEM (ORANGE, "orange")
ITEM (POMME , "pomme" )
ITEM (FRAISE, "fraise")
ITEM (KIWI  , "kiwi"  )
ITEM (MANGUE, "mangue")
ITEM (ANANAS, "ananas")
 
#undef ITEM
};

Le #undef permet le 'recyclage' du nom de la macro qui est invariablement ITEM.

L'tape ultime consiste inclure la liste partir d'un fichier exterieur auquel je donne l'extension .itm (par exemple : fruits.itm semble appropri).

Je conseille de placer un commentaire dans ce fichier qui rappelle son nom et le dbut de la dfinition de la macro avec la signification des champs.

 
Sélectionnez
/* fruits.itm
 
#define ITEM(id, chaine)\
 
*/
ITEM (BANANE, "banane")
ITEM (ORANGE, "orange")
ITEM (POMME , "pomme" )
ITEM (FRAISE, "fraise")
ITEM (KIWI  , "kiwi"  )
ITEM (MANGUE, "mangue")
ITEM (ANANAS, "ananas")

La maintenance de ce fichier est extrmement simple. Elle est "visuelle".

Les deux dfinitions deviennent alors :

 
Sélectionnez
enum fruits
{
#define ITEM(id, chaine)\
   id,
#include "fruits.itm"
#undef ITEM
 
   NB_FRUITS
};

et

 
Sélectionnez
static char const *chaines_fruits[] =
{
#define ITEM(id, chaine)\
   chaine,
#include "fruits.itm"
#undef ITEM
};

Ce qui allge considrablement le code source et rend la maintenance automatique. (Le C, c'est Bien)

Il peut y avoir des centaines de lignes dans un .itm. Idem pour le nombre de champs. Ici, il y en a 2 champs, mais on aurait pu en avoir qu'un seul. En effet, une macro sait transformer un paramtre symbolique (ORANGE) en une chaine ("ORANGE") avec #. Mais elle ne sait pas modifier la casse des caractres, d'o mon choix de mettre 2 champs.

Exemples de fichiers itm que j'utilise

(Simple) : gestion des erreurs

(Complexe) : tables de caractres

Je laisse au lecteur le soin d'crire l'ensemble de l'exemple et de faire les tests ncessaires. Tous les lments sont l.


prcdentsommairesuivant

Copyright © 2009 Emmanuel Delahaye. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.