cours_C by MeriemeBenhakki

VIEWS: 0 PAGES: 129

									Universit´ du Luxembourg
         e                                           2005–2006




                    Cours de programmation avanc´e.
                                                e

                                             Le langage C



 e
S´bastien Varrette <Sebastien.Varrette@imag.fr>     Version : 0.4
Nicolas Bernard    <n.bernard@lafraze.net>
2
              e
Table des mati`res

1 Les bases                                                                                                   2
                          e
  1.1 Un langage compil´ . . . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .    2
               e e     e
      1.1.1 G´n´ralit´s sur les langages de programmation                            .   .   .   .   .   .    2
                                           e
      1.1.2 Le C comme langage compil´ . . . . . . . . . .                           .   .   .   .   .   .    3
      1.1.3 Notes sur la normalisation du langage C . . . .                          .   .   .   .   .   .    4
      1.1.4 C en pratique : compilation et debuggage . . .                           .   .   .   .   .   .    4
                 e
  1.2 Les mots-cl´s . . . . . . . . . . . . . . . . . . . . . . .                    .   .   .   .   .   .    5
  1.3 Les commentaires . . . . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .    6
                 e e
  1.4 Structure g´n´rale d’un programme C . . . . . . . . .                          .   .   .   .   .   .    7
  1.5 Notion d’identificateur . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .    9
                     e
  1.6 Conventions d’´critures d’un programme C . . . . . .                           .   .   .   .   .   .    9
  1.7 Les types de base . . . . . . . . . . . . . . . . . . . . .                    .   .   .   .   .   .   10
                        e
      1.7.1 Les caract`res . . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .   10
      1.7.2 Les entiers . . . . . . . . . . . . . . . . . . . .                      .   .   .   .   .   .   11
      1.7.3 Les flottants . . . . . . . . . . . . . . . . . . .                       .   .   .   .   .   .   13
      1.7.4 Le type void . . . . . . . . . . . . . . . . . . .                       .   .   .   .   .   .   14

2 La syntaxe du langage                                                                                      15
                         e
  2.1 Expressions et Op´rateurs . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
                e                e
      2.1.1 Op´rateurs arithm´tiques . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
                e
      2.1.2 Op´rateurs d’affectation . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   16
                e
      2.1.3 Op´rateurs relationnels . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   16
                e
      2.1.4 Op´rateurs logiques . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   17
                e             `
      2.1.5 Op´rateurs bit a bit . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   18
                e               e a       e
      2.1.6 Op´rateurs d’acc`s ` la m´moire          .   .   .   .   .   .   .   .   .   .   .   .   .   .   18
                        e
      2.1.7 Autres op´rateurs . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
                              o
  2.2 Les structures de contrˆle . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
      2.2.1 Instruction if...else . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
      2.2.2 Instruction for . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
      2.2.3 Instruction while . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
      2.2.4 Instruction do...while . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
      2.2.5 Instruction switch . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
      2.2.6 Instruction goto . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
      2.2.7 Instruction break . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
      2.2.8 Instruction continue . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   24
           e       e
  2.3 La r´cursivit´ . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   24

                                       3
   2.4   Les conversions de types . . . . . . . . . . . . . . . . . .               .   .   .   .   .   25
         2.4.1 Les situations de la conversion de type . . . . . .                  .   .   .   .   .   25
                     e
         2.4.2 La r`gle de ”promotion des entiers” . . . . . . . .                  .   .   .   .   .   26
                                        e
         2.4.3 Les conversions arithm´tiques habituelles . . . .                    .   .   .   .   .   26
         2.4.4 Les surprises de la conversion de type . . . . . .                   .   .   .   .   .   27
   2.5                               e
         Principales fonctions d’entr´es-sorties standard . . . . .                 .   .   .   .   .   27
         2.5.1 La fonction getchar . . . . . . . . . . . . . . . .                  .   .   .   .   .   28
         2.5.2 La fonction putchar . . . . . . . . . . . . . . . .                  .   .   .   .   .   28
         2.5.3 La fonction puts . . . . . . . . . . . . . . . . . .                 .   .   .   .   .   28
                               e       a e                e
         2.5.4 La fonction d’´criture ` l’´cran formatt´e printf                    .   .   .   .   .   29
         2.5.5 La fonction de saisie scanf . . . . . . . . . . . .                  .   .   .   .   .   31

3 Les pointeurs                                                                                         33
        e
  3.1 D´claration d’un pointeur . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   34
          e
  3.2 Op´rateurs de manipulation des pointeurs .        .   .   .   .   .   .   .   .   .   .   .   .   34
                   e
      3.2.1 L’op´rateur ’adresse de’ & . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   34
                   e
      3.2.2 L’op´rateur ’contenu de’ : * . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   35
  3.3 Initialisation d’un pointeur . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   36
               e
  3.4 Arithm´tique des pointeurs . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   37
                                  e
  3.5 Allocation dynamique de m´moire . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   38
          e
  3.6 Lib´ration dynamique avec la fonction free        .   .   .   .   .   .   .   .   .   .   .   .   40

              e e
4 Les types d´riv´s                                                                                     41
            e   e
  4.1 Les ´num´rations . . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   41
  4.2 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   41
       4.2.1 Initialisation d’un tableau . . . . . . . . . . . . .                  .   .   .   .   .   42
       4.2.2 Tableaux multidimensionnels . . . . . . . . . . .                      .   .   .   .   .   44
                                             e
       4.2.3 Passage de tableau en param`tre . . . . . . . . .                      .   .   .   .   .   45
       4.2.4 Relation entre tableaux et pointeurs . . . . . . .                     .   .   .   .   .   47
       4.2.5 Cas des tableaux de chaˆ                  e
                                        ınes de caract`res . . . .                  .   .   .   .   .   49
       4.2.6 Gestion des arguments de la ligne de commande                          .   .   .   .   .   50
  4.3 Les structures . . . . . . . . . . . . . . . . . . . . . . . .                .   .   .   .   .   51
       4.3.1 Initialisation et affectation d’une structure . . . .                   .   .   .   .   .   52
       4.3.2 Comparaison de structures . . . . . . . . . . . .                      .   .   .   .   .   52
       4.3.3 Tableau de structures . . . . . . . . . . . . . . .                    .   .   .   .   .   52
       4.3.4 Pointeur vers une structure . . . . . . . . . . . .                    .   .   .   .   .   53
                                eee
       4.3.5 Structures auto-r´f´r´es . . . . . . . . . . . . . .                   .   .   .   .   .   53
  4.4 Les unions . . . . . . . . . . . . . . . . . . . . . . . . . .                .   .   .   .   .   55
               e
       4.4.1 D´claration d’une union . . . . . . . . . . . . . .                    .   .   .   .   .   55
       4.4.2 Utilisation pratique des unions . . . . . . . . . .                    .   .   .   .   .   56
                     e               e          e
       4.4.3 Une m´thode pour all´ger l’acc`s aux membres .                         .   .   .   .   .   57
  4.5 Les champs de bits . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   57
         e
  4.6 D´finition de synonymes de types avec typedef . . . . .                        .   .   .   .   .   58

                                        4
5 Retour sur les fonctions                                                                              59
        e             e
  5.1 D´claration et d´finition d’une fonction . .       .   .   .   .   .   .   .   .   .   .   .   .   59
  5.2 Appel d’une fonction . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   61
          e
  5.3 Dur´e de vie des identificateurs . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   61
           e
  5.4 Port´e des variables . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   62
      5.4.1 Variables globales . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   62
      5.4.2 Variables locales . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   63
        e       e
  5.5 R´cursivit´ . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   64
                        e     a
  5.6 Passage de param`tres ` une fonction . . .        .   .   .   .   .   .   .   .   .   .   .   .   64
               e e    e
      5.6.1 G´n´ralit´s . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   64
                                 e
      5.6.2 Passage des param`tres par valeur .         .   .   .   .   .   .   .   .   .   .   .   .   65
                                 e
      5.6.3 Passage des param`tres par adresse .        .   .   .   .   .   .   .   .   .   .   .   .   65
                                            e
      5.6.4 Passage de tableau en param`tre . .         .   .   .   .   .   .   .   .   .   .   .   .   66
               a                              e
  5.7 Fonction ` nombre variable de param`tres .        .   .   .   .   .   .   .   .   .   .   .   .   67
  5.8 Pointeurs sur une fonction . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   69

6 Gestion des fichiers                                                                                   71
  6.1 Ouverture et fermeture de fichiers . . . . . . . . . . . . . . . . . .                             71
      6.1.1 Ouverture de fichiers : la fonction fopen . . . . . . . . . .                                71
      6.1.2 Fermeture de fichiers : la fonction fclose . . . . . . . . .                                 73
              e                 e
  6.2 Les entr´es-sorties format´es . . . . . . . . . . . . . . . . . . . . .                           73
                             e
      6.2.1 La fonction d’´criture en fichier fprintf . . . . . . . . . . .                              73
      6.2.2 La fonction de saisie en fichier fscanf . . . . . . . . . . .                                73
                                      e
  6.3 Impression et lecture de caract`res dans un fichier . . . . . . . .                                74
                         e                   e
      6.3.1 Lecture et ´criture par caract`re : fgetc et fputc . . . .                                  74
                         e                e              e
      6.3.2 Lecture et ´criture optimis´es par caract`re : getc et putc                                 74
                                   e
      6.3.3 Relecture d’un caract`re . . . . . . . . . . . . . . . . . . .                              75
                      e
      6.3.4 Les entr´es-sorties binaires : fread et fwrite . . . . . . .                                76
      6.3.5 Positionnement dans un fichier : fseek, rewind et ftell                                      77

                       e
7 Les directives du pr´processeur                                                                       79
  7.1 La directive #include . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   79
  7.2 La directive #define . . . . . . . . . . . . . . . .          .   .   .   .   .   .   .   .   .   79
               e
      7.2.1 D´finition de constantes symboliques . . .               .   .   .   .   .   .   .   .   .   80
                                     e
      7.2.2 Les macros avec param`tres . . . . . . . .              .   .   .   .   .   .   .   .   .   80
  7.3 La compilation conditionnelle . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   81
                         e `
      7.3.1 Condition li´e a la valeur d’une expression             .   .   .   .   .   .   .   .   .   82
                         e `
      7.3.2 Condition li´e a l’existence d’un symbole               .   .   .   .   .   .   .   .   .   82
                  e
      7.3.3 L’op´rateur defined . . . . . . . . . . . .             .   .   .   .   .   .   .   .   .   83
      7.3.4 La commande #error . . . . . . . . . . .                .   .   .   .   .   .   .   .   .   83
      7.3.5 La commande #pragma . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   84

8 La programmation modulaire                                                                            85
                 ee
  8.1 Principes ´l´mentaires . . . . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   85
  8.2 Eviter les erreurs d’inclusions multiples . . . .         .   .   .   .   .   .   .   .   .   .   87
                       e e
  8.3 La compilation s´par´e . . . . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   88
       e     e      e
  8.4 R´sum´ des r`gles de programmation modulaire              .   .   .   .   .   .   .   .   .   .   88

                                       5
   8.5   L’outils ’make’ . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   89
         8.5.1 Principe de base . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   89
                    e
         8.5.2 Cr´ation d’un Makefile . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   89
         8.5.3 Utilisation de macros et de variables         .   .   .   .   .   .   .   .   .   .   .   .   90

               e
9 La biblioth`que standard                                                                                    93
  9.1 Diagnostics d’erreurs <assert.h> . . . . . . . . . . . . . . . . . .                                    93
  9.2 Gestion des nombres complexes <complex.h> . . . . . . . . . . .                                         94
                                e
  9.3 Classification de caract`res et changements de casse <ctype.h> .                                         94
  9.4 Valeur du dernier signal d’erreur <errno.h> . . . . . . . . . . . .                                     95
                                       a
  9.5 Gestion d’un environnement ` virgule flottante <fenv.h> . . . .                                          95
       9.5.1 Gestion des exceptions . . . . . . . . . . . . . . . . . . . .                                   95
       9.5.2 Gestion des arrondis . . . . . . . . . . . . . . . . . . . . .                                   96
       9.5.3 Gestion des environnements en virgule flottante. . . . . .                                        96
                       e
  9.6 Intervalle et pr´cision des nombres flottants <float.h> . . . . . .                                      96
         e                                       e
  9.7 D´finitions de types entiers de taille fix´e <inttypes.h> . . . . .                                       96
                  e
  9.8 Alias d’op´rateurs logiques et binaires <iso646.h> . . . . . . . .                                      97
  9.9 Intervalle de valeur des types entiers <limits.h> . . . . . . . . .                                     97
  9.10 Gestion de l’environnement local <locale.h> . . . . . . . . . . .                                      97
                           e
  9.11 Les fonctions math´matiques de <math.h> . . . . . . . . . . . . .                                      98
                                   e
       9.11.1 Fonctions trigonom´triques et hyperboliques . . . . . . . .                                     98
       9.11.2 Fonctions exponentielles et logarithmiques . . . . . . . . .                                    98
       9.11.3 Fonctions diverses . . . . . . . . . . . . . . . . . . . . . .                                  98
  9.12 Branchements non locaux <setjmp.h> . . . . . . . . . . . . . . .                                       98
  9.13 Manipulation des signaux <signal.h> . . . . . . . . . . . . . . .                                      99
                                   e
  9.14 Nombre variable de param`tres <stdarg.h> . . . . . . . . . . . .                                       99
         e                      e
  9.15 D´finition du type bool´en <stdbool.h> . . . . . . . . . . . . . .                                      99
         e
  9.16 D´finitions standards <stddef.h> . . . . . . . . . . . . . . . . . .                                    99
         e
  9.17 D´finitions de types entiers <stdint.h> . . . . . . . . . . . . . .                                    100
            e
  9.18 Entr´es-sorties <stdio.h> . . . . . . . . . . . . . . . . . . . . . .                                 100
       9.18.1 Manipulation de fichiers . . . . . . . . . . . . . . . . . . .                                  100
                    e                    e
       9.18.2 Entr´es et sorties format´es . . . . . . . . . . . . . . . . .                                 100
                                               e
       9.18.3 Impression et lecture de caract`res . . . . . . . . . . . . .                                  100
  9.19 Utilitaires divers <stdlib.h> . . . . . . . . . . . . . . . . . . . .                                 101
       9.19.1 Allocation dynamique . . . . . . . . . . . . . . . . . . . .                                   101
       9.19.2 Conversion de chaˆ                 e
                                  ınes de caract`res en nombres . . . . . .                                  101
                 e e                               e
       9.19.3 G´n´ration de nombres pseudo-al´atoires . . . . . . . . .                                      101
                       e
       9.19.4 Arithm´tique sur les entiers . . . . . . . . . . . . . . . . .                                 102
       9.19.5 Recherche et tri . . . . . . . . . . . . . . . . . . . . . . . .                               102
       9.19.6 Communication avec l’environnement . . . . . . . . . . .                                       102
  9.20 Manipulation de chaˆ                e
                             ınes de caract`res <string.h> . . . . . . . .                                   103
                 e e                                 e
  9.21 Macros g´n´riques pour les fonctions math´matiques <tgmath.h>                                         103
  9.22 Date et heure <time.h> . . . . . . . . . . . . . . . . . . . . . . .                                  104
                                e    e
  9.23 Manipulation de caract`res ´tendus <wchar.h> et <wctype.h> .                                          104

                                          6
A Etude de quelques exemples                                                106
  A.1 Gestion des arguments de la ligne de commande . . . . . . . . . 106
                          ın´
  A.2 Exemple de liste chaˆ ee . . . . . . . . . . . . . . . . . . . . . . . 110

           e e
B Makefile g´n´rique                                                                               112

      e
C Le bˆtisier                                                                                     116
                         e
  C.1 Erreur avec les op´rateurs . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   . 116
      C.1.1 Erreur sur une comparaison . . . . . . .         .   .   .   .   .   .   .   .   .   . 116
      C.1.2 Erreur sur l’affectation . . . . . . . . . .      .   .   .   .   .   .   .   .   .   . 116
  C.2 Erreurs avec les macros . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   . 116
                                            e
      C.2.1 Un #define n’est pas une d´claration .           .   .   .   .   .   .   .   .   .   . 117
      C.2.2 Un #define n’est pas une initialisation          .   .   .   .   .   .   .   .   .   . 117
                                              e
      C.2.3 Erreur sur macro avec param`tres . . .           .   .   .   .   .   .   .   .   .   . 117
      C.2.4 Erreur avec les effets de bord . . . . . .        .   .   .   .   .   .   .   .   .   . 117
  C.3 Erreurs avec l’instruction if . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   . 117
  C.4 Erreurs avec les commentaires . . . . . . . . . .      .   .   .   .   .   .   .   .   .   . 118
                              e         e
  C.5 Erreurs avec les priorit´s des op´rateurs . . . .      .   .   .   .   .   .   .   .   .   . 118
  C.6 Erreur avec l’instruction switch . . . . . . . .       .   .   .   .   .   .   .   .   .   . 119
      C.6.1 Oubli du break . . . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   . 119
      C.6.2 Erreur sur le default . . . . . . . . . .        .   .   .   .   .   .   .   .   .   . 119
  C.7 Erreur sur les tableaux multidimensionnels . .         .   .   .   .   .   .   .   .   .   . 119
                                    e e
  C.8 Erreur avec la compilation s´par´e . . . . . . .       .   .   .   .   .   .   .   .   .   . 119
  C.9 Liens utiles . . . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   . 121




                                       7
Avant-propos

             e                e                                     e
Ce polycopi´ vient en compl´ment au cours de programmation avanc´e dispens´   e
                      e
au sein de l’universit´ du Luxembourg en DUT d’informatique. En aucun cas il
                     e                                                     e
ne dispense de la pr´sence en cours. Ce document n’a pas pour vocation d’ˆtre
                 ee
un ouvrage de r´f´rence du Langage C (pour cela, voir par exemple [KR88]). Il
                                              e
doit simplement permettre au lecteur d’appr´hender rapidement ce langage.
                                                         ee e         e
Pour la petite histoire, le langage de programmation C a ´t´ d´velopp´ dans les
     e
ann´es 70 par Dennis Ritchie aux laboratoires Bell. Il puise ses origines dans
le langage de programmation sans type BCPL (Basic Combined Programming
                     e                                    e
Language, developp´ par M. Richards) et dans B (developp´ par K. Thompson).
                                                                e
En 1978, Brian Kernighan et Dennis Ritchie produisent la premi`re description
officielle de C.
                         e
Le design de C lui conf`re un certain nombre d’avantages :
– Le code source est hautement portable
                            e
– Le code machine est tr`s efficace
                                                        e
– les compilateurs C sont disponibles dans tous les syst`mes courants.
La version courante de ce document ainsi que le code source des exemples
       e                   e
abord´s dans ce polycopi´ sont disponibles sur mon site
http://www-id.imag.fr/~svarrett/




                                      1
Chapitre 1

Les bases

1.1                      e
        Un langage compil´
1.1.1    e e     e
        G´n´ralit´s sur les langages de programmation
Dans le domaine de la programmation, on utilise aujourd’hui des langages de
                                                        e
programmation de haut niveau (i.e ”facilement” compr´hensibles par le pro-
grammeur) par opposition aux langages de bas niveau (de type assembleur) qui
                e
sont plus orient´s vers le langage machine. Parmi les exemples de langages de
haut niveau, on peut citer les langages C, C++, Java, Cobol etc...
        e           e             e          e
Il est n´anmoins n´cessaire d’op´rer une op´ration de traduction du langage
de haut niveau vers le langage machine (binaire). On distingue deux types de
traducteurs :
              e
  1. l’interpr´teur qui traduit les programmes instruction par instruction dans
                       e
     le cadre d’une int´raction continue avec l’utilisateur. Ainsi, le programme
                 `           e
     est traduit a chaque ex´cution.
  2. le compilateur qui traduit les programmes dans leur ensemble : tout le
                      e
     programme doit ˆtre fourni en bloc au compilateur pour la traduction. Il
     est traduit une seule fois.
       e
Il en r´sulte deux grands types de langages de programmation :
                       ee                     e
– les langages interpr´t´s : dans ce cas, l’ex´cution des programmes se fait sous
                  a      e                                    e     e
   un ”terminal” ` la vol´e. Ce type de langage est donc adapt´ au d´veloppement
                                               e
   rapide de prototypes (on peut tester imm´diatement ce que l’on est en train
   de faire) On citera par exemple les langages Scheme, Ruby, etc...
                       e
– les langages compil´s : les source du code est dans un premier temps pass´     e
                                                     e e                     e
   dans une ”moulinette” (le compilateur) qui va g´n´rer, sauf erreur, un ex´cu-
                                           e
   table qui est un fichier binaire compr´hensible par votre machine. Ce com-
                         e
   portement est illustr´ dans la figure 1.1. Ce type de langage est donc adapt´  e
   a     e
   ` la r´alisation d’applications plus efficaces ou de plus grande envergure (il y
                                                               e
   a une optimisation plus globale et la traduction est effectu´e une seule fois et
        a            e
   non ` chaque ex´cution).
                                  e
   En outre, un langage compil´ permet de diffuser les programmes sous forme
   binaire, sans pour autant imposer la diffusion le source du code, permettant

                                        2
                                            ee
  ainsi une certaine protection de la propri´t´ intellectuelle. Quelques exemples
                      e
  de langages compil´s : C, C++, Java etc...
                                          e    a                 e
A noter que certains langages peuvent ˆtre ` la fois compil´s et interpr´t´s,ee
comme par exemple CAML.
                   prog.c                                                a.out

                  void f(int a){}
                  int main(){
                     ...                  COMPILATION
                   }

             source du programme                                      executable binaire


                            Fig. 1.1 – Compilation d’un code source



1.1.2                                e
            Le C comme langage compil´
                                         e    e                       e
Le langage C est donc un langage compil´ (mˆme s’il existe des interpr´teurs
                  e                                       e
plus ou moins exp´rimentaux). Ainsi, un programme C est d´crit par un fichier
            e                                   e
texte, appel´ fichier source. La compilation se d´compose en fait en 4 phases
successives1 :
                             e
  1. Le traitement par le pr´processeur : le fichier source (portant l’extension
                   e          e
     .c) est analys´ par le pr´processeur qui effectue des transformations pure-
     ment textuelles (remplacement de chaˆ                 e
                                           ınes de caract`res, inclusion d’autres
                                                              e
     fichiers sources, compilation conditionnelle ...). Les diff´rentes commandes
                  e                   e                          e
     qui peuvent ˆtre fournies au pr´processeur seront expos´es dans le cha-
     pitre 7.
                                                                         e ee
  2. La compilation : la compilation proprement dite traduit le fichier g´n´r´
               e                                                   a
     par le pr´processeur (d’extension .i) en assembleur, c’est-`-dire en une
                                                                 e
     suite d’instructions du microprocesseur qui utilisent des mn´moniques ren-
     dant la lecture possible.
                             e
  3. L’assemblage : cette op´ration transforme le code assembleur (extension
                                     a                                       e
     .s) en un fichier binaire, c’est-`-dire en instructions directement compr´-
                                    e e
     hensibles par le processeur. G´n´ralement, la compilation et l’assemblage
                          e                   e
     se font dans la foul´e, sauf si l’on sp´cifie explicitement que l’on veut
                                                                        e
     le code assembleur. Le fichier produit par l’assemblage est appel´ fichier
     objet (et porte l’extension .o). Les fichiers objets correspondant aux li-
                e       e
     brairies pr´-compil´es ont pour suffixe .a.
        e                                              e e
  4. L’´dition de liens : un programme est souvent s´par´ en plusieurs fichiers
                                        ea
     sources (voir le chapitre 8 consacr´ ` la programmation modulaire), pour
                          e                             e e               a
     des raisons de clart´ mais aussi parce qu’il fait g´n´ralement appel ` des
                                         ea e
     librairies de fonctions standards d´j` ´crites. Une fois chaque fichier de
                            e                                     e
     code source assembl´, il faut donc lier entre eux les diff´rents fichiers
                e                                               e
     objets. L’´dition de liens produit alors un fichier dit ex´cutable (a.out
           e
     par d´faut).
  1
                             e                                             e
      Les extensions mentionn´es peuvent varier selon le compilateur utilis´ (gcc ici).


                                                3
1.1.3      Notes sur la normalisation du langage C
              e
[KR88], publi´ initialement en 1978 par Brian W. Kernighan et Denis M. Ritchie
                                                                        ee
a fait office de norme pendant longtemps (on parle alors de C K&R, en r´f´rence
aux 2 auteurs).
Devant la popularit´ du C, l’American National Standard Institut2 charge en
                     e
               e
1983 le comit´ X3J11 de standardiser le langage C. Le fruit de ce travail est
            ee          e     e
finalement ´t´ approuv´ en d´cembre 1989, le standard ANSI C89 est n´.    e
L’International Organization for Standardization3 a adopt´ en 1990 ce standard
                                                          e
en tant que standard international sous le nom de ISO C90. Ce standard ISO
                e e                             e    a      e
remplace le pr´c´dent standard ANSI (C89) mˆme ` l’int´rieur des USA, car
rappelons-le, ANSI est une organisation nationale, et non internationale comme
l’ISO.
                                                a       e
Les standards ISO, en tant que tel, sont sujets ` des r´visions, notamment en
1995 (C95) et en 99 (on parle alors de C99). Comme on le verra dans la suite,
                       e e                             e
certaines fonctionnalit´s d´crites dans ce document h´ritent de la norme C99
     e            e
et n’´tait pas pr´sentes avant.

1.1.4      C en pratique : compilation et debuggage
Compilation d’un programme C
          e
Il existe ´videmment un grand nombre de compilateurs C pour chaque plate-
                                                      e   e
forme (Windows, Linux, MAC etc...). Le compilateur d´taill´ dans cette section
correspond au compilateur libre gcc, disponible sur quasiment toutes les plate-
formes UNIX.
                                     e           e e           e
La compilation du fichier prog.c se d´roule en g´n´ral en deux ´tapes :
  1. traduction du langage C dans le langage de la machine (binaire) sous
     forme d’un fichier objet prog.o :
     gcc -O3 -Wall -c prog.c
  2. regroupement des variables et des fonctions du programme avec les fonc-
     tions fournies par des bibliotheques du systeme ou faites par l’utilisateur
     (edition de liens) : gcc -o prog prog.o
                                              e e       e
Ces deux commandes ont donc permis de g´n´rer l’ex´cutable prog qui peut
               e                      e
alors est lanc´ (./prog dans une fenˆtre de shell).
                                    e        e e       e
Si un seul fichier source est utilis´ pour g´n´rer l’ex´cutable, les deux com-
            e e               e            e
mandes pr´c´dentes peuvent ˆtre regroup´es en une seule :
                 gcc -O3 -Wall prog.c -o prog
Quelques explications sur les options de gcc s’imposent :
                                                e ee
– -O : effectue des optimisations sur le code g´n´r´. La compilation peut alors
                                              e                              a
  prendre plus de temps et utiliser plus de m´moire. Un niveau, variant de 0 ` 3
     e            e                        e
  sp´cifie le degr´ d’optimisation demand´. Ainsi, l’option -O3 permet d’otenir
                         e
  le code le plus optimis´.
                                                                 e
– -Wall : active des messages d’avertissements (warnings) suppl´mentaires ;
                                                          e
– -c : demande au compilateur de ne faire que la premiere ´tape de compilation ;
  2
      www.ansi.org
  3
      www.iso.ch


                                       4
                       e
– -o : permet de pr´ciser le nom du fichier produit.
il est important de noter que l’option -Wall est indispensable puisqu’elle
permet de detecter la plupart des erreurs d’inattention, de mauvaise pratique
du langage ou d’usage involontaire de conversion implicite.
Il existe bien sˆ r de nombreuses autres options disponibles4 . En voici certaines
                  u
                e
qui pourront ˆtre utiles :
                         e                               e
– -Idir : ajoute le r´pertoire dir dans la liste des r´pertoires ou se trouvent
   des fichiers de header .h (voir chapitre 8). Exemple : -Iinclude/
                      e
– -Werror : consid`re les messages d’avertissements (warnings) comme des er-
   reurs
              e
– -g : est n´cessaire pour l’utilisation d’un debugger. Il est possible de four-
                                                            e            e e
   nir plus ou moins d’informations au debugger. Ce crit`re est caract´ris´ par
                                              e
   un niveau variant entre 1 et 3 (2 par d´faut). Ainsi, l’utilisation du niveau
   maximal (option -g3) assure que le maximum d’informations sera fourni au
   debugger ;
– -pg : n´cessaire pour l’utilisation du profiler GNU : gprof5 (qui permet de
           e
     e
   d´terminer par exemple quelles sont les parties du code qui prennent le plus
                    e                           e          e
   de temps d’ex´cution et qui doivent donc ˆtre optimis´es).
Enfin, il est possible d’automatiser la compilation de l’ensemble des fichiers d’un
        a                                        e   e
projet ` l’aide de l’utilitaire make qui sera pr´sent´ au §8.5.
Note sur les erreurs de compilation : A ce stade, on peut classer les erreurs de
compilation en deux categories :
                                                    e
– les erreurs de syntaxe produites lors de la premi`re phase lorsque le code n’est
       e
   pas ´crit en C correct.
                                                                       e
– les erreurs de resolution des symboles produites lors de la deuxi`me phase
                                                  e
   lorsque des variables ou des fonctions utilis´es ne sont definies nulle part.

Outils de debuggage
                                                    e          e
Il existe un certain nombre de debugger qui peuvent ˆtre utilis´s pour corriger
les erreurs d’un programme :
– en ligne de commande : gdb (http://www.gnu.org/software/gdb/gdb.html)
– sous forme d’une application graphique :
   – xxgdb (http://www.cs.uni.edu/Help/xxgdb.html)
   – ddd (http://www.gnu.org/software/ddd/)
          e                                               e
A noter ´galement l’existence de valgrind qui permet de d´tecter les fuites de
m´moire6
   e


1.2                e
        Les mots-cl´s
                                   e      e
Un certains nombres de mots sont r´serv´s pour le langage C. Avant de com-
mencer, il convient donc d’en donner la liste exhaustive :

  4
    man gcc si vous avez du courage
  5
    http://www.gnu.org/software/binutils/manual/gprof-2.9.1/
  6
    Voir http://developer.kde.org/~sewardj/docs-2.1.2/manual.html


                                        5
                          auto     double          int       struct
                          break     else          long       switch
                           case    enum         register    typedef
                           char    extern        return      union
                          const     float          short    unsigned
                        continue     for         signed       void
                         default    goto         sizeof     volatile
                            do        if         static       while

1.3       Les commentaires
                        e
Les commentaires sont tr`s important dans n’importe quel langage de program-
mation car ils permettent de documenter les fichiers de sources.

/* Ceci est un commentaire
              e
   qui peut s’´taler sur plusieurs lignes */

                                         e
// Ceci est un commentaire qui ne peut s’´taler que sur une ligne

                                                                  e       ee
Attention car le commentaire sur une ligne est une fonctionnalit´ qui n’a ´t´
 e
d´finie qu’avec la norme C99 !
                     a                             e e
Une bonne habitude ` prendre dans le domaine g´n´ral de la programmation
        e
est de d´couper chaque fichier source selon plusieurs niveaux de commentaires :
  1. le fichier : pour indiquer le nom de l’auteur, du fichier, les droits de copy-
                         e
     right, la date de cr´ation, les dates et auteurs des modifications ainsi que
                  e
     la raison d’ˆtre du fichier ;
            e                              e                    e
  2. la proc´dure : pour indiquer les param`tres et la raison d’ˆtre de la pro-
      e
     c´dure
                                                  e
  3. groupe d’instruction : pour exprimer ce que r´alise une fraction significa-
                    e
     tive d’une proc´dure.
      e
  4. d´claration ou instruction : le plus bas niveau de commentaire.
           e
On consid´rera ainsi l’exemple fourni en annexe A.1 page 106.
                                                    e
Attention : Une erreur classique est d’oublier la s´quence fermante */.
                                             a               e a
Dans l’exemple suivantes, les instructions 1 ` n seront ignor´es ` la compilation :
/* premier commentaire
Instruction 1
...
Instruction n
          e
/* deuxi`me commentaire */
             e
Il convient ´galement de prendre la bonne habitude de comment´ son code e
source ”` la mode Doxygen”7 . Doxygen est un syst`me de documentation pour
        a                                             e
de nombreux langages de programmation (C++, C, Java, Objective-C, Python,
                           e e                                            e
IDL etc...). Il permet de g´n´rer facilement une documentation compl`te sur un
code source en utilisant un format de commentaires particulier.
  7
      www.doxygen.org


                                            6
1.4                 e e
         Structure g´n´rale d’un programme C
         e    e e
De mani`re g´n´rale, un programme C consiste en la construction de blocs indi-
              e
viduels appel´es fonctions qui peuvent s’invoquer l’un l’autre. Chaque fonction
r´alise une certaine tˆche8 .
 e                    a
                   e
Pour pouvoir s’ex´cuter, un programme C doit contenir une fonction sp´cialee
      e                               e        e              a              e
appel´e main qui sera le point d’entr´e de l’ex´cution (c’est ` dire la premi`re
                e       e               e
fonction invoqu´e au d´marrage de l’ex´cution). Toutes les autres fonctions sont
en fait des sous-routines.
                               e             e
Un programme C comporte ´galement la d´claration de variables qui corres-
          a                          e
pondent ` des emplacements en m´moire. Ces emplacements permettent de
                                  e         e          e
stocker des valeurs qui peuvent ˆtre utilis´es/modifi´es par une ou plusieurs
fonctions. Les noms des variables sont des identificateurs quelconques (voir
§1.5).
Voici deux exemples significatifs :
                                             a e
   1. D’abord, le petit classique qui affiche ` l’´cran ”Hello World !” :
       #include <stdio.h> // Directive de preprocesseur
       void main() {
           printf("Hello world!");
       }
                                                    ıtre toutes les compo-
   2. Un exemple un peu plus complet, faisant apparaˆ
      santes d’un programme C :
       #include <stdio.h> // Directive de preprocesseur
       #define TVA 15     // idem - la TVA au Luxembourg

                          e
       float prix_TTC; //d´claration d’une variable externe

                      e
       /* Exemple de d´claration d’une fonction secondaire */
       /* ajoute la TVA au prix HT; renvoie le prix TTC    */
       float ajoute_TVA(float prix_HT) {
           return prix_HT*(1 + (TVA/100));
       }

                                               e
       /* Fonction main: point d’entree de l’ex´cution*/
       void main() {
                        e
           float HT; //d´claration d’une variable interne
           puts("Entrer le prix H.T. : "); // appel de fonctions
           scanf("%f",&HT);                    e
                                           // d´finies dans stdio.h
           prix_TTC = ajoute_TVA(HT); //appel de notre fonction
           printf("prix T.T.C. : %.2f\n",prix_TTC);
       }
                                     e
On voit sur ces deux exemples les diff´rentes composantes d’un programme C :
  1. les directives du pr´processeur : elles permettent d’effectuer des ma-
                           e
     nipulations sur le texte du programme source, avant la compilation. Une
   8
                                                   e       e                 e
    A noter qu’il existe en C des fonctions pr´compil´es qui peuvent ˆtre incluses dans le
                            e                                     e            e
programme, ces fonctions ´tant contenues dans des fichiers sp´ciaux appel´s library files (ex-
                           e     a                                      e
tension *.lib). Pour acc´der ` ces fonctions, une directive doit ˆtre issue au compilateur
                                                                      e
indiquant d’inclure les header files (extension *.h) contenant les d´clarations correspondantes
a                                                                e    e
` ces fonctions. Dans tous les cas, les autres fonctions doivent ˆtre ´crites par le programmeur.


                                               7
                  e
   directive du pr´processeur est une ligne de programme source commen-
   ¸                  e    e                                  e    e
   cant par le caract`re di`se (#). Ces instructions seront d´taill´es dans le
                                  ea                                    e
   chapitre 7 mais on distingue d´j` les deux directives les plus utilis´es :
                                                                          e
   – #include qui permet d’inclure un fichier. Ici, le fichier stdio.h d´finit
            ee           e                                      e
     (on pr´f`re dire d´clare) les fonctions standards d’entr´es/sorties (en
     anglais STanDard In/Out), qui feront le lien entre le programme et la
                       e
     console (clavier/´cran). Dans cet exemple, on utilise les fonctions puts,
     printf et scanf (voir §2.5).
                    e
   – #define qui d´finit une constante. Ici, a chaque fois que le compilateur
     rencontrera le mot TVA, il le remplacera par 15.
2. les d´clarations de variables : Ces d´clarations sont de la forme :
        e                               e

                       type nom variable [= <valeur>] ;

                            e     e    e             c                  a
   Les variables peuvent ˆtre d´clar´es soit de fa¸on externe (c’est ` dire en
                                        c                                  a
   dehors d’une fonction), soit de fa¸on interne (on dit aussi locale) ` une
   fonction.
                            e                                    e
   Par exemple, dans la d´claration float prix_TTC ; on a d´fini la variable
                    e
   externe identifi´e par prix_TTC, de type float (le type des nombres r´els  e
       a                        u
   dit ` virgule flottante, d’o` ce nom). Les trois types scalaires de base du C
                            e                       e                          e
   sont l’entier (int), le r´el (float) et le caract`re (char) et seront abord´s
   au §1.7.
                                  e
   Toute variable C est typ´e. Cela permet notamment de savoir quelle
                                     e                                    e
   place la variable occupera en m´moire et comment il faudra interpr´ter la
                      e        e                       u e
   suite de bits stock´e en m´moire. On peut bien sˆ r d´finir ses propres types
   (voir chapitre 4), mais il existe un certain nombre de types disponibles de
     c                                       e    e
   fa¸on native. Ces types de base sont d´taill´s dans la section 1.7.
   Enfin, on ne peut jamais utiliser de variable sans l’avoir d´clar´e.  e    e
3. la d´finition de fonctions. Ce sont des sous-programmes dont les ins-
       e
                   e                                          e
   tructions vont d´finir un traitement sur des variables. La d´claration d’une
   fonction est de la forme :

         type_resultat nom fonction (type1 arg1 . . . typen argn ) {
                    e
                 <d´claration de variables locales >
                 <liste d’instructions >
         }

                           e
   En C, une fonction est d´finie par :
                    e
    (a) une ligne d´clarative qui contient :
                                           e
        – type_resultat : le type du r´sultat de la fonction.
        – nom fonction : le nom qui identifie la fonction.
                                                                       e
        – type1 arg1 . . . typen argn : les types et les noms des param`tres de
          la fonction
                               e     e
   (b) un bloc d’instructions d´limit´ par des accolades { ... }, conte-
       nant :

                                     8
                     e                                        e
              – <d´claration de variables locales > : les d´clarations des donn´es  e
                                a                 e
                 locales (c’est ` dire des donn´es qui sont uniquement connues `      a
                       e
                 l’int´rieur de la fonction)
                                                                         e
              – <liste d’instructions > : la liste des instructions qui d´finit l’action
                           e      e e
                 qui doit ˆtre ex´cut´e.
                                                              e
                 Une instructions est une expression termin´e par un ”;”. C’est un
                         ee                          a
                 ordre ´l´mentaire que l’on donne ` la machine et qui manipulera
                           e
                 les donn´es (variables) du programme. Dans notre exemple, on a
                 vu deux types de manipulation : l’appel de fonctions d’une part
                 (puts, printf, scanf ou ajoute_TVA) et une affectation (=).
                                  a e
                 – puts affiche ` l’´cran le texte fourni en argument.
                 – scanf attend que l’on entre une valeur au clavier, puis la met
                                                        e
                    dans la variable HT, sous format r´el (%f).
                                    a e
                 – printf affiche ` l’´cran un texte formatt´.    e
                                         e     e
                 Ces fonctions seront d´taill´es au §2.5 page 27.
                      e                                           e
              Par d´finition, toute fonction en C fournit un r´sultat dont le type
                     e     e                     e                  e e    a
              doit ˆtre d´fini. Le retour du r´sultat se fait en g´n´ral ` la fin de
              la fonction par l’instruction return. Le type d’une fonction qui ne
                                e            e     e
              fournit pas de r´sultat est d´clar´ comme void (voir §1.7.4).
                                              e    e           e
  4. Des commentaires (voir §1.3) : ils sont ´limin´s par le pr´processeur.
                                                               ee
     A noter que pour ignorer une partie de programme il est pr´f´rable d’uti-
                              e
     liser une directive du pr´processeur (#ifdef ... #endif : voir le cha-
     pitre 7).
D’autres exemples de fichiers source sont fournis en annexe A page 106.


1.5       Notion d’identificateur
Un identificateur, comme son nom l’indique, permet de donner un nom ` une a
     e
entit´ du programme (qu’il s’agisse d’une variable ou d’une fonction). Ils sont
            e
sujets aux r`gles suivantes :
                  e                               a           a
  1. Ils sont form´s d’une suite de lettres (’a’ ` ’z’ et ’A’ ` ’Z’), de chiffres (0
     a                                                        e
     ` 9) et du signe ’_’. En particulier, les lettres accentu´es sont interdites ;
                      e                              e
  2. le premier caract`re de cette suite ne peut pas ˆtre un chiffre ;
  3. les identificateurs sont case-sensitive.
Ainsi, les noms var1, S_i, _start et InitDB sont des identificateurs valides,
tandis que i:j ou 1i ne le sont pas.


1.6                     e
          Conventions d’´critures d’un programme C
                                       e                             e
Avant d’aller plus loin, il convient d’´tablir un certain nombre de r`gles de
  e                                                                e
pr´sentation que devra suivre tout bon programmeur qui souhaite ´crire des
programmes C lisibles9 :
                                                    e
– ne jamais placer plusieurs instructions sur une mˆme ligne ;
  9
           e                       e    e        a
      Ces r`gles peuvent largement ˆtre ´tendues ` tout langage de programmation


                                             9
– utiliser des identificateurs significatifs ;
      a a
– grˆce ` l’indentation des lignes, faire ressortir la structure syntaxique du
   programme ;
                                          e              e                      e
– laisser une ligne blanche entre la derni`re ligne des d´clarations et la premi`re
   ligne des instructions ;
     e                                                               e
– a´rer les lignes de programme en entourant par exemple les op´rateurs avec
   des espaces ;
                                        e
– bien commenter les listings tout en ´vitant les commentaires triviaux.
                                                                        e
A ce propos, la lecture de [Her01] devrait permettre au lecteur d’aqu´rir de bon
 e
r´flexes de programmation.


1.7     Les types de base
1.7.1              e
         Les caract`res
                     e              e
On utilise le mot-cl´ char pour d´signer une variable de type char. Il s’agit
                       e                   ee                 e         e
en fait d’un entier cod´ sur 8 bits interpr´t´ comme un caract`re utilis´ sur la
                       e e                              e
machine (il s’agit en g´n´ral du code ASCII de ce caract`re).
Ex :

                   e
char c1 = ’a’; // D´claration d’une variable c1 de type char
               // a laquelle on affecte la valeur ’a’
               // A noter l’utilisation du simple quote
                               e                  e
char c2 = 97; //c2 correspond ´galement au caract`re ’a’

                                                                   e
Le tableau 1.1 donne la liste des principaux codes ASCII en d´cimal (pour
e      e                          e        e           e                       e
ˆtre pr´cis, il s’agit de la premi`re moiti´ des caract`re ASCII, l’autre moiti´
correspondants en g´n´ral ` des caract`res ´tendus comme A).
                      e e    a          e    e               ¨

 code      0      1        2      3       4      5       6       7        8     9
    0    NUL    SOH      STX    ETX     EOT     ENQ     ACK     BEL     BS     HT
   10     LF     VT       NP     CR      SO      SI     DLE     DC1     DC2    DC3
   20    DC4    NAK      SYN    ETB     CAN     EM      SUB     ESC      FS    GS
   30     RS     US       SP      !       ”      #       $       %       &      ’
   40      (      )        *     +        ,       -      .       /        0     1
   50      2      3        4      5       6      7       8       9         :    ;
   60     <      =        >       ?      @       A       B       C       D      E
   70     F      G        H       I       J      K       L       M       N      O
   80     P      Q        R       S       T      U       V       W       X      Y
   90      Z      [        \      ]       ^              ‘       a       b      c
  100      d      e        f      g       h       i      j       k        l     m
  110      n      o        p      q       r       s      t       u        v     w
  120      x      y        z      {       |       }      -      DEL

                                                 e
                      Tab. 1.1 – Codes ASCII en d´cimal


                                        10
      e
Caract`res particuliers
                                     e
Il existe un certain nombre de caract`res particuliers dont les principaux sont
 e     e
r´sum´s dand le tableau 1.2.

       e
 Caract`re              ’\n’               ’\t’        ’\b’        ’\’’      ’\"’   \?
  e
 S´mantique              `
                  retour a la ligne     tabulation   backspace       ’         ”     ?


                                              e      e
                    Tab. 1.2 – Quelques caract`res sp´ciaux

            e                                    e
Ces caract`res ont une signification particuli`re pour le langage C. Par exemple,
          e           e        e
le caract`re " doit ˆtre utilis´ dans les chaˆ              e           e
                                              ınes de caract`re (et la s´quence ’\"’
                 e                e
permet de repr´senter ce caract`re dans une chaˆ                e              e
                                                     ıne). De mˆme, le caract`re ?
          e                                    e                        e
est utilis´ dans les trigraphes qui sont des s´quences de trois caract`res permet-
           e                 e                                             e
tant de d´signer les caract`res # [ ] \ ^ { } | ~. Le tableau 1.3 d´taille les
trigraphes disponibles.

     Trigraphe         ??=   ??(      ??/    ??)   ??’   ??<     ??!   ??>    ??-
    Signification        #     [        \      ]     ^     {       |     }      ~


            Tab. 1.3 – Les 9 trigraphes disponibles en norme ANSI


          ınes de caract`res ?
Et les chaˆ             e
        e                                ınes de caract`res sont vues comme un
En empi´tant un peu sur la suite, les chaˆ             e
                       e
pointeur sur des caract`res et sont donc de type char *. Ex :

                                                       e
char * chaine = "Hello World !";// une chaine de caract`re
                                // noter l’utilisation du double
                                // quote

         e                                e
Plus de d´tails dans le chapitre 3 consacr´ aux pointeurs.

1.7.2    Les entiers
                          e
On utilise alors le mot-cl´ int. Exemple :

    e
/* d´claration la plus courante d’une variable de type int */
                                          e a
int a = 14; // la variable a est initialis´e ` la valeur 14

                     e                   e e
/* Utilisation des pr´cisions (cas le + g´n´ral)*/
                         e
short int b; // b est cod´ sur 16 bits
int c;                   e
             // c est cod´ sur 16 ou 32 bits
                         e
long int d; // d est cod´ sur 32 bits

                e      e                  e
// la possibilit´ de l’´criture suivante d´pend du compilateur
                             e
long long int e; // d est cod´ sur 64 bits.

                                            11
     e
/* Pr´cision du signe */
                                               e
unsigned long int n; //n est un entier non sign´ sur 32 bits

       e                            e               e                        e
Par d´faut, les entiers sont en repr´sentation sign´e (principalement en repr´-
                      e    a
sentation par compl´ment ` 2 pour les acharn´s). e
Ainsi, un short int peut contenir tout entier donc la valeur est comprise entre
−215 et 215 − 1. Au contraire, un unsigned short int (non sign´) pourra
                                                                      e
contenir un entier compris entre 0 et 2  16 − 1.

Le tableau 1.4 regroupe les types entiers standards avec quelques informations
       e                         e         e
suppl´mentaires (la taille utilis´e en m´moire et l’intervalle des valeurs pos-
                                    e
sibles. Voici quelques valeurs num´riques pour avoir un meilleur ordre d’id´ee
                      e
des intervalles utilis´s :
  215 = 32.768                     216 = 65.536
  231 = 2.147.483.648              232 =4.294.967.295               .
  263 =9.223.372.036.854.775.808   2 64 =18.446.744.073.709.551.616


  Type                              e
                           Taille m´moire       Intervalle de valeurs
  char                     1 octet              [-128 ;127] ou [0,255]
  unsigned char            1 octet              [0 ;255]
  signed char              1 octet              [-128 ;127]
  int                      2 ou 4 octets        [-215 ;215 − 1] ou [−231 ; 231 − 1]
  unsigned int             2 ou 4 octets        [0 ;216 − 1] ou [0 ;232 − 1]
  short int                2 octets             [−215 ; 215 − 1]
  unsigned short int       2 octets             [0; 216 − 1]
  long                     4 octets             [−231 ; 231 − 1]
  unsigned long            4 octets             [0; 232 − 1]
  long long(*)             8 octets             [−263 ; 263 − 1]
  unsigned long long(*)    8 octets             [0; 264 − 1]


    Tab. 1.4 – Les types entiers standards. (*) : d´pend du compilateur utilis´
                                                   e                          e

                                                              e
Remarque : les valeurs limites des differents types sont indiqu´es dans le fichier
header <limits.h> (voir §9.9). En principe, on a
sizeof(short) <= sizeof(int) <= sizeof(long)

                       e
Cas des constantes enti`res
On distingue 3 notations :
   e         e                             e
– d´cimale (´criture en base 10) : c’est l’´criture usuelle. Ex : 372 ;
– octale (base 8) : on commence par un 0 suivi de chiffres octaux. Ex : 0477 ;
   e     e
– h´xad´cimale (base 16) : on commence par 0x (ou 0X) suivis de chiffres h´xa-  e
   e
  d´cimaux (0-9 a-f). Ex : 0x5a2b, 0X5a2b, 0x5A2b.
      e                                e                                     e
Par d´faut, le type des constantes enti`res est le type int. On peut aussi sp´cifier
explicitement le type en ajoutant l’un des suffixes L ou l (pour le type long),
LL or ll (pour le type long long). On peut aussi ajouter le suffixe U ou u (pour
unsigned). Des exemples sont fournis dans le tableau suivant :

                                        12
                     e
                   D´cimal          Octal          Hexa         Type
                   15               017            Oxf          int
                   32767            077777         0x7FFF       int
                   10U              012U           0xAU         unsigned int
                   32768U           0100000U       0x8000u      unsigned int
                   16L              020L           0x10L        long
                   27UL             033ul          0x1BUL       unsigned long

1.7.3     Les flottants
                                                    e                      a
On distingue trois types de flottants allant de la pr´cision la plus faible ` la
                 e              e
plus forte (qui d´pend de l’impl´mentation) : float, double et long double.
Ex : double Pi = 3.14159;

Representation Interne
                                e                                         e
La taille de stockage et la repr´sentation interne des nombres flottant d´pend
                      e
du compilateur utilis´ mais la plupart utilise le standard IEEE 754-1985[Ste90].
                                         e
Un nombre flottant x est ainsi compos´ d’un signe s (s = 1 pour un nombre
 e
n´gatif, 0 sinon) , d’une mantisse m et un exposant exp en base 2 tels que :

                                                        1.0 ≤ m < 2 ou
                        x = s ∗ m ∗ 2exp avec
                                                             m=0
       e                             e                                e
La pr´cision du nombre flottant d´termine le nombre de bits utilis´e pour la
                                             e       e
mantisse, alors l’intervalle de valeur est d´termin´ par le nombre de bits uti-
   e                                   e
lis´s par l’exposant. Le tableau 1.5 d´taille ces informations pour chaque type
flottant.

 Type                         e
                    Taille m´moire            Intervalle de valeurs                  e
                                                                                  Pr´cision
 float               4 octets                  [1, 2 ∗ 10−38 ; 3, 4 ∗ 1038 ]                  e
                                                                                  6 chiffres d´cimaux
 double             8 octets                  [2, 3 ∗ 10−308 ; 1, 7 ∗ 10308 ]                  e
                                                                                  15 chiffres d´cimaux
 long double        10 octets                 [3, 4 ∗ 10−4932 ; 1, 1 ∗ 104932 ]                e
                                                                                  19 chiffres d´cimaux


                                                               e
                Tab. 1.5 – Les types flottant pour les nombres r´els.

La figure 1.2 montre le format de stockage d’un nombre de type float dans la
    e
repr´sentation IEEE :
                                   Exposant                   Mantisse
                              s       exp                         m
           Position du bit:   31              22                                     0


        Fig. 1.2 – Format de stockage IEEE pour un nombre de type float

En binaire, le premier bit de la mantisse non nulle sera toujours 1 : il n’est donc
         e    e                      e
pas repr´sent´. L’exposant est stock´ avec un biais (qui vaut 127 pour le type
float) Un petit exemple pour bien comprendre : −2, 5 = −1 ∗ 1, 25 ∗ 21 . Les
              e
valeurs stock´es seront donc : S = 1 ; exp = 127 + 1 ; m = 0.25

                                                   13
Cas des constantes flottantes
                                                          e   e
Des exemples d’utilisation des constantes flottantes sont r´sum´es dans le ta-
bleau suivant :
         notation C    correspondance        notation C   correspondance
             2.                2                 .3             0.3
            1.4               1.4               2e4           2 ∗ 104
            2.e4            2 ∗ 104             .3e4         0.3 ∗ 104
          1.4e-4          1.4 ∗ 10−4




1.7.4    Le type void
                               e        e         e
On a vu que toute variable C ´tait typ´e, de mˆme que toute valeur de retour
d’un fonction. Mais il peut arriver qu’aucune valeur ne soit disponible. Pour
              e                                         e                         e
exprimer l’id´e de ”aucune valeur”, on utilise le mot-cl´ void. Ce type est utilis´
dans trois situations :
  1. les expressions de type void : on les rencontre principalement dans la
       e
     d´claration de fonctions qui n’ont pas de valeur de retour.
     Ex : void exit (int status);
     On utilise aussi dans le cadre d’une conversion de type (on parle de cast,
                                                                        e
     voir §2.4) vers le type void, ce qui n’a de sens que si la valeur r´sultat
                     e
     n’est pas utilis´e. Ex : (void)printf("Un exemple.");
                                          e
  2. le prototype de fonctions sans param`tres : int rand(void);
                                 e
     A noter qu’en pratique, on ´crira plus simplement : int rand();
  3. les pointeurs vers le type void : le type void * est le type pointeur g´-e
       e            a
     n´rique, c’est ` dire qu’il est capable de pointer vers n’importe quel type
                      e
     d’objet. Il repr´sente donc l’adresse de l’objet et non son type. Plus de
        e                                         e
     pr´cisions dans la section3 page 33 consacr´e aux pointeurs.




                                        14
Chapitre 2

La syntaxe du langage

2.1                         e
           Expressions et Op´rateurs
                                          e             e
Une expression est une combination d’op´rateurs et d’op´randes. Dans les cas les
                                 e      a
plus simples, une expression se r´sume ` une constante, une variable ou un appel
                                       e         e        e              e
de fonction. Des expressions peuvent ´galement ˆtre utilis´es comme op´randes
   e                                 e
et ˆtre jointes ensembles par des op´rateurs pour obtenir des expressions plus
complexes.
Toute expression a un type et si ce type n’est pas void, une valeur. Quelques
exemples d’expressions :

4 * 512           // Type: int
4 + 6 * 512       // Type: int; equivalent to 4 + (6 * 512)
printf("Un exemple!\n") // Appel de fonction, type: int
1.0 + sin(x)            // Type: double
srand((unsigned)time(NULL))     // Appel de fonction; type: void
(int*)malloc(count*sizeof(int)) // Appel de fonction; type: int *

       e                 e             a        e                   a
Les op´rateurs peuvent ˆtre unaires (` une op´rande) ou binaires (` deux op´-e
                              e
randes). Il existe un seul op´rateur ternaire (il s’agit de ?:) Les paragraphes
              e              e
qui suivent d´taillent les op´rateurs disponibles.

2.1.1         e              e
            Op´rateurs arithm´tiques
                   e              e              e     e
Les principaux op´rateurs arithm´tiques sont r´sum´s dans le tableau 2.1. Les
op´randes de ces op´rateurs peuvent appartenir ` tout type arithm´tique1
  e                  e                             a                  e
Quelques remarques :
                                                     e            e
– On peut effectuer une conversion de type aux op´randes. Le r´sultat de l’op´-  e
                                                              e          a
  ration prend le type de cette conversion. Ainsi ; 2.0/3 est ´quivalent ` 2.0/3.0
         e
  et le r´sultat est de type float.
       e
– Le r´sultat d’une division d’entiers est aussi un entier ! Ex :
  6 / 4           // Resultat: 1
  6 % 4           // Resultat: 2
  6.0 / 4.0       // Resultat: 1.5
  1
               e
      Seul l’op´rateur % requiert des types entiers


                                               15
    e
 Op´rateur       Traduction      Exemple          e
                                               R´sultat
      +          Addition          x + y       l’addition de x et y
      -          Soustraction      x - y       la soustraction de x et y
      *          Produit           x * y       la multiplication de x et y
      /          Division          x / y       le quotient de x et y
      %          Reste             x % y       Reste de la division euclidienne de x par y
  +(unaire)      Signe positif       +x        la valeur de x
  -(unaire)             e
                 Signe n´gatif       -x             e              e
                                               la n´gation arithm´tique de x
 ++(unaire)          e
                 Incr´ment       x++ ou ++x              e     e                   e
                                               x est incr´ment´ (x = x + 1). L’op´rateur
                                                  e                                e
                                               pr´fixe ++x (resp. suffixe x++) incr´mente
                                                                  e       e
                                               x avant (resp. apr`s) de l’´valuer
  --(unaire)         e
                 Decr´ment       x-- ou --x            e        e                  e
                                               x est d´crement´ (x = x − 1). L’op´rateur
                                                  e                             e e
                                               pr´fixe --x (resp. suffixe x--) d´cr´mente
                                                                  e       e
                                               x avant (resp. apr`s) de l’´valuer


                                           e              e
               Tab. 2.1 – Les principaux op´rateurs arithm´tiques

                   e            e
– Concernant l’incr´mentation pr´/postfixe, voici un petit exemple pour bien
  comprendre :
                                    e      a
  Supposons que la valeur de N soit ´gale ` 5 :
        e                                 e
  – Incr´mentation postfixe : X = N++ ; R´sultat : N = 6 et X = 5
        e            e                  e
  – Incr´mentation pr´fixe : X = ++N ; R´sultat : N = 6 et X = 6

2.1.2      e
         Op´rateurs d’affectation

   e
 Op´rateur       Traduction          Exemple         e
                                                   R´sultat
     =           affectation simple     x = y                              a
                                                   assigne la valeur de y ` x
   (op)=         affectation com-      x += y       x (op)=y       est    ´quivalent
                                                                         e              a
                                                                                        `
                    e
                 pos´e                             x = x (op) y


                                      e
                     Tab. 2.2 – Les op´rateurs d’affectation

                               e                                  e      e
On distingue deux types d’op´rateurs d’assignement qui sont r´sum´s dans le
                 e                      e                         e
tableau 2.2. L’op´rande de gauche doit ˆtre une expression qui d´signe un objet
(une variable par exemple).
       e                                     e    e
Dans l’´criture x(op)=y, la variable x n’est ´valu´e qu’une seule fois. (op) est un
  e              e                                         e
op´rateur arithm´tique ou de manipulation de bits Les op´rateurs d’affectation
        e
compos´s sont donc :
             +=    -=   *=     /=    %=     &=    ^=    |=    <<=      >>=

2.1.3      e
         Op´rateurs relationnels
Toute comparaison est une expression de type int qui renvoie la valeur 0 (false)
                               e                  e              e
ou 1 (true). Il faut que les op´randes soient du mˆme type arithm´tique (ou des
                                e
pointeurs sur des objets de mˆme type).

                                        16
   e
 Op´rateur        Traduction           Exemple         e
                                                     R´sultat
     <               e
                  inf´rieur              x < y                     e      a
                                                     1 si x est inf´rieur ` y
     <=              e         e
                  inf´rieur ou ´gal     x <= y                     e
                                                     1 si x est inf´rieur ou ´gal ` y
                                                                              e   a
     >                e
                  sup´rieur              x > y                      e      a
                                                     1 si x est sup´rieur ` y
     >=               e         e
                  sup´rieur ou ´gal     x >= y                      e
                                                     1 si x est sup´rieur ou ´gal ` y
                                                                               e   a
     ==           e     e
                  ´galit´               x == y                  e     a
                                                     1 si x est ´gal ` y
     !=             e     e
                  in´galit´             x != y                      e
                                                     1 si x est diff´rent de y


                                        e
                       Tab. 2.3 – Les op´rateurs relationnels


       e         e                           e    e
Les diff´rents op´rateurs relationnels sont d´taill´s dans le tableau 2.3.
                                                                e         e e
Attention : Une erreur ultra classique est de confondre l’op´rateur d’´galit´
(==) avec celui d’affectation (=). Ainsi, consid´rons le code suivant2 :
                                               e

/* Fichier: erreur.c */
#include <stdio.h>
int main () {
                                e
    int x=14,y=1; // x est diff´rent de y
    if (x = y)                            e
                  //erreur!!! il faudrait ´crire ’if (x == y)’
                          e    a
            printf("x est ´gal ` y (%i=%i)\n",x,y);
    else
                              e
            printf("x est diff´rent de y (%i!=%i)\n",x,y);
    return 0;
}

                         e                     a
A priori, x et y sont diff´rents et on s’attend ` obtenir un message le confirmant.
  e
N´anmoins, l’erreur d’ecriture a permis d’effectuer une affectation de la valeur
de x si bien que ce programme renvoit la ligne3 : x est ´gal ` y (1=1)) (not´
                                                          e     a                 e
la valeurs de x)
           e
Avec une ´criture correcte, on aurait obtenu : x est diff´rent de y (14!=1)
                                                            e
Remarque : en initialisant y a 0 (et non a 1), on aurait obtenu le message x est
                                     e
diff´rent de y (0 !=0) car le r´sultat de l’affectation est la valeur affect´e
      e                                                                         e
                                ee                                         e
(ici 0). Cette valeur est consid´r´e comme ’fausse’ pour la condition test´e dans
                                                             e e
le if si bien que les instruction de la branche else sont ex´cut´es (voir §2.2.1).

2.1.4        e
           Op´rateurs logiques
       e                      e
Les op´rateurs logiques, list´s dans le tableau 2.4 permettent de combiner le
 e
r´sultat de plusieurs expressions de comparaison en une seule expression logique.
       e               e                         e
Les op´randes des op´rateurs logiques peuvent ˆtre n’importe quel scalaire (i.e
       e                                     e                      ee
arithm´tique ou pointeur). Toute valeur diff´rente de 0 est interpr´t´e comme
                         a
vraie (et 0 correspond ` ’faux’). Comme pour les expressions relationnelles, les
                                               e
expressions logiques renvoient une valeur enti`re (0=false ; 1=true).
                   e                 e             e                  a
Remarque : les op´rateurs && et || ´valuent les op´randes de gauche ` droite et
    e                   e     e                            e
le r´sultat est connu d`s l’op´rande de gauche. Ainsi, l’op´rande de droite n’est
  2
      Compiler ce code avec la commande gcc -Wall erreur.c -o erreur
  3
                                               e         e
      l’option de compilation ’-Wall’ a quand mˆme soulev´ un warning


                                           17
   e
 Op´rateur        Traduction      Exemple        e
                                               R´sultat
     &&           ET logique       x && y                          e
                                               1 si x et y sont diff´rents de 0
     ||           OU logique       x || y                              e
                                               1 si x et/ou y sont diff´rents de 0
     !            NON logique        !x                    e    a
                                               1 si x est ´gal ` 0. Dans tous les autres
                                                                 e
                                               cas, 0 est renvoy´.


                                          e
                         Tab. 2.4 – Les op´rateurs logiques


e    e                                                       e
´valu´e que si celle de gauche est vraie dans le cas de l’op´rateur && (respec-
                                   e
tivement fausse dans le cas de l’op´rateur ||). Par exemple, dans l’expression
                                                           e
(i < max) && (f(14) == 1), la fonction f n’est appel´e que si i < max.

2.1.5          e            a
             Op´rateurs bit ` bit

   e
 Op´rateur        Traduction             Exemple     e
                                                   R´sultat (pour chaque position
                                                   de bit)
        &                a
                  ET bit ` bit            x & y    1 si les bits de x et y valent 1
        |                a
                  OU bit ` bit            x | y    1 si le bit de x et/ou de y vaut 1
        ^                  a
                  XOR bit ` bit           x ^ y    1 si le bit de x ou de y vaut 1
        ~                  a
                  NON bit ` bit            ~x      1 si le bit de x est 0
        <<         e       a
                  d´calage ` gauche      x << y     e
                                                   d´cale chaque bit de x de y positions
                                                   vers la gauche
        >>         e       a
                  s´calage ` droite      x >> y     e
                                                   d´cale chaque bit de x de y positions
                                                   vers la droite


                                 e
                Tab. 2.5 – Les op´rateurs de manipulation des bits

       e             a            e                                e
Les op´rateurs bits ` bits n’op`rent que sur des entiers. Les op´randes sont in-
     ee                                          a                               ee
terpr´t´es bits par bits (le bit 1 correspondant ` une valeur vraie, 0 est consid´r´
comme une valeur fausse). Quelques exemples pour bien comprendre (chaque
   e                                  e
op´rande est fournie sous forme d´cimale et binaire) :

                      x         y            e
                                          Op´ration       e
                                                        R´sultat
                 14    1110   9 1001        x & y       8   1000
                 14    1110   9 1001        x | y      15   1111
                 14    1110   9 1001        x ^ y       7   0111
                 14    1110                   ~x        1   0001
                 14    1110   2   0010     x << y      56 111000
                 14    1110   2   0010     x >> y       3    11

2.1.6          e             e a      e
             Op´rateurs d’acc`s ` la m´moire
    e              e                         e                        e
L’op´rande de l’op´rateur d’adresse & doit ˆtre une expression qui d´signe un
                                                        e
objet ou une expression. Ainsi, &x renvoie l’adresse m´moire de x et est donc
                             e                           e e
un pointeur vers x. Plus de d´tails dans le chapitre 3 d´di´ aux pointeurs.

                                          18
 Op.      Traduction                Exemple         e
                                                 R´sultat
  &       Adresse de                   &x                    e
                                                 l’adresse m´moire de x
  *       Indirection                  *p                                      e
                                                 l’objet (ou la fonction) point´e par p
 [ ]        e
          El´ment de tableau          t[i]          e                            ee
                                                 L’´quivalent de *(x+i), l’´l´ment
                                                 d’indice i dans le tableau t
  .       Membre d’une structure       s.x       le membre x dans la structure ou
          ou d’une union                         l’union s
  ->      Membre d’une structure       p->x      le membre x dans la structure ou
          ou d’une union                                       e
                                                 l’union point´e par p


                                  e             e a      e
                 Tab. 2.6 – Les op´rateurs d’acc`s ` la m´moire


      e             e
Les op´rateurs d’acc`s aux membres d’une structure ou d’une union seront plus
            e     e
amplement d´taill´s dans les §4.3 et §4.4.


2.1.7              e
          Autres op´rateurs
                                       e                     e
Il existe un certain nombre d’autres op´rateurs qui sont list´s dans le tableau 2.7.
On y retrouve notamment l’appel de fonction et la conversion de type (qui ne
                                                       e              e
s’applique qu’aux types scalaires et qui sera abord´e plus en d´tail dans la
section 2.4 page 25).

  Op.       Traduction               Exemple         e
                                                  R´sultat
  ()        Appel de fonction         f(x,y)          e
                                                  Ex´cute la fonction f avec les argu-
                                                  ments x et y
 (type)     cast                     (long)x                                     e e
                                                  la valeur de x avec le type sp´cifi´
 sizeof     taille en bits          sizeof(x)                            e
                                                  nombre de bits occup´ par x
  ? :       Evaluation     condi-     x?:y:z                  e
                                                  si x est diff´rent de 0, alors y sinon z
            tionnelle
      ,      e
            s´quencement                x,y       Evalue x puis y


                                                e
                        Tab. 2.7 – Les autres op´rateurs

    e
L’op´rateur sizeof renvoie le nombre de bits requis pour stocker un objet du
       e e         e
type sp´cifi´. Le r´sultat est une constante de type size_t.
    e                                                  e
L’op´rateur ?: permet une ecriture plus compacte de l’´valuation conditionnelle
if...then...else. Ainsi l’expression :
                                (x >= 0) ? x : -x
                                            e
renvoie la valeur absolue de de x. Plus de d´tails au §2.2.1.


2.2                              o
          Les structures de contrˆle
                                                                       e
Comme pour la plupart des langages de programmation, le langage C d´finit
                                        o
un certain nombre de structures de contrˆle (boucles et branchements).

                                        19
2.2.1    Instruction if...else
Syntaxe :
  1. if ( expression ) Instruction1
   2. if ( expression ) Instruction1 else Instruction2
                            e     e                   e
La valeur de expression est ´valu´e et si elle est diff´rente de 0, Instruction1 est
  e e                               e e
ex´cut´e, sinon Instruction2 est ex´cut´e (si elle existe). Exemple :
if (x > y)   max = x; // Assigne la plus grande valeur entre x et y
else                     a
             max = y; // ` la variable max.

Remarque :
                e e                  e
– l’exemple pr´c´dent aurait pu s’´crire plus simplement (mais moins lisible-
                                                    e
  ment) : (x > y)?(max = x):(max = y); ou mˆme max = (x>y)? x : y;
                                                e                       ee
– Attention aussi au fait que expression doit ˆtre correctement parenth´s´e.
                                                     e
– la partie ’then’ n’est pas introduite par le mot-cl´ then comme dans certains
  langages.

2.2.2    Instruction for
Syntaxe :
– for (expression1 ; expression2 ; expression3 )
       Instruction
                                       e
expression1 et expression3 peuvent ˆtre n’importe quelle expression. expression2
                             o               e                         e
est une expression de contrˆle et doit donc ˆtre de type scalaire. Le d´roulement
                                e
de cette instruction est illustr´ par l’organigramme de la figure 2.1.

                                       Expression2 != 0   non
                 Expression1                                    fin du for
                                              ?
                                                oui

                                         Instruction


                                        Expression3


                Fig. 2.1 – Organigramme de l’instruction for

Un exemple pour bien comprendre :
int i,MAX=14; //compteur
for (i=0; i < MAX ; i++) {
   printf("Valeur de i : %i\n",i);
}

2.2.3    Instruction while
Syntaxe :
– while (expression) Instruction
                                                                          e e
Il s’agit d’une boucle : tant que expression est vraie, Instruction est ex´cut´e,
          e     a
conform´ment ` l’organigramme de la figure 2.2.
Exemple :

                                        20
                           Expression != 0      non
                                                       fin du while
                                  ?
                                   oui

                             Instruction


              Fig. 2.2 – Organigramme de l’instruction while


#define MAX 14
int i=0;
while (i < MAX ) {
   printf("Valeur de i : %i\n",i);
   i++;
}


2.2.4   Instruction do...while

Syntaxe :
       do instruction while (expression) ;
                                                                        a
l’instruction do...while a un fonctionnement globalement similaire ` celui
                           a                                              e e
d’une boucle while mise ` part le fait que le corps de la boucle est ex´cut´
                               o        e    e
avant que l’expression de contrˆle soit ´valu´e, comme dans l’organigramme de
                                                          e e
la figure 2.3. Ainsi, le corps de la boucle est toujours ex´cut´ au moins une
fois.


                              Instruction



                            Expression != 0      non
                                                        fin du do
                                   ?
                                     oui


                Fig. 2.3 – Organigramme de l’instruction do

L’exemple suivant imprimera ”Valeur de i : 14” (alors que i ≥ 14) :

#include <stdio.h>
#define MAX 14
int main() {
    int i=MAX;
    do {
        printf("Valeur de i : %i\n",i);
        i++;
    } while (i < MAX);
    return 0;
}


                                           21
2.2.5       Instruction switch
                             e e     e
Cette instruction est un if g´n´ralis´. Sa syntaxe est la suivante :
switch (expression) {
   case constante1 : liste d′ instructions1 break ;
   ...
   case constanten : liste d′ instructionsn break ;
   default : liste d′ instructions
}
                    e    e            e                e
  1. expression est ´valu´e, puis le r´sulat est compar´ avec constante1 , constante2
     etc...
  2. A la premi`re constantei dont la valeur est ´gale ` expression, la (ou les4 )
                 e                               e     a
     liste d′ instructions correspondante(s) est ex´cut´e jusqu’` la rencontre
                                                    e e          a
     d’une instruction break. La rencontre d’un break termine l’instruction
     swith.
                                                         e     a
  3. s’il n’existe aucune constantei dont la valeur soit ´gale ` celle de expression,
     on ex´cute la liste d′ instructions de l’alternative default si celle-ci existe,
            e
     sinon rien n’est fait.
                                   e                           e
ATTENTION : expression est n´cessairement une valeur enti`re (voir le ta-
bleau 1.4 page 12). C’est pourquoi on utilisera souvent dans les exemples qui
                   e
suivent des enum´rations (voir §4.1 page 41).
                           ee
Compte tenu du nombre d’´l´ments optionnels, on peut distinguer 3 types d’uti-
lisations possibles :
    1. pour chaque alternative case, on trouve un break. Exemple :
        enum {FALSE, TRUE};
        void print_boolean(int bool) {
           switch (bool) {
              case FALSE: printf("faux"); break;
              case TRUE: printf("vrai"); break;
              default: printf("Erreur interne");
           }
        }
  2. on peut avoir une ou plusieurs alternatives ne poss´dant ni liste d′ instructions,
                                                        e
     ni break. Par exemple, si on veut compter le nombre de caract`res qui e
     sont des chiffres et ceux qui ne le sont pas, on peut utiliser le switch
     suivant :
        switch (c) {
           case ’0’:
           case ’1’:
           case ’2’:
           case ’3’:
           case ’4’:
           case ’5’:
           case ’6’:
           case ’7’:
           case ’8’:
  4
      l’instruction break est optionnelle


                                            22
          case ’9’: nb_chiffres++; break;
          default: nb_non_chiffres++;
      }
  3. enfin on peut ne pas avoir de break comme dans l’exemple suivant :
      enum {POSSIBLE, IMPOSSIBLE};
      void print_cas(int cas) {
         switch (cas) {
            case IMPOSSIBLE: printf("im");
            case POSSIBLE: printf("possible");
         }
      }
                         e    e       a
L’instruction break peut ˆtre ´tendue ` d’autres cas de branchements (ou de
sauts) comme on le verra au §2.2.7.
Les paragraphes qui suivent abordent les branchement non conditionnels (par
opposition aux branchements conditionnels correspondant aux instructions if...else
et switch) qui permettent de naviguer au sein des fonctions du programme.

2.2.6     Instruction goto
Syntaxe :
    goto etiquette ;
                                                   a
La directive goto permet de brancher directement ` n’importe quel endroit de la
                         e          e              e
fonction courante identifi´e par une ´tiquette. Une ´tiquette est un identificateur
suivi du signe ”:”.
Exemple :
for ( ... )
    for ( ... )
       if ( erreur )
         goto TRAITEMENT_ERREUR;
  ...
TRAITEMENT_ERREUR:                                           e
                        // le traitement d’erreur est effectu´ ici
   printf("Erreur: ....");
   ...

Remarque :
                                             e                                 e
– Encore une fois, l’instruction goto et la d´claration de l’etiquette doivent ˆtre
                           e
  contenu au sein de la mˆme fonction.
– N’utiliser cette instruction que lorsque vous ne pouvez pas faire autrement.
  Le plus souvent, vous pouvez vous en passer alors n’en abusez pas !
                                    e
– Pour une saut en dehors d’une mˆme fonction, on pourra utiliser les fonctions
  setjmp et longjmp.

2.2.7     Instruction break
Syntaxe :
   break;
            o
On a vu le rˆle de l’instruction break au sein de la directive de branchement
                                           e e
multiple switch. On peut l’utiliser plus g´n´ralement au sein de n’importe

                                        23
quelle boucle (instructions for, while ou do...while) pour interrompre son
 e                                 a        e
d´roulement et passer directement ` la premi`re instruction qui suit la boucle.
Exemple :
while (1) {
...
   if (command == ESC) break; // Sortie de la boucle
...
}
                     e
// on reprend ici apr`s le break

                         e
En cas de boucles imbriqu´es, break fait sortir de la boucle la plus interne

2.2.8    Instruction continue
Syntaxe :
    continue ;
                                 e         e
L’instruction continue ne peut ˆtre utilis´e que dans le corps d’une boucle
                                                                  a     e
(instruction for, while ou do). Elle permet de passer directement ` l’it´ration
suivante.
Exemple :
for (i = -10; i < 10; i++) {
                                  a          e
  if (i == 0) continue; // passer ` 1 sans ex´cuter la suite
...
}


2.3         e        e
        La r´cursivit´
                                           e        e
Comme pour beaucoup de langages, la r´cursivit´ est possible en C. Qu’est-
            e        e                 ee
ce que la r´cursivit´ ? C’est la propri´t´ qu’a une fonction de s’auto-appeler,
      a                           e                                 e
c’est-`-dire de se rappeler elle-mˆme plusieurs fois. La notion de r´currence est
              e                            e
largement pr´sente dans le domaine math´matique, notamment dans la d´fini-   e
tion des suites. Exemple :

                             u0 = 1
                             un = 2un−1 + 1     ∀n≥1

                           e
Les premiers termes cons´cutifs de cette suite sont donc :
u0 = 1
u1 = 2u0 + 1 = 3
u2 = 2u1 + 1 = 7 . . .
      u
Bien sˆ r, on pourrait calculer explicitement la valeur de un en fonction de n (ici,
on peut montrer que ∀n ∈ N, un = 2n+1 − 1) mais on peut utiliser directement
    e
la d´finition de la fonction pour la programmation du calcul correspondant :
#include <stdio.h>
/***********************************************************
                                      e
 * Exemple de fonction traduisant la d´finition de la suite:
 *   u_0 = 1
 *   u_n = 2*u_{n-1} + 1 si n >= 1

                                        24
  * Contrainte: n>=0
  ************************************************************/
int ma_suite(unsigned int n) {
     if (n == 0) return 1; //u_0 = 1
                                               e
     else return 2*ma_suite(n-1) + 1; //appel r´cursif
}
                                 e         e
/*** Fonction main: point d’entr´e de l’ex´cution ***/
int main() {
     unsigned int n;
     puts("Entrer la valeur de n : ");
     scanf("%u",&n);
     printf("valeur de la suite : %u\n",ma_suite(n));
     return 0;
}


2.4     Les conversions de types
Une conversion de type renvoie la valeur d’une expression (de type A) dans un
                                                 e           e
nouveau type B. Les conversions ne peuvent ˆtre effectu´es que sur les types
                 a                       e
scalaires (c’est ` dire les types arithm´tiques et les pointeurs).
Une conversion de type conserve toujours la valeur originale dans la mesure ou
                                           e
le nouveau type est capable de la repr´senter. Ainsi, un nombre flottant peut
e
ˆtre arrondi dans une conversion de double vers float. Enfin, la conversion
       e                        a            e
peut ˆtre soit implicite, c’est ` dire effectu´e par le compilateur (par exemple,
si i est une variable de type float et j de type int, alors l’expression i+j est
automatiquement de type float).
            e    e                      a a      e
Elle peut ˆtre ´galement explicite grˆce ` l’op´rateur de cast (transtypage en
     c                       u       e                          e
fran¸ais). Il est de bon goˆ t et tr`s important (pour la clart´ du programme)
               e                  e
d’utiliser l’op´rateur de cast d`s qu’une conversion de type est requise. Cela
e
´vite aussi les warnings du compilateur. Exemple :

                                                   e
void f(float i, float j) { // fonction avec 2 param`tres de type
   ...                     // float
}
int main() {
   int i = 14;
   float j = 2.0;
   ...
   f( (float)i, j); // appel de f avec conversion de i vers
                    // le type float
}



2.4.1   Les situations de la conversion de type
                      u
En C, les situations o` se produisent les conversions sont les suivantes :
                                            e
  1. une valeur d’un certain type est utilis´e dans un contexte qui en requiert
     un autre :
                          e             e                                  e
     – passage de param`tre : le param`tre effectif n’a pas le type du param`tre
                 e                              e e
       formel (c’´tait le cas dans l’exemple pr´c´dent) ;

                                       25
                              a                     e
     – affectation : la valeur ` affecter n’a pas le mˆme type que la variable ;
                                             e
     – valeur rendue par une fonction : l’op´rande de return n’a pas le type
             e           e
       indiqu´ dans la d´claration de la fonction.
       e
  2. op´rateur de conversion : le programmeur demande explicitement une
     conversion.
          e               e                   e
  3. un op´rateur a des op´randes de types diff´rents.
                                                         e e
Dans ce dernier cas, et contrairement aux deux cas pr´c´dents, c’est le compi-
                                            e                    e
lateur qui effectue la conversion selon des r`gles soigneusement d´finies qui sont
  e    e
d´taill´es dans les deux paragraphes qui suivent.


2.4.2       e
        La r`gle de ”promotion des entiers”
        e                 e         e            e
Cette r`gle est appliqu´e aux op´randes des op´rateurs unaires + et -, ainsi
           e                      e
qu’aux op´rateurs binaires de d´calage << et >> et dans les conversions arith-
   e                          e
m´tiques habituelles, abord´es au §2.4.3. Elle a pour but d’amener les ”petits
         a
entiers” ` la taille des int.
                e
Ainsi, toute op´rande de type char, unsigned char, short , unsigned short
                                e        e e
ou champs de bits sur les op´rateurs pr´c´dent est automatiquement conver-
                                                                       e
tie en int (ou en unsigned int si le type int ne permet pas de repr´senter
     e
l’op´rande).


2.4.3                         e
        Les conversions arithm´tiques habituelles
         e             e a             e              e                        e
Cette r`gle est appliqu´e ` tous les op´rateurs arithm´tiques binaires, except´s
       e            e                                         e      e
les op´rateurs de d´calage << et >> et les seconde et troisi`me op´randes de
    e
l’op´rateur ?:.
     e
La r`gles qui s’applique est la suivante :
                  e            e                  ea     e
Dans le cas d’op´randes enti`res, on applique d´j` la r`gle de promotion des
                                e                                              e
entiers, ce qui permet de se d´barrasser des petits entiers. Ensuite, si les op´-
                                    e
randes sont toujours de types diff´rents, on les convertit dans le type le plus
                e              e
haut dans la hi´rarchie expos´e dans la figure 2.4.

                                     long double
                                        double
                                         float
                                  unsigned long long
                                      long long
                                    unsigned long
                                         long
                                     unsigned int
                                          int



                     e                              e
        Fig. 2.4 – Hi´rarchie des conversions arithm´tiques habituelles



                                       26
2.4.4    Les surprises de la conversion de type
     e e                                      c                     a
En g´n´ral, les conversions fonctionnent de fa¸on satisfaisante vis ` vis du pro-
grammeur. Cependant, il existe une situation permettant d’obtenir des r´sul-e
                                                                e             e
tats surprenants : lors de la comparaison entre des entiers sign´s et non sign´s.
       e
Consid´rer ainsi le programme suivant :
#include <stdio.h>
int main () {
   unsigned int i = 0;
   if (i < -1) printf("Cha ch’est louche\n");
   else printf("On est super content!\n");
   return 0;
}

Celui ci imprimera le message Cha ch’est louche, laissant croire que 0 < −1...
                                      e              e           e
L’explication est la suivante : l’op´rateur < poss`de une op´rande de type
                                                                   e
unsigned int (i) et l’autre de type int (la constante -1). D’apr`s la figure 2.4,
           e                                                      e
cette derni`re est convertie en unsigned int et le compilateur r´alise la compa-
                e
raison non sign´e entre 0 et 4294967295 (puisque -1 = 0xFFFFFFFF = 4294967295).
CQFD.
                                                            e
Pour que tout revienne dans l’ordre, il suffit d’utiliser l’op´rateur de conversion
dans le test qui devient : if ( (int) i < -1)
Attention car on peut aussi utiliser des unsigned int sans s’en rendre compte !
Ainsi :
#include <stdio.h>
int main () {
   if ( sizeof(int) < -1) printf("cha ch’est louche\n");
   else printf("On est super content!\n");
   return 0;
}

imprimera le message... Cha ch’est louche. Pourtant, les int n’ont pas une
           e                                              e
longueur n´gative... Il se trouve simplement qui dans la d´claration de la fonc-
               e                                  ee
tion sizeof (d´finie dans stddef.h) renvoie un ´l´ment de type size_t, un
               e
entier non sign´...
Conclusion :
                   e                  e             e
  1. Ne jamais m´langer entiers sign´s et non sign´s dans les comparaisons
                                                                           e
     (l’option -Wall du compilateur le signale normalement) ! Utiliser l’op´ra-
                                           e
     teur de conversion pour amener les op´randes de la comparaison dans le
        e
     mˆme type.
                                                                     e
  2. bien noter que sizeof renvoit une valeur de type entier non sign´.


2.5                                 e
        Principales fonctions d’entr´es-sorties standard
Il s’agit des fonctions de la librairie standard stdio.h (voir aussi le §9.18 et le
                  e                 e                  e
chapitre 6) utilis´es avec les unit´s classiques d’entr´es-sorties, qui sont respec-
                          e                                            a
tivement le clavier et l’´cran. Sur certains compilateurs, l’appel ` la librairie
                                  e
stdio.h par la directive au pr´processeur #include <stdio.h> n’est pas n´-        e
                                        e    e
cessaire pour utiliser les fonctions pr´sent´es ici, notamment printf et scanf.

                                        27
2.5.1   La fonction getchar
                                   e    e                      e a
La fonction getchar permet la r´cup´ration d’un seul caract`re ` partir du
clavier. La syntaxe d’utilisation de getchar est la suivante :
                             var=getchar();
                    e
Notez que var doit ˆtre de type char. Exemple :

#include <stdio.h>
int main() {
    char c;
                            e
    printf("Entrer un caract`re:");
    c = getchar();
                     e       e
    printf("Le caract`re entr´ est %c\n",c);
    return 0;
}

                                                e           a
A noter enfin que cette fonction est strictement ´quivalente ` getc(stdin)
                          e
(Cette fonction sera abord´e au chapitre 6).

2.5.2   La fonction putchar
                                                      e        e
La fonction putchar permet l’affichage d’un seul caract`re sur l’´cran de l’or-
                                                   e
dinateur. putchar constitue alors la fonction compl´mentaire de getchar. La
syntaxe d’utilisation est la suivante :
                            putchar(var);

 u
o` var est de type char. Exemple :

#include <stdio.h>
int main() {
    char c;
                            e
    printf("Entrer un caract`re:");
    c = getchar();
    putchar(c);
    return 0;
}


2.5.3   La fonction puts
Syntaxe :
                                     puts(ch);

                                        ıne         e
Cette fonction affiche, sur stdout, la chaˆ de caract`res ch puis positionne
                e
le curseur en d´but de ligne suivante. puts retourne EOF en cas d’erreur.
Exemple :

#include <stdio.h>
int main() {
    char * toto = "on est super content!";
    puts(toto);
    return 0;
}


                                      28
2.5.4                 e        a e              e
        La fonction d’´criture ` l’´cran formatt´e printf
                                                          e
La fonction printf est une fonction d’impression format´e, ce qui signifie que
        e
les donn´es sont converties selon le format particulier choisi. Sa syntaxe est la
suivante :
       printf("cha^ne de contr^le", expression1 , . . . , expressionn );
                    ı               o

        ıne         o                   a                e
La chaˆ de contrˆle contient le texte ` afficher et les sp´cifications de format
                a                                    e
correspondant ` chaque expression de la liste. Les sp´cifications de format ont
                                          e a
pour but d’annoncer le format des donn´es ` visualiser. Elles sont introduites
             e           e                     ıne       o                e
par le caract`re %. Le i-`me format de la chaˆ de contrˆle sera remplac´ par
la valeur effective de expressioni .
      e           e          e               e
Pour ˆtre plus pr´cis, une sp´cification de i-`me format est de la forme :
                                    e
            %[flag][largeur][.pr´cision][modificateur]type

    ee                                              e      ee                  e
Les ´l´ments entre crochet sont facultatifs. Les diff´rents ´l´ments de cette sp´-
cification sont les suivants :
                                                   e
  1. [flag] fournit des options de cadrage (par d´faut, expressioni est justifi´  e
     ` droite) et peut prendre les valeurs suivantes :
     a
       -                                ea                             e
                expressioni sera justifi´ ` gauche (ajout de blancs si n´cessaire)
       +                                                        e
                affichage du signe (+ ou -) avant la valeur num´rique
                                                                   a
       espace impression d’un espace devant un nombre positif, ` la place du
                signe
     Il existe d’autres valeurs possibles mais qui sont moins utiles.
                                                   e    ae
  2. [largeur] est le nombre minimal de caract`res ` ´crire (des blancs sont
           e      e                      ae                              e
     ajout´s si n´cessaire). Si le texte ` ´crire est plus long, il est n´anmoins
     e               e
     ´crit en totalit´. En donnant le signe * comme largeur, expressioni four-
                                                     a
     nira la largeur (et expressioni+1 la variable ` afficher)
     Exemple : printf("%*f",14,var).
         e           e                e                              e
  3. [.pr´cision] d´finit, pour les r´els, le nombre de chiffres apr`s la virgule
                       e     e      a
     (ce nombre doit ˆtre inf´rieur ` la largeur). Dans le cas de nombres entiers,
                                                          e e
     ce champ indique le nombre minimal de chiffres d´sir´s (avec l’ajout de 0
                                     ıne
     sinon), alors que pour une chaˆ (%s), elle indique la longueur maximale
             e           e                                                  e
     imprim´e (tronqu´e si trop longue). Comme pour la largeur, on peut ´crire
                                   e            e      a
     .* pour que expressioni+1 d´signe la pr´cision ` appliquer.
                   e
     Valeur par d´faut : 6.
                                     e
  4. [modificateur] modifie l’interpr´tation de expressioni et peut valoir h
                                                              e
     (pour short), l (long pour les entiers, double pour les r´els), ou encore
                              e
     L (long double pour les r´els).
             e             e       a        a
  5. type pr´cise l’interpr´tation ` donner ` expressioni . Les valeurs possibles
           e     e
     sont d´taill´es dans la table 2.8
                                     e  e                             e
printf retourne le nombre de caract`res ´crits, ou EOF en cas de probl`me.
Voici plusieurs exemples didactiques :
printf("|%d|\n",14);                          |14|
printf("|%d|\n",-14);                         |-14|
printf("|%+d|\n",14);                         |+14|

                                       29
 format   conversion en    e
                           ´criture
   %d     int                e           e
                           d´cimale sign´e
   %ld    long int           e           e
                           d´cimale sign´e
   %u     unsigned int       e
                           d´cimale non sign´e e
   %lu    unsigned long      e
                           d´cimale non sign´e e
   %o     unsigned int                     e
                           octale non sign´e
   %lo    unsigned long                    e
                           octale non sign´e
   %x     unsigned int            e
                           hexad´cimale non sign´ee
   %lx    unsigned long           e
                           hexad´cimale non sign´ee
   %f     double             e
                           d´cimale virgule fixe
   %lf    long double        e
                           d´cimale virgule fixe
   %e     double             e
                           d´cimale notation exponentielle
   %le    long double        e
                           d´cimale notation exponentielle
   %g     double             e            e
                           d´cimale, repr´sentation la plus courte parmi %f et %e
   %lg    long double        e            e
                           d´cimale, repr´sentation la plus courte parmi %lf et %le
   %c     unsigned char           e
                           caract`re
   %s     char*                ıne
                           chaˆ de caract`rese

                            e
          Tab. 2.8 – Les diff´rents formats de la fonction printf


printf("|%+d|\n",-14);                   |-14|
printf("|% d|\n",14);                    | 14|
printf("|% d|\n",-14);                   |-14|
printf("|%x|\n",0x56ab);                 |56ab|
printf("|%X|\n",0x56ab);                 |56AB|
printf("|%#x|\n",0x56ab);                |0x56ab|
printf("|%#X|\n",0x56ab);                |0X56AB|
========================
printf("|%o|\n",14);                     |16|
printf("|%#o|\n",14);                    |016|
========================
printf("|%10d|\n",14);                   |        14|
printf("|%10.6d|\n",14);                 |    000014|
printf("|%.6d|\n",14);                   |000014|
printf("|%*.6d|\n",10,14);               |    000014|
printf("|%*.*d|\n",10,6,14);             |    000014|
========================
printf("|%f|\n",1.234567890123456789e5);     |123456.789012|
printf("|%.4f|\n",1.234567890123456789e5); |123456.7890|
printf("|%.20f|\n",1.234567890123456789e5); |123456.78901234567456413060|
printf("|%20.4f|\n",1.234567890123456789e5); |         123456.7890|
========================
printf("|%e|\n",1.234567890123456789e5);     |1.234568e+05|
printf("|%.4e|\n",1.234567890123456789e5); |1.2346e+05|
printf("|%.20e|\n",1.234567890123456789e5); |1.23456789012345674564e+05|
printf("|%20.4e|\n",1.234567890123456789e5); |          1.2346e+05|
========================
printf("|%.4g|\n",1.234567890123456789e-5); |1.235e-05|
printf("|%.4g|\n",1.234567890123456789e5); |1.235e+05|

                                   30
printf("|%.4g|\n",1.234567890123456789e-3); |0.001235|
printf("|%.8g|\n",1.234567890123456789e5); |123456.79|

2.5.5    La fonction de saisie scanf
                                e    e            e
La fonction scanf permet de r´cup´rer les donn´es saisies au clavier, dans le
           e e             e             e                e e
format sp´cifi´. Ces donn´es sont stock´es aux adresses sp´cifi´es par les argu-
                                                e
ments de la fonction scanf (on utilise donc l’op´rateur d’adressage & pour les
variables scalaires). scanf retourne le nombre de valeurs effectivement lues et
  e      e
m´moris´es (en ommettant les %*). La syntaxe est la suivante :
            scanf("cha^ne de contr^le", arg1 , . . . , argn );
                         ı              o

       ıne         o                                         e
La chaˆ de contrˆle indique le format dans lequel les donn´es lues sont conver-
                                          e
ties. Elle ne contient pas d’autres caract`res (notamment pas de \n). Comme
                                                e e                  e     e e e
pour printf, les conversions de format sont sp´cifi´es par un caract`re pr´c´d´
                                                             e     e e
du signe %. Les formats valides pour la fonction scanf diff`rent l´g`rement de
ceux de la fonction printf.
           e a                             e     e e
Les donn´es ` entrer au clavier doivent ˆtre s´par´es par des blancs ou des
                                       e
<RETURN> sauf s’il s’agit de caract`res.
      e           e          e
Pour ˆtre plus pr´cis, les sp´cifications de format ont la forme suivante :
                       %[*][larg][modif]type

   *                                              e
         la valeur sera lue mais ne sera pas stock´e
 larg       e                                  e    a
         pr´cise le nombre maximal de caract`res ` lire
 modif      e                  e                    e       e
         sp´cifie une taille diff´rente pour les donn´es point´es par l’argument :
         h : short int
         l : long int (pour les entier) ou double (pour les nombres flottants).
         L : long double
 type       e                    e
         sp´cifie le type de donn´e lue et comment il faut le lire.

                                        e
La plupart des types de scanf sont les mˆmes que pour printf :

 Type    Description                                              Argument requis
 c              e
         caract`re simple (espace inclu)                              char *
 d                                          e e e
         entier : nombre optionnellement pr´c´d´ par un signe          int *
 e,E,                                 e       e e e
         Floating point : nombre d´cimal pr´c´d´ eventuelle-          float *
 f,g,G                              e                      e
         ment d’un signe et suivi ´ventuellement du caract`re
                                    e
         e ou E et d’un nombre d´cimal. Exemple :-732.103
         ou 7.12e4
 o       entier en notation octale.                                      int *
 s           ıne          e             a
         Chaˆ de caract`res (jusqu’` la lecture d’un espace)            char *
 u                      e
         entier non sign´                                           unsigned int *
 x       entier hexadecimal                                              int *

Exemple :
#include <stdio.h>
int main() {

                                      31
    int i;
    printf("entrez un entier sous forme hexadecimale i = ");
    scanf("%x",&i);
    printf("i = %d\n",i);
    return 0;
}

                           e
Les autres fonctions d’entr´es/sorties (permettant notamment la manipulation
                         e
des fichiers) seront abord´es dans le chapitre 6.




                                    32
Chapitre 3

Les pointeurs

                      e                              e                   e
Toute variable manipul´e dans un programme est stock´e quelque part en m´-
                        e                 e                           e
moire centrale. Cette m´moire est constitu´e d’octets qui sont identifi´s de
     e                       e
mani`re univoque par un num´ro qu’on appelle adresse comme l’illustre la fi-
gure 3.1.

                                Adresse   32 bits

                                 0x5E00                             p (un pointeur vers une zone mémoire)
                                 0x5E04
         adresses croissantes




                                            MEMOIRE CENTRALE




                                 0x5E08

                                 0x5E0C

                                 0x5E10

                                 0x5E14

                                 0x5E18

                                 0x5E1C

                                 0x5E20




                                                       e
         Fig. 3.1 – Illustration de l’adressage de la m´moire centrale

                                e                                        e
Par analogie, on peut voir la m´moire centrale comme une armoire constitu´e
              e e             e                      a
de tiroirs num´rot´s. Un num´ro de tiroir correspond ` une adresse.

                    e                                                  e
Ainsi, lorsqu’on d´clare une variable var de type T, l’ordinateur r´serve un
         e
espace m´moire (de sizeof(T) octets) pour y stocker les valeurs de var.
Pour retrouver cette variable, il suffit donc de connaˆ  ıtre l’adresse du premier
       u                e
octet o` elle est stock´e (ou, s’il s’agit d’une variable qui recouvre plusieurs
octets contigus, l’adresse du premier de ces octets).

                                Un pointeur est une variable de type adresse.

                e
Les pointeurs pr´sentent de nombreux avantages :
                                   c                     e
– Ils permettent de manipuler de fa¸on simple des donn´es de taille importante
  (comme les tableaux, les structures etc...). Ainsi, au lieu de passer en para-
     e a                  ee         e
  m`tre ` une fonction un ´l´ment tr`s grand (en taille), on pourra se contenter

                                                               33
                                       ee                     e
  de lui fournir un pointeur vers cet ´l´ment... On gagne ´videmment alors en
          e           e
  efficacit´ dans l’ex´cution du programme.
– Comme on le verra au chapitre 4, les tableaux ne permettent de stocker qu’un
              e ee               e
  nombre fix´ d’´l´ments de mˆme type. Si les composantes du tableau sont des
                                             ee
  pointeurs, il sera possible de stocker des ´l´ments de tailles diverses (comme
  des chaˆ                e
          ınes de caract`res : voir §4.2.5)
                        e                     ın´
– Il est possible de cr´er des structures chaˆ ees (on dit aussi structures auto-
   eee                      e        e                     e
  r´f´r´es) qui sont utilis´es pour d´finir des listes chain´es. De telles listes sont
                   e                                       ee
  beaucoup utilis´es en programmation (le nombre d’´l´ments de cette liste
        e
  peut ´voluer dynamiquement, ce qui permet une utilisation plus souple que
                                                 e       e
  celle des tableaux). Cette notion sera abord´e en d´tail au §4.3.5.


3.1      e
        D´claration d’un pointeur
                                 e a                   e                e
En C, chaque pointeur est limit´ ` un type de donn´e. En effet, mˆme si la
                                                                  e
valeur d’un pointeur (une adresse) est toujours un entier (ou ´ventuellement
                                         e                                e
un entier long), le type d’un pointeur d´pend du type de l’objet point´. Cette
                              a          e
distinction est indispensable ` l’interpr´tation (en fait la taille) de la valeur
      e
point´e.
      e
On d´clare un pointeur par l’instruction :
                         type *nom-du-pointeur ;

 u                                  e
o` type est le type de l’objet point´. Exemple :

int *pi;        // pi est un pointeur vers un int
short int *psi; // psi est un pointeur vers un short int
char *pc;       // pc pointeur vers un char

                                         e e             a
A noter aussi l’existence de pointeurs g´n´riques, c’est ` dire capable de pointer
vers n’importe quel type d’objet. On utilise pour cela le type void *.
Sans un tel type, il ne serait pas possible par exemple d’indiquer le type d’objet
                                             e
rendu par les fonctions d’allocation de m´moire qui rendent un pointeur vers
             e                                           a
l’objet allou´, puisque ce type varie d’une invocation ` l’autre de la fonction.
                                                   e                   e
Par exemple, la fonction malloc de la biblioth`que standard est d´finie de la
     e
mani`re suivante : void *malloc(size_t size);


3.2       e
        Op´rateurs de manipulation des pointeurs
Lors du travail avec des pointeurs, nous avons besoin :
          e
– d’un op´rateur ’adresse de’ & pour obtenir l’adresse d’une variable.
          e                               e
– d’un op´rateur ’contenu de’ * pour acc´der au contenu d’une adresse.

3.2.1        e
         L’op´rateur ’adresse de’ &
     e                    e    a
L’op´rateur & permet d’acc´der ` l’adresse d’une variable. La syntaxe est la
suivante :

                                         34
                                          &nom-variable

                           e          e
  Cette adresse peut alors ˆtre utilis´e pour initialiser la valeur d’un pointeur.
                              e
  Dans l’exemple suivant, on d´finit un pointeur p qui pointe vers un entier i :

  int * p;      e                                               e
              //´tape (1): pointeur vers un entier non initialis´
                e                       e            e
  int i = 14; //´tape (2): variable enti`re initialis´e a 14
  p=&i;         e
              //´tape (3): p pointe vers i

         e       e              e                  e
  Les diff´rentes ´tapes de ce sc´nario sont illustr´es dans la figure 3.2.

               (1)                               (2)                                 (3)
            int * p;                         int i = 14;                           p = &i;



Ox1A30                       p   Ox1A30                        p     Ox1A30        Ox352C           p


    (déclaration d’un pointeur
         p vers un entier)       Ox352C           14           i     Ox352C           14            i


                                    (déclaration et initialisation             (affectation de
                                      d’une variable entière i)               l’adresse de i à p)


                                   e                               e
          Fig. 3.2 – Illustration m´moire d’une affectation par l’op´rateur &

       e                          e           ea
  L’op´rateur & peut seulement ˆtre appliqu´ ` des objets qui se trouvent dans
       e                    a       a
  la m´moire interne, c’est ` dire ` des variables et des tableaux. Il ne peut pas
  e           ea
  ˆtre appliqu´ ` des constantes ou des expressions.

  3.2.2           e
              L’op´rateur ’contenu de’ : *
       e                                            e                 a
  L’op´rateur unaire d’indirection * permet d’acc´der directement ` la valeur de
               e                eee
  l’objet point´ (on dit qu’on d´r´f´rence un pointeur). La syntaxe est la suivante :
                                    *nom-pointeur

                                                    e
  Ainsi, si p est un pointeur vers un entier i, *p d´signe la valeur de i. Exemple :
  int main() {
    int i = 14;
    int *p;
    p = &i;                  //p contient l’adresse de i (0x352C)
    printf("*p = %d \n",*p); //affiche "*p = 14"
  }

        e           e                    e e
  Pour r´sumer, apr`s les instructions pr´c´dentes :
       e
  – i d´signe le contenu de i (soit 14)
         e
  – &i d´signe l’adresse de i (soit 0x352C)
       e
  – p d´signe l’adresse de i (soit 0x352C)
         e
  – *p d´signe le contenu de i (soit 14)
  En outre :

                                                   35
      e
– &p d´signe l’adresse de p (soit 0x1A30)
             e e       e
– *i est en g´n´ral ill´gal (puisque i n’est pas un pointeur mais il peut arriver
  que la valeur *i ait un sens)

Petites devinettes (Merci Bernard Cassagne [Cas98]) : Soient i et j deux poin-
teurs vers des entiers,
  1. A quoi correspond l’expression *i**j ?
  2. Et *i/*j ?


3.3     Initialisation d’un pointeur
      e                     e
Par d´faut, lorsque l’on d´clare un pointeur p sur un objet de type T, on ne
                                               e
sait pas sur quoi il pointe. En effet, la case m´moire qu’il occupe contient une
certaine valeur qui risque de le faire pointer vers une zone hasardeuse de la
  e
m´moire.

                                               e             e
        Comme toute variable, un pointeur doit ˆtre initialis´ !

                                                c
Cette initialisation peut s’effectuer de trois fa¸ons :
  1. affectation ` l’adresse d’une autre variable de p. Si la variable est un poin-
                a
     teur, on peut faire l’affectation directement, sinon on doit utiliser l’op´-e
     rateur &. Exemple :
                      e
      int *p1, *p2;//d´claration de 2 pointeurs vers des entiers
                                               a
      int i = 14; //supposons que i se trouve ` l’adresse 0x352C
      p1 = &i;                   a
                   //affectation ` l’adresse de i de p1 , soit 0x352C
      p2 = p1;                         a
                   //affectation de p2 ` p1:
                   //p2 contient aussi l’adresse de i
                      a
  2. affectation de p ` la valeur NULL : on peut dire qu’un pointeur ne pointe
                                                                e
     sur rien en lui affectant la valeur NULL (cette valeur est d´finie dans le
     fichier stddef.h). Exemple :
     int * p = NULL;
                                        e             e
  3. affectation directe de *p (la zone m´moire point´e par p). Pour cela, il faut
               e       a               e                   e
     d’abord r´server ` *p un espace-m´moire de taille ad´quate (celui du type
           e                                                        e
     point´ par p, soit sizeof(T) octets). L’adresse de cet espace-m´moire sera
                              e                  a e                     e
     la valeur de p. Cette op´ration consistant ` r´server un espace-m´moire
                                e
     pour stocker l’objet point´ s’appelle une allocation dynamique et sera
            e           e
     abord´e plus en d´tail au §3.5.
                       ee
Pour bien montrer l’int´rˆt de l’initialisation de tout pointeur, reprenons l’exemple
  e e
pr´c´dent dans lequel on remplace l’affectation p2 = p1 par *p2 = *p1 :
 int * p1, *p2;
 int i = 14;
 p1 = &i;
 *p2 = *p1;
Que va t il se passer ?

                                        36
                       e
– p1 est bien initialis´ et pointe sur i (*p1=14)
                     ee         e         e                   e
– mais p2 n’a pas ´t´ initialis´ : *p2 d´signe une adresse m´moire a priori in-
                                              e
   connue. L’instruction *p2 = *p1 force l’´criture de la valeur *p1=14 dans
             e             e                                        e
   la case m´moire point´e par p2 ce qui pourrait avoir des cons´quences d´-   e
                                       e e             e
   sastreuses ! Cette ecriture est en g´n´ral sanctionn´ par le message d’erreur
                             a    e
   ”Segmentation fault” ` l’ex´cution.
                 e                                                e
Il faut quand mˆme noter que certain compilateurs ont la bonne id´e d’initialiser
                    a                                       e
automatiquement ` la valeur NULL un pointeur non affect´. Mais cela ne doit
         e
pas empˆcher de prendre la bonne habitude d’initialiser tout pointeur.


3.4           e
        Arithm´tique des pointeurs
                           e
La valeur d’un pointeur ´tant un entier, on peut lui appliquer un certain nombre
      e               e                                   e              e
d’op´rateurs arithm´tiques classiques. Les seules op´rations arithm´tiques va-
lides sur les pointeurs sont :
                           a
– l’addition d’un entier ` un pointeur −→ p + i
        e                             e
   Le r´sultat est un pointeur de mˆme type que le pointeur de d´part.e
                                a
– la soustraction d’un entier ` un pointeur −→ p – i
        e                             e
   Le r´sultat est un pointeur de mˆme type que le pointeur de d´part.e
          e
– la diff´rence de deux pointeurs −→ p1 – p2
   Il faut absolument que les deux pointeurs pointent vers des objets de mˆme  e
               e                                        e     a
   type T. Le r´sultat est un entier dont la valeur est ´gale ` (p1 - p2)/sizeof(T).
Attention : la somme de deux pointeurs n’est pas autoris´e !    e

                                                                       e    e
Ainsi, soit i un entier et p un pointeur sur un objet de type T (donc d´clar´
                                                             e
par l’instruction : T *p ;). Alors p+i (respectivement p-i) d´signe un poin-
                                                 e   a               e
teur sur un objet de type T. Sa valeur est ´gale ` celle de p incr´ment´e  e
                   e e      e
(respectivement d´cr´ment´e) de i*sizeof(T). Exemple :
#include <stdio.h>
int main() {
    int i = 2;
    int *p1, *p2;
    p1 = &i;
    p2 = p1 + 1;
    printf("Adresse p1 = Ox%lx \t Adresse p2 = 0x%lx\n",
            (unsigned long)p1,
            (unsigned long)p2);
                 e
    printf("Diff´rence des adresses: %lu \t sizeof(int)=%u\n",
            (unsigned long)p2-(unsigned long)p1,
            sizeof(int));
    printf("MAIS p2-p1 = %i !\n",p2-p1);
    return 0;
}

Ce programme renverra :

Adresse p1 = Oxbffffb24                  Adresse p2 = 0xbffffb28
    e
Diff´rence des adresses: 4               sizeof(int)=4
MAIS p2-p1 = 1 !

                                       37
                   e     e
On peut faire la mˆme exp´rience avec le type double (au lieu du type int).
On obtiendrait alors :

Adresse p1 = Oxbffffb20                          Adresse p2 = 0xbffffb28
    e
Diff´rence des adresses: 8                       sizeof(double)=8
MAIS p2-p1 = 1 !
                     e                          e
A noter qu’on peut ´galement utiliser les op´rateurs ++ et -- avec des poin-
            e e                         e
teurs. En g´n´ral, on les utilise pour r´aliser des parcours de tableaux et plus
         e
particuli`rement dans les chaˆ                e
                               ınes de caract`res. Exemple (comme on le verra
                    ıne                           e                e
au §4.2.1, toute chaˆ se termine par un caract`re null, le caract`re ’\0’) :
#include <stdio.h>
int main() {
    char * mess = "On est super content!";
    char *p;
    for (p = &mess[0]; *p != ’\0’; p++) {
        printf("Adresse: %ld | Contenu: %c\n",(long)p,*p);
    }
              e
    // Autre m´thode classique, avec while
                 e
    p = mess; // ´quivalent de p = &mess[0] dans ce cas
    puts("========================================");
    while (*p != ’\0’) {
        printf("Adresse: %ld | Contenu: %c\n",(long)p,*p);
        p++;
    }
    return 0;
}

Les chaˆ               e                           e     e
         ınes de caract`res seront plus amplement d´taill´es au §4.2.1 page 43.
                    e                           e
A noter que les op´rateurs de comparaison sont ´galement applicables aux poin-
teurs1 .


3.5                                 e
           Allocation dynamique de m´moire
                                                           e       e
Nous avons vu que l’utilisation de pointeurs permet de m´moriser ´conomique-
                e        e                                             e
ment des donn´es de diff´rentes grandeurs (puisqu’on se contente de m´moriser
                     e                                                   e
l’adresse de ces donn´es). Pour permettre une utilisation efficace de la m´moire,
                                               e             e            e
il est primordial de disposer de moyens pour r´server et lib´rer de la m´moire
                             a              e
dynamiquement au fur et ` mesure de l’ex´cution. C’est ce qu’on appelle un
                               e
allocation dynamique de la m´moire. Les fonctions pour la gestion dynamique
         e            e    e                             a
de la m´moire sont d´clar´es dans le fichier stdlib.h (` inclure).

    e                  a e                   e           e e
L’op´ration consistant ` r´server un espace-m´moire est r´alis´ par la fonction
malloc. Sa syntaxe est :
                             malloc(nb octets)

Cette fonction retourne un pointeur de type char * pointant vers une zone
m´moire de nb octets octets. Pour initialiser des pointeurs vers des objets qui
 e
  1
      a                 u                                                              e
      ` condition bien sˆ r de comparer des pointeurs qui pointent vers des objets de mˆme type.


                                               38
ne sont pas de type char, il faut convertir le type de la sortie de la fonction
        a                    eae       e
malloc ` l’aide d’un cast (d´j` ´tudi´ au §2.4).
                                                                         e
A noter enfin qu’en pratique, on utilise la fonction sizeof() pour d´terminer
                                                                            e
la valeur nb octets. Ainsi, pour initialiser un pointeur vers un entier, on ´crit :
#include <stdlib.h>
int main() {
    int *p;
    p = (int*)malloc(sizeof(int)); // allocation dynamique
    *p = 14;
}

Il est primordial de bien comprendre qu’avant l’allocation dynamique (et
        e e
plus g´n´ralement avant toute initialisation de p), *p n’a aucun sens !
                                                        e e           e e
En particulier, toute manipulation de la variable *p g´n´rerait en g´n´ral une
            e        e        a     e
violation m´moire, d´tectable ` l’ex´cution par le message d’erreur Segmentation
fault.

                          e
La fonction malloc permet ´galement d’allouer un espace pour plusieurs objets
              e              e
contigus en m´moire. On peut ´crire par exemple
#include <stdlib.h>
#include <stdio.h>
int main() {
    int *p;
    p = (int*)malloc(2 * sizeof(int)); //allocation pour 2 int
    *p = 14;
    *(p + 1) = 10;
    printf("p = Ox%lx \t *p = %i \t p+1 = 0x%lx \t *(p+1)=%i\n",
            (unsigned long)p, *p, (unsigned long)(p+1), *(p+1));
    return 0;
}

         a                                       e                     e
L’appel ` la fonction malloc a ainsi permis de r´server 8 octets en m´moire
                                                              a
(qui permettent de stocker 2 objets de type int) et d’affecter ` p l’adresse de
            e
cette zone m´moire. Le programme affiche :
p = Ox8049718       *p = 14            p+1 = 0x804971c              *(p+1)=10.

                            e    o
La fonction calloc a le mˆme rˆle que la fonction malloc mais elle permet
de r´server nb-objets objets de nb octets octets et de les initialiser ` z´ro. Sa
    e                                                                  a e
syntaxe est :
                        calloc(nb-objets,nb octets)

Ainsi, si p est de type int*, l’instruction
p = (int*)calloc(N,sizeof(int));
     e                e
est s´mantiquement ´quivalente `   a
p = (int*)malloc(N * sizeof(int));
for (i = 0; i < N; i++)
  *(p + i) = 0;
L’emploi de calloc est simplement plus pratique et plus rapide.

                                        39
3.6       e
       Lib´ration dynamique avec la fonction free
                                             e          e
Lorsque l’on n’a plus besoin de l’espace-m´moire allou´ dynamiquement par
                          a                                                   e
malloc ou calloc (c’est-`-dire quand on n’utilise plus le pointeur p initialis´
                              e                e                    a
par ces fontions), il faut lib´rer ce bloc de m´moire. Ceci se fait ` l’aide de
l’instruction free qui a pour syntaxe :
                                free(nom-du-pointeur );

                      e                e       e     e
Cette instruction lib`re le bloc de m´moire d´sign´ par nom-du-pointeur mais
n’a pas d’effet si le pointeur a la valeur NULL.
                                                     e            e
A toute instruction de type malloc ou calloc doit ˆtre associ´e une instruction
de type free.
Attention :
                                     a      e                           e
– La fonction free peut aboutir ` un d´sastre si on essaie de lib´rer de la
    e                    ee       e
  m´moire qui n’a pas ´t´ allou´e par malloc ou calloc.
– free ne change pas le contenu du pointeur.
          e                   ee a
– Si la m´moire n’est pas lib´r´e ` l’aide free, alors elle l’est automatiquement
  a
  ` la fin du programme. Cependant, cela ne doit pas dispenser de l’utilisation
                                               ın´      e     e
  de cette fonction. L’exemple des listes chaˆ ees (d´taill´ au §4.3.5) est tout
  a            e           e                     ee a
  ` fait adapt´ : si la m´moire n’est pas lib´r´e ` chaque suppression d’un
  ee                                            a                     e
  ´l´ment de la liste, on aboutira rapidement ` une violation de m´moire et au
  traditionnel message d’erreur ”Segmentation fault”.




                                       40
Chapitre 4

           e e
Les types d´riv´s

                     e e                 e                                   e
A partir des types pr´d´finis en C (caract`res, entiers, flottants), on peut cr´er
                         e          e e                              e
de nouveaux types, appel´s types d´riv´s, qui permettent de repr´senter des
                   e         e
ensembles de donn´es organis´es.


4.1         e   e
        Les ´num´rations
    e     e                         e
Les ´num´rations permettent de d´finir un type pour des variables qui ne sont
      e
affect´es qu’a un nombre fini de valeurs.
                 e     e             e
Un objet de type ´num´ration est d´fini par le mot-clef enum et un identificateur
        e
de mod`le, suivi de la liste des valeurs que peut prendre cet objet :
          enum modele {constante1 , constante2 ,. . . ,constanten } ;

    e e                                         e    e
En r´alit´, les objets de type enum sont repr´sent´s comme des int. Les valeurs
                                                             e
possibles constante1 , constante2 ,. . . ,constanten sont cod´es par des entiers de
  a
0 ` n-1.
                                                                       a
Dans l’exemple suivant, le type enum boolean associe l’entier 0 ` la valeur
                     a
FALSE et l’entier 1 ` la valeur TRUE.

#include <stdio.h>
                               e                  e
enum boolean {FALSE, TRUE}; //d´finition de l’enum´ration boolean
int main () {
    enum boolean b1 = TRUE; //declaration
    printf("b = %d\n",b1);
    return 0;
}

                               e                                         e
On peut modifier le codage par d´faut des valeurs de la liste lors de la d´claration
        e   ee
du type ´num´r´, par exemple :
enum boolean {FALSE = 12, TRUE = 23};


4.2     Les tableaux
                                 ee           e              e      e
Un tableau est un ensemble fini d’´l´ments de mˆme type, stock´s en m´moire
a                     e
` des adresses contigu¨s.

                                        41
     e                          `                           c
La d´claration d’un tableau a une dimension se fait de la fa¸on suivante :
                                                  e e
                  type nom-du-tableau[nombre-´l´ments];
 u           ee                                      e
o` nombre-´l´ments est une expression constante enti`re positive correspondant
                         ee
au nombre maximal d’´l´ment dans le tableau. On l’appelle aussi la dimension
                                  ee
(ou taille) du tableau. Chaque ´l´ment du tableau est une composante de celui-
ci.
                   e
Par exemple, la d´claration int tab[10]; indique que tab est un tableau de
    ee                                                               e
10 ´l´ments de type int. En faisant le rapprochement avec les math´matiques,
                                                               e
on dit encore que ”t est un vecteur de dimension 10”. Cette d´claration alloue
            e                                                    e
donc en m´moire pour l’objet tab un espace de 10*4 octets cons´cutifs.
                   e                 e                  a
Pour plus de clart´, il est recommand´ de donner un nom ` la constante nombre-
ee                                 e
´l´ments par une directive au pr´processeur (voir chapitre 7), par exemple :

#define TAILLE 20      //noter l’absence de ";"
int t[TAILLE];         //t tableau de TAILLE int

                            a
Voici les points importants ` retenir :
           e a      ee                                       e
  1. On acc`de ` un ´l´ment du tableau en lui appliquant l’op´rateur [].
                     e                             a        ee
  2. les index des el´ments d’un tableau vont de 0 ` nombre-´l´ments-1
                                  e
  3. la taille d’un tableau DOIT ˆtre connue statiquement par le compilateur.
                          e
      Impossible donc d’´crire int t[n] ou n est une variable.
                                        ee
Ainsi, le programme suivant imprime les ´l´ments du tableau tab :
#define N 10
int main() {
   int tab[N];
   int i;
   ...
   for (i = 0; i < N; i++)
      printf("tab[%d] = %d\n",i,tab[i]);
   ...
}

                               a                             ee
Un tableau correspond en fait ` un pointeur vers le premier ´l´ment du tableau.
                                                                     e
Ce pointeur est constant, ce qui implique en particulier qu’aucune op´ration glo-
                  e
bale n’est autoris´e sur un tableau. Notamment, un tableau ne peut pas figurer
a                  e                                      e
` gauche d’un op´rateur d’affectation : on ne peut pas ´crire ”tab1 = tab2;”.
                                               ee
Il faut effectuer l’affectation pour chacun des ´l´ments du tableau.

4.2.1   Initialisation d’un tableau
                                           e
On peut initialiser un tableau lors de sa d´claration par une liste de constantes
        c
de la fa¸on suivante :
    type nom-du-tableau[N] = {constante1 ,constante2 ,. . . , constanteN } ;
Par exemple :
#define N 5
int t[N] = {14, 27, 3, 18, 81};
int main() {
   int i;

                                       42
    for (i = 0; i < N; i++)
       printf("t[%d] = %d\n",i,t[i]);
    ...
}

La figure 4.1 montre ce qu’il se passe en pratique au niveau de la m´moire e
lors de l’initialisation du tableau t. En supposant qu’une variable du type int
                          a                                             e
occupe 4 octets (c’est ` dire : sizeof(int)=4), alors le tableau t r´servera
                                 e
N ∗ 4 = 5 ∗ 4 = 20 octets en m´moire.

                                                             int t[5]={14,27,3,18,81};
                                      Adresse   32 bits
                                                                             (pointeur vers le premier
                                                                       t
                                       0x5E00                                 élément du tableau)

                                       0x5E04
               adresses croissantes




                                       0x5E08    14       t[0]
                                       0x5E0C    27       t[1]
                                       0x5E10     3       t[2]     t[i]=*(t+i)

                                       0x5E14    18       t[3]
                                       0x5E18    81       t[4]
                                       0x5E1C

                                       0x5E20




                                                  e
           Fig. 4.1 – Expression d’un tableau en m´moire physique

On peut donner moins de valeurs d’initialisations que le tableau ne comporte
  ee                                  ee                       e
d’´l´ments. Dans ce cas, les premiers ´l´ments seront initialis´s avec les valeurs
      e                                         e a e
indiqu´es, tandis que les autres seront initalis´es ` z´ro. Exemple :

#define N 10
int t[N] = {14,27};

    ee                                       e
Les ´l´ments d’indice 0 et 1 seront initialis´s respectivement avec les valeurs 14
                  ee                    a                    e a e
et 27, les autres ´l´ments (d’indices 2 ` 9) seront initialis´s ` z´ro.

 e
R´servation automatique
                                e
Si la dimension n’est pas indiqu´e explicitement lors de l’initialisation, alors le
             e                                              e
compilateur r´serve automatiquement le nombre d’octets n´cessaires. Exemple :

                              e
int A[] = {1, 2, 3, 4, 5}; //r´servation de 5*sizeof(int) octets
                                            e
float B[] = {-1.05, 3.3, 87e-5, -1.3E4}; //r´servation de
                                         //4*sizeof(float) octets

                                      e
Cas particulier des tableaux de caract`res
       e                           ıne        e              e
La repr´sentation interne d’une chaˆ de caract`res est termin´e par le symbole
nul ’\0’.
                                     e              e
Ainsi, pour un texte de n caract`res, il faut pr´voir n + 1 octets.

                                                            43
                         e          e         e
– un tableau de caract`res peut ˆtre initialis´ comme une liste de constantes
         e
  caract`res. Exemple : char ch[3] = {’a’, ’b’, ’c’};
                e                e
  Cela devient ´videmment tr`s vite lourd.
                                                e              ıne   e
– On peut aussi initialiser un tableau de caract`re par une chaˆ litt´rale :
                                                                       e
  char ch[8]="exemple"; La figure suivante montre le contenu de la m´moire
  a
  ` la suite de cette initialisation.
                    ch



                           ’e’    ’x’    ’e’    ’m’    ’p’    ’l’    ’e’    ’\0’
               Adresses:   1E04   1E05   1E06   1E07   1E08   1E09   1E0A   1E0B



             e                    e       a               ıne   e
– on peut d´clarer une taille sup´rieure ` celle de la chaˆ litt´rale :
  char ch[100]="exemple";
– enfin, on peut ne pas indiquer la taille et le compilateur comptera le nombre
            e            ıne    e
  de caract`res de la chaˆ litt´rale pour dimensionner correctement le tableau
                         e
  (sans oublier le caract`re null ’\0’). Exemple :
  char ch[]="ch aura 22 caract`res"; e
         e                                        e                        e
– Il est ´galement possible de donner une taille ´gale au nombre de caract`res
            ıne.
  de la chaˆ Dans le cas, le compilateur comprend qu’il ne faut pas rajouter
                       ıne.
  le null en fin de chaˆ Exemple : char ville[10]="luxembourg";
                                                      e
  Mais dans ce cas, attention aux surprises ! Consid´rer l’exemple suivant :
  #include <stdio.h>
  int main() {
      char t1[10]="luxembourg"; //sans ’\0’
      char t2[]="luxembourg"; //avec ’\0’
      printf("t1 (de taille %i)=%s\n",sizeof(t1)/sizeof(char),t1);
      printf("t2 (de taille %i)=%s\n",sizeof(t2)/sizeof(char),t2);
      return 0;
  }
  Ce programme renverra :
  t1 (de taille 10)=luxembourgߨP˚ @˘d@ ^ßXA^ßÆ-@
                                     u A a u      u
  t2 (de taille 11)=luxembourg
                                   e              e        a                e
  En effet, pour t1, toute la zone m´moire est affich´e jusqu’` ce que le carat`re
                   e
  nul soit rencontr´.


4.2.2   Tableaux multidimensionnels
                                                ee
En C, un tableau multidimensionnel est consid´r´ comme un tableau dont les
ee                   e                             a                    e
´l´ments sont eux-mˆme des tableaux. Un tableau ` deux dimensions se d´clare
                e
donc de la mani`re suivante :
              int mat[10][20];
                                            e
En faisant le rapprochement avec les math´matiques, on peut dire que mat est
                                                 e          e
une matrice de 10 lignes et de 20 colonnes. Les mˆmes consid´rations que celles
                  e      e
que nous avons d´velopp´ sur les tableaux unidimensionnels s’appliquent :
           e                                             e
  1. A la d´claration, le compilateur allouera une zone m´moire permettant de
                     e         u
     stocker de mani`re contig¨ e 10 tableaux de 20 entiers, soit 200 entiers ;

                                                44
             ee         e       `                                            e
  2. toute r´f´rence ult´rieure a mat sera convertie en l’adresse de sa premi`re
     ligne, avec le type pointeur vers un tableau de 20 int.
        e a      ee
On acc`de ` un ´l´ment du tableau par l’expression mat[i][j].
                            a                       a
Pour initialiser un tableau ` plusieurs dimensions ` la compilation, on utilise
                       ee
une liste dont chaque ´l´ment est une liste de constantes :

#include <stdio.h>
#define L 3   //nombre de lignes
#define C 2   //nombre de colonnes
short tab[L][C] = {{1, 2}, {14, 15}, {100, 200}};
int main() {
   int i, j;
   for (i = 0 ; i < L; i++) {
      for (j = 0; j < C; j++)
        printf("tab[%d][%d]=%d\n",i,j,tab[i][j]);
   }
   return 0;
}

             e
On vient de d´finir la matrice
                                                   
                                             1   2
                                    tab =  14 15 
                                            100 200

                                           e      a
La figure suivante montre le contenu de la m´moire ` la suite de cette initiali-
sation.
                           tab



                                     1      2     14     15     100    200

               Adresses:     1E04   1E08   1E0C   1E10   1E14   1E18   1E1C   1E20



                                                                     e
On comprend bien avec cette figure que si l’on souhaite utiliser la r´servation
                               e       e
automatique, il faudra quand mˆme sp´cifier toutes les dimensions sauf la pre-
  e
mi`re (le nombre de lignes dans le cas d’un tableau bi-dimentionnel.

short int mat[][3] = {{1, 0, 1},
                      {0, 1, 0}};

 e
r´servation de 2*3*2 = 12 octets et

                                                   1 0 1
                                    mat =
                                                   0 1 0

4.2.3                              e
        Passage de tableau en param`tre
                                                       a                     ee
Puisqu’un identificateur de type tableau correspond ` l’adresse du premier ´l´-
                                                                  e          e
ment du tableau (voir figure 4.1), c’est cette adresse qui est pass´e en param`tre
              e
formel. Consid´rons par exemple une fonction print_tab qui affiche le contenu
d’un tableau d’entiers :

                                                  45
#include <stdio.h>
/* Affiche le contenu du tableau d’entiers tab ayant nb_elem composantes */
void print_tab(int tab[], int nb_elem) {
    int i; //compteur
    for (i=0; i < nb_elem; i++) printf("tab[%i]=%i\n",i,tab[i]);
}
#define TAILLE 4
int main() {
    int t[TAILLE] = {1, 2, 3, 4};
    print_tab(t, TAILLE);
    return 0;
}
                                    e                                  e
Dans le prototype d’une fonction, l’´criture int tab[] est strictement ´quiva-
       a                      e                e e
lente ` int * tab (mais on pr´ferera la premi`re ´criture pour des raisons de
         e
lisibilit´).

                ee                        e         e
Modification des ´l´ments d’un tableau pass´ en param`tre
                                                    e
Puisque finalement on passe un pointeur en param`tre, on peut modifier au
                                      e
besoin le contenu du tableau. On peut ´crire par exemple :
#include <stdio.h>
void print_tab(int tab[], int nb_elem) {...}
       e
/* incr´mente chaque composantes d tableau */
void incr_tab(int tab[], int nb_elem) {
    int i;
    for (i=0; i < nb_elem; i++) tab[i]++;
}
#define TAILLE 4
int main() {
    int t[TAILLE] = {1, 2, 3, 4};
    incr_tab(t, TAILLE);
    print_tab(t, TAILLE);
    return 0;
}

                             ee                        e
Interdire la modification des ´l´ments d’un tableau pass´ en para-
  e
m`tre
                              e             e                           e
On utilise pour cela le mot-cl´ const qui sp´cifie que la variable associ´e ne
peut pas ˆtre modifi´e1 . Exemple :
         e          e
                                                          ^
const int i = 14; //cette variable ne pourra plus etre modifi´e         e

                           ee                          e
On voit tout de suite l’int´rˆt de const pour les param`tres de fonction. Ainsi,
            e                                                          e
dans la proc´dure print_tab, on peut exprimer le fait que cette proc´dure ne
                                            e          e
doit pas modifier le contenu du tableau pass´ en param`tre. Le prototype de la
     e
proc´dure deviendra pour cela :
void print_tab(const int tab[], int nb_elem);
   1
                     e                        e e     e
    En pratique, la m´moire n’est pas prot´g´e en ´criture et seule l’utilisation directe de la
                      e      e                                    e      a
variable constante empˆche l’´criture. Il est toujours possible d’´crire ` l’adresse de la variable
                 e      e
par des moyens d´tourn´s.


                                                46
4.2.4       Relation entre tableaux et pointeurs
                      a
Pointeurs et tableaux ` une dimension
On rappelle que tout tableau en C est en fait un pointeur constant.
            e                         e                                e
Ainsi, la d´claration int tab[10]; d´finit un tableau de 10 valeurs enti`res dont
les indices varient entre les valeurs 0 et 9 et tab est un pointeur constant
                                                           ee
(non modifiable) dont la valeur est l’adresse du premier ´l´ment du tableau.
Autrement dit, tab a pour valeur &tab[0]. On peut donc utiliser un pointeur
         ea                         ee
initialis´ ` tab pour parcourir les ´l´ments du tableau, comme dans l’exemple
suivant :

#define N 5
int tab[5] = {1, 2, 6, 0, 7};
int main() {
    int i;
    int *p;
    p = tab;
    for (i = 0; i < N; i++) {
        printf(" %d \n",*p);
        p++;
    }
    return 0;
}

                e a ee                                          a a
Comme on acc`de ` l’´l´ment d’indice i du tableau tab grˆce ` l’op´rateure
                                                    e
d’indexation [] par l’expression tab[i], on peut d´duire la relation entre cet
   e                          e
op´rateur d’indexation et l’op´rateur * :
                                  tab[i] = *(tab + i)
       e                 e
Pour r´sumer, on a les ´quivalences suivantes :
  tab + 1           ⇐⇒      &(tab[1])                    e     ee
                                          (adresse du 2i`me ´l´ment)
  *(tab + 1)        ⇐⇒      tab[1]                     e     ee
                                          (valeur du 2i`me ´l´ment)
  *tab              ⇐⇒      tab[0]                        ee
                                          (valeur du 1er ´l´ment)
  *(tab + k)        ⇐⇒      tab[k]                          e     ee
                                          (valeur du (k+1)`me ´l´ment)
  tab + k           ⇐⇒      &(tab[k])                         e    ee
                                          (adresse du (k+1)`me ´l´ment)
                                                           e          e
Pointeurs et tableaux se manipulent donc exactement de mˆme mani`re. Atten-
                                   o              e                     ee
tion cependant puisqu’aucun contrˆle n’est effectu´ pour s’assurer que l’´l´ment
                  e
du tableau adress´ existe effectivement. En outre, la manipulation de tableaux
     e                   e                 a                                 u
poss`de certains inconv´nients par rapport ` la manipulation des pointeurs dˆ s
au fait qu’un tableau est un pointeur constant :
                       e
– On ne peut pas cr´er de tableaux dont la taille est une variable du pro-
   gramme2 ;
                     e
– on ne peut pas cr´er de tableaux bidimensionnels dont les lignes n’ont pas
              e              ee
   toutes le mˆme nombre d’´l´ments.
       e                             e                                       e
Ces op´rations deviennent possibles d`s que l’on manipule des pointeurs allou´s
                                e                        a ee            u
dynamiquement. Ainsi, pour cr´er un tableau d’entiers ` n ´l´ments o` n est
                                e
une variable du programme, on ´crit :

#include <stdlib.h>
int main() {
  2
                                                                                     e
      En fait, on peut le faire depuis la norme C99 mais on ignorera cette possibilit´ ici.


                                                47
    int n;
    int *tab;
    ...
    tab = (int*)malloc(n * sizeof(int));
    ...
    free(tab);
}

                                ee                                      e a
Si on veut en plus que tous les ´l´ments du tableau tab soient initialis´s ` 0, on
remplace l’allocation dynamique avec malloc par :
   tab = (int*)calloc(n, sizeof(int));
On pourra aussi utiliser la fonction memset.
     ee                             e           e
Les ´l´ments de tab sont manipul´s avec l’op´rateur d’indexation [], exacte-
ment comme pour les tableaux.
                                                         e
Pour conclure, on retiendra que les deux principales diff´rence entre un tableau
et un pointeur sont les suivantes :
                              e           e
– un pointeur doit toujours ˆtre initialis´, soit par une allocation dynamique,
                       a
   soit par affectation ` une expression de type adresse, par exemple p = &i ;
                                                                   `
– un tableau est un pointeur constant ne peut donc pas figurer a gauche d’un
      e                                                         e         e
   op´rateur d’affectation. En particulier, un tableau ne pas ˆtre utilis´ direc-
                                      e                        e
   tement dans une expression arithm´tique (on ne peut pas ´crire tab++ ; par
   exemple).

                      a
Pointeurs et tableaux ` plusieurs dimensions
             a                         e                c
Un tableau ` deux dimensions peut ˆtre vu de deux fa¸ons :
                                           e
– soit comme un pointeur sur une zone m´moire de taille le produit des deux
                                         e
  dimensions, comme c’est le cas dans l’´criture :
           int tab[L][C];
               e                                    e     a
  Avec cette ´criture, tab a une valeur constante ´gale ` l’adresse du premier
  ee                         a
  ´l´ment du tableau, c’est ` dire &tab[0][0].
                                                ee
– soit comme un tableau de pointeurs. Chaque ´l´ment du tableau est alors lui-
    e                                      e
  mˆme un pointeur sur un tableau. On d´clare un pointeur qui pointe sur un
                                                   e         e
  objet de type type * (deux dimensions) de la mˆme mani`re qu’un pointeur,
        a              e
  c’est-`-dire par la d´claration :
  type **nom-du-pointeur ;
        e                                                          e
  De mˆme un pointeur qui pointe sur un objet de type type ** (´quivalent `   a
               a                     e
  un tableau ` 3 dimensions) se d´clare par
  type ***nom-du-pointeur ;
                                e                       a
L’exemple suivant illustre la d´claration d’un tableau ` deux dimensions (une
                                    a
matrice de k lignes et n colonnes ` coefficients entiers) sous forme d’un tableau
de pointeurs :
int main() {
    int k=14, n=5;
    int **tab; // pointeur vers la matrice

     tab = (int**)malloc(k * sizeof(int*));
     for (i = 0; i < k; i++)
         tab[i] = (int*)malloc(n * sizeof(int));

                                       48
    ....
    for (i = 0; i < k; i++)
        free(tab[i]);
    free(tab);
    return 0;
}

          e                          e                        e
La premi`re allocation dynamique r´serve pour l’objet point´ par tab l’espace-
  e                       a
m´moire correspondant ` k pointeurs sur des entiers. Ces k pointeurs corres-
                                                                        e
pondent aux lignes de la matrice. Les allocations dynamiques suivantes r´servent
                                         e        e
pour chaque pointeur tab[i] l’espace-m´moire n´cessaire pour stocker n entiers.
        e                          ee                                  e a
Si on d´sire en plus que tous les ´l´ments du tableau soient initialis´s ` 0, il
suffit d’utiliser la fonction calloc au lieu de malloc (voir §3.5 page 38)
                                                                               e
L’un des avantages des pointeurs de pointeurs sur les tableaux multi-dimensionn´s
                                                            e
est que cela permet de choisir par exemple des tailles diff´rentes pour chacune
des lignes tab[i].

4.2.5                           ınes de caract`res
         Cas des tableaux de chaˆ             e
On peut initialiser un tableau de ce type par :
char * jour[] = {"lundi", "mardi", "mercredi", "jeudi",
                     "vendredi", "samedi", "dimanche"};
                                    e
L’allure du tableau jour est illustr´ dans la figure 4.2.

          jour


                             ’l’   ’u’   ’n’   ’d’   ’i’   ’\0’

                             ’m’   ’a’   ’r’   ’d’   ’i’   ’\0’

                             ’m’   ’e’   ’r’   ’c’   ’r’   ’e’    ’d’    ’i’   ’\0’

                             ’j’   ’e’   ’u’   ’d’   ’i’   ’\0’

                             ’v’   ’e’   ’n’   ’d’   ’r’   ’e’    ’d’    ’i’   ’\0’

                             ’s’   ’a’   ’m’   ’e’   ’d’   ’i’    ’\0’

                             ’d’   ’i’   ’m’   ’a’   ’n’   ’c’    ’h’    ’e’ ’\0’


                               e                             ınes de caract`res
Fig. 4.2 – Illustration de la d´claration d’un tableau de chaˆ             e

                    e
A noter que cette ´criture directe n’est possible qu’avec les char. Il est impos-
        e
sible d’´crire par exemple :
int * tab[] = {{1}, {1,2}, {1,2,3}};
                                                                           e
(on a ici un tableau de pointeurs vers des tableaux d’entiers de taille diff´rentes).
                                                                  e
Une boucle d’impression des valeurs du tableau jour pourra s’´crire :

#define NB_JOUR 7
int i;
for (i=0 ; i < NB_JOUR; i++) printf("%s\n",jour[i]);

                                          49
4.2.6    Gestion des arguments de la ligne de commande
Les tableaux de pointeurs vers des chaˆ                  e
                                          ınes de caract`res sont une structure de
     e     e                                     e
donn´e tr`s importante puisqu’elle est utilis´e dans la transmission de para-
  e                e
m`tres lors de l’ex´cutution d’un programme.
                                e
Lorsqu’un utilisateur lance l’ex´cution du programme prog avec les param`tres  e
                                            e
param_1, param_2, . . . , param_n, l’interpr´teur collecte tous ces mots sous forme
       ıne           e       e
de chaˆ de caract`res, cr´e un tableau de pointeurs vers ces chaˆ     ınes et lance
        e                                        e
la proc´dure main en lui passant deux param`tres :
                                                    e
– un entier contenant la taille du tableau (appel´ classiquement argc) ;
– le tableau des pointeurs vers les chaˆ ınes (traditionnellement argv).
                                                   ee
Pour que le programmeur puisse exploiter ces ´l´ments, la fonction main doit
e     e             c
ˆtre d´finie de la fa¸on suivante :
int main(int argc, char * argv[]) {...}
                                               e
Voici un exemple d’utilisation de ces param`tres :

#include <stdio.h>
int main(int argc, char * argv[]) {
    int i;
    printf("Nom du programme: %s\n", argv[0]);
    for (i=1 ; i < argc ; i++)
                     e
        printf("Param`tre %i: %s\n",i,argv[i]);
    return 0;
}

En compilant ce programme par la commande gcc -Wall -g3 toto.c, l’appel
./a.out nif naf nouf
renvoit :

Nom du programme: ./a.out
     e
Param`tre 1: nif
     e
Param`tre 2: naf
     e
Param`tre 3: nouf

                             e              e
La librairie getopt.h permet ´galement de g´rer plus simplement les arguments
de la ligne de commande. Supposons par exemple vouloir compiler un pro-
                                           ee
gramme prog au format d’appel suivant (les ´l´ments entre crochets sont option-
nels) : prog [-i val_i] [-f val_f] [-s string] [-k key] [-h] file
sachant que :
                              e
   1. l’option -i permet de sp´cifier la valeur de la variable val_i, de type int
                e
      (14 par d´faut) ;
                              e
   2. l’option -f permet de sp´cifier la valeur de la variable val_f, de type
                      e
      float (1.5 par d´faut).
                              e                                              ıne
   3. l’option -s permet de sp´cifier la valeur de la variable string, une chaˆ
                e       e                          e
      de caract`res poss´dant au plus 256 caract`res (”On est super content !”
            e
      par d´faut).
                               e                    e
   4. l’option -k permet de sp´cifier au format hexad´cimal la valeur de la
                                                           e
      variable key, de type unsigned long (0x90ABCDEF par d´faut).

                                        50
   5. l’option -h affiche l’aide (auteur, but du programme et usage) et sort du
      programme.
                       e                                   e
   6. file est un param`tre obligatoire du programme qui sp´cifie la valeur de
            ıne        e
      la chaˆ de caract`res input_file.
        e                                                              e e
Pour g´rer ces options, on peut soit adapter l’exemple de code pr´c´dent et
                               ee
traiter individuellement les ´l´ments du tableau argv ainsi que les cas d’erreurs,
soit utiliser la librairie <getopt.h>3 qui permet de g´rer facilement les options
                                                      e
de la ligne de commande.
                         e
Cet exemple est trait´ avec <getopt.h> en annexe A.1 page 106.


4.3      Les structures
Une structure est une suite finie d’objets de types diff´rents. Ce m´canisme per-
                                                      e              e
                                                             e
met de grouper un certain nombre de variables de types diff´rents au sein d’une
  e        e                                        e      ee
mˆme entit´. Contrairement aux tableaux, les diff´rents ´l´ments d’une struc-
                        e                               e         e
ture n’occupent pas n´cessairement des zones contigu¨s en m´moire. Chaque
ee                             e                          e     e
´l´ment de la structure, appel´ membre ou champ, est d´sign´ par un identifi-
                                                   e               c
cateur. L’utilisation pratique d’une structure se d´roule de la fa¸on suivante :
                           e                      e          e    e e
   1. On commence par d´clarer la structure elle-mˆme. Le mod`le g´n´ral de
             e
      cette d´claration est le suivant :
            struct nom structure {
                type_1 membre1 ;
                type_2 membre2 ;
                ...
                type_n membren ;
            };
             e                                                        e  e e
   2. Pour d´clarer un objet de type structure correspondant au mod`le pr´c´-
      dent, on utilise la syntaxe :
        struct nom structure identificateur-objet ;
                          e                ee e      e      e
      Ou bien, si le mod`le n’a pas encore ´t´ d´clar´ au pr´alable :
            struct nom structure {
                type_1 membre1 ;
                type_2 membre2 ;
                ...
                type_n membren ;
            } identificateur-objet ;
              e           e                                a a        e
   3. On acc`de aux diff´rents membres d’une structure grˆce ` l’op´rateur
                                e         e                        e    e
      membre de structure, not´ ”.”. Le i-`me membre de objet est d´sign´ par
      l’expression :
                     identificateur-objet.membrei
                           e                                      e
On peut effectuer sur le i-`me membre de la structure toutes les op´rations
                    e
valides sur des donn´es de type type_i.
   3
                                                                           a
     informations : man 3 getopt ou plus simplement le tutorial disponible ` l’adresse :
http://www.stillhq.com/extracted/howto-getopt/output.ps


                                             51
                             e                                  e
Ainsi, le programme suivant d´finit la structure complexe, compos´e de deux
champs de type double et calcule la norme d’un nombre complexe.

#include <math.h>
struct complexe {
  double reelle;               e
                     //partie r´elle
  double imaginaire; //partie imaginaire
}; // ne pas oublier le ";"

int main() {
                        e
  struct complexe z; //d´claration d’un objet de type struct complexe
  double norme;
   ...
  norme = sqrt(z.reelle * z.reelle + z.imaginaire * z.imaginaire);
  printf("norme de (%f + i %f) = %f \n",z.reelle,z.imaginaire,norme);
}


4.3.1   Initialisation et affectation d’une structure
     e                                                  e                    e
Les r`gles d’initialisation d’une structure lors de sa d´claration sont les mˆmes
                             e
que pour les tableaux. On ´crit par exemple :
                             struct complexe i = {0. , 1.};
                      e                                            e
A noter qu’a la diff´rence des tableaux, on peut appliquer l’op´rateur d’affec-
                                             e e              e
tation aux structures. Dans le contexte pr´c´dent, on peut ´crire :

int main() {
  struct complexe z1, z2;
   ...
  z2 = z1;
}


4.3.2   Comparaison de structures
Aucune comparaison n’est possible sur les structures (en particulier, on ne peut
                e
appliquer les op´rateurs == ou !=).

4.3.3   Tableau de structures
     e                                                                  e
On d´clare un tableau dont les composantes sont des structures de la mˆme
  c          e                       ee
fa¸on qu’on d´clarerait un tableau d’´l´ments de type simple. Exemple :

struct personne {
   char nom[20];
   char prenom[20];
};
...
struct personne t[100]; //tableau de 100 personnes

      ee                                                  e
Pour r´f´rencer le nom de la personne qui a l’index i, on ´crira :
                                  t[i].nom

                                       52
4.3.4    Pointeur vers une structure
                                        e e            e
En reprenant la structure personne pr´c´dentes, on d´clarera une variable de
                                             e
type pointeur vers cette structure de la mani`re suivante :

struct personne *p;

                a
On peut affecter ` ce pointeur des adresses sur des struct personne. Exemple :

struct personne {...};
int main() {
   struct personne pers; // pers = variable de type struct personne
   struct personne * p; // p = pointeur vers une struct personne
   p = &pers; // p pointe maintenant vers pers
}

         e
Pour acc`der aux membres de la structure point´e :e
– il faudrait ´crire normalement (*p).nom
              e                             4;

                                e               e
– mais on utilise en pratique l’´criture simplifi´e p->nom.


4.3.5                     ee e
         Structures auto-r´f´r´es
Il s’agit de cas particulier de structures dont un des membres pointe vers
                      e                      e
une structure du mˆme type. Cette repr´sentation permet en particulier de
                          ı e
construire des listes chaˆn´es.
                                 e                  ee           e
En effet, il est possible de repr´senter une liste d’´l´ments de mˆme type par un
                                                 e                     e
tableau (ou un pointeur). Toutefois, cette repr´sentation, dite contigu¨, impose
que la taille maximale de la liste soit connue a priori (on a besoin du nombre
  ee                                                               e
d’´l´ments du tableau lors de l’allocation dynamique). Pour r´soudre ce pro-
  e                          e             ın´       ee
bl`me, on utilise une repr´sentation chaˆ ee : l’´l´ment de base de la chaˆ   ıne
                         e                                      ee
est une structure appel´e cellule qui contient la valeur d’un ´l´ment de la liste
                      ee                            ee
et un pointeur sur l’´l´ment suivant. Le dernier ´l´ment pointe sur le pointeur
         e                                           e
NULL (d´fini dans stddef.h). La liste est alors d´finie comme un pointeur sur
            ee
le premier ´l´ment de la chaˆ  ıne.
        e                                          e
Consid´rons par exemple la structure toto poss´dant 2 champs :
   1. un champ data de type int ;
   2. un champ next de type pointeur vers une struct toto.
La liste est alors un objet de type pointeur sur une struct toto. Grˆce au   a
                                 e
mot-clef typedef, on peut d´finir le type liste, synonyme du type pointeur
                                                     e
sur une struct toto (voir §4.6). On peut alors d´finir un objet liste l qu’il
                       a                  e                    e
convient d’initialiser ` NULL. Cette repr´sentation est illustr´e dans la figure 4.3.
                               e              ın´                 e             e
Un des avantages de la repr´sentation chaˆ ee est qu’il est tr`s facile d’ins´rer
   ee        a                                                      e       ee
un ´l´ment ` un endroit quelconque de la liste. Ainsi, pour ins´rer un ´l´ment
    e
en tˆte de liste, on utilise la fonction suivante :
   4
                                      ee                          e       e
     et non *p.nom qui sera interpr´t´ (compte tenu des priorit´s entre op´rateurs) par
*(p.nom) ce qui n’a de sens que si le champ nom est un pointeur !


                                          53
                                                     #include <stddef.h>
                                                     struct toto {
   l                                                      int data;
                                                          struct toto *next;
                 struct toto                          };
                 data 14                             typedef struct toto *liste;
                 next                                liste l = NULL;
                                                     //ajout d’éléments
                                                      ...

                                                      struct toto
                                                       data 10
                                                       next

                                                                                   NULL

                                                          ın´
                 Fig. 4.3 – Representation d’une liste chaˆ ee


liste insere(int element, liste Q) {
    liste L;
    L = (liste)malloc(sizeof(struct toto)); // allocation de la zone
                                                e         e
                                            // m´moire nec´ssaire
    L->data = element;
    L->next = Q;
    return L;
}

      c     e e                       ee                        ın´
De fa¸on g´n´ral, l’ajout d’un nouvel ´l´ment dans une liste chaˆ ee s’effectue
         e
en trois ´tapes :
                             u                           e       ee
  1. recherche de l’endroit o` la nouvelle cellule devra ˆtre ins´r´e ;
       e                                                  a
  2. cr´ation de la nouvelle cellule (par malloc) et mise ` jour de ses champs.
                                      e
     Au niveau de l’allocation en m´moire, on prendra garde :
               e                                                        e e
       (a) de r´server le bon nombre d’octets (en reprenant l’exemple pr´c´dent,
           sizeof(struct toto) et non sizeof(struct toto *))
                            e
       (b) de convertir le r´sultat du malloc vers le bon type (pointeur vers la
           structure).
          a
  3. mise ` jour des champs des cellules voisines.
                                            e      ee
Supposons par exemple que l’on souhaite ins´rer un ´l´ment en queue de liste :
liste insereInTail(int element, liste Q) {
    liste L, tmp=Q;
    // creation de la nouvelle cellule
    L = (liste)malloc(sizeof(struct toto)); // allocation de la zone
                                                e         e
                                            // m´moire nec´ssaire
    L->data = element;
    L->next = NULL;
    if (Q == NULL)
        return L;
    // maintenant Q est forcement non vide ==> champ tmp->next existe
                                    e
    // recherche de l’endroit ou ins´rer
                                                   e
    while (tmp->next != NULL) tmp = tmp->next; // d´placement jusqu’au
                                                   e e
                                        // dernier ´l´ment de la liste

                                       54
              a
      // mise ` jour des cellules voisines (ici, une seule: tmp)
      tmp->next = L;
      return Q;
}

                            ee
Pour supprimer le premier ´l´ment de la liste par exemple, il est important de
   e              e           e
lib´rer l’espace m´moire allou´ :
liste supprime_tete(liste L) {
    liste suivant = L;
                                   u
    if (L != NULL) { // pour etre s^r que L->next existe
       suivant= L->next;
                     e                        e
       free(L); //lib´ration de l’espace allou´ pour une cellule
    }
    return suivant;
}

                                                             ın´               e
Il est primordial d’utiliser free dans le cas des listes chaˆ ees. Sinon, la m´-
                e                    e
moire risque d’ˆtre rapidement satur´e (par exemple lors d’ajouts et de suppres-
                            e                        e              c
sions successives qui ne lib`rent pas l’espace allou´). Ainsi, de fa¸on similaire
a                                                         ın´
` l’ajout, la suppression d’une cellule dans une liste chaˆ ee s’effectue en trois
e
´tapes :
                               a
    1. Recherche de la cellule ` supprimer ;
            a
    2. mise ` jour des champs des cellules voisines ;
          e
    3. lib´ration de la cellule avec free.
                                                       ın´
Un exemple plus complet de l’utilisation des listes chaˆ ees est founi en an-
nexe A.2 page 110.


4.4      Les unions
                e                                                    e
Il est parfois n´cessaire de manipuler des variables auxquelles on d´sire affecter
                           e                    e
des valeurs de types diff´rents. Une union d´signe un ensemble de variables
              e                                                    e
de types diff´rents susceptibles d’occuper alternativement une mˆme zone m´-    e
                                      e
moire. Une union permet donc de d´finir un objet comme pouvant ˆtre d’un e
type au choix parmi un ensemble fini de types. Si les membres d’une union
                        e                e    e       e                   e
sont de longueurs diff´rentes, la place r´serv´e en m´moire pour la repr´senter
             a
correspond ` la taille du membre le plus grand.

4.4.1      e
          D´claration d’une union
    e                  e                           e
La d´claration et la d´finition d’une union ne diff`rent de celles d’une structure
                               e                               e
que par l’utilisation du mot-cl´ union (qui remplace le mot-cl´ struct).
                                                                    e
Dans l’exemple suivant, la variable hier de type union jour peut ˆtre soit un
      e                                      a
caract`re, soit un entier (mais pas les deux ` la fois) :
#include <stdio.h>
union jour {
    char lettre;
    int numero;

                                         55
};
int main() {
    union jour hier, demain;
    hier.lettre = ’J’; //jeudi
    printf("hier = %c\n",hier.lettre);
    hier.numero = 4;
    demain.numero = (hier.numero + 2) % 7;
    printf("demain = %d\n",demain.numero);
    return 0;
}

          ee                                     e            e
Si on se r´f`re au tableau 1.4 page 12, la zone m´moire allou´e pour une variable
de type union jour sera de sizeof(int) (2 ou 4 octets).
                            e       ee                             e      e
On aura compris qu’on acc`de aux ´l´ments d’une union avec le mˆme op´rateur
    e                                  e
de s´lection (. ou ->) que celui utilis´ dans les structures.

4.4.2   Utilisation pratique des unions
Lorsqu’il manipule des unions, le programmeur n’a malheureusement aucun
                 a                  e
moyen de savoir ` un instant donn´ quel est le membre de l’union qui poss`de  e
une valeur.
      e                                            e          e a
Pour ˆtre utilisable, une union doit donc toujours ˆtre associ´e ` une variable
dont le but sera d’indiquer le membre de l’union qui est valide. En pratique,
                              e          e e               e a       e
une union et son indicateur d’´tat sont g´n´ralement englob´s ` l’int´rieur d’une
structure. Exemple :
#include <stdio.h>
enum etat {CARAC, ENTIER};
/* struct jour a deux membres ’indicateur’ de type int,
   et ’day’ de type union de char et de int */
struct jour {
    enum etat indicateur; //indique ce qui est dans j
    union {
        char lettre;
        int numero;
    } day;
};
                                            e
/* Affiche le contenu de la variable d, nomm´e name */
void print_jour(struct jour d, char * name){
    if (d.indicateur == CARAC) printf("%s = %c\n",name,d.day.lettre);
    else if (d.indicateur == ENTIER)
          printf("%s = %i\n",name,d.day.numero);
    else printf("Erreur!\n");
}

int main() {
                             e
    struct jour ex1, ex2; //d´claration
    //utilisation
    ex1.indicateur = CARAC;
    ex1.day.lettre = ’j’; //jeudi
    print_jour(ex1, "ex1");
    ex2.indicateur = ENTIER;
    ex2.day.numero = 4;

                                       56
      print_jour(ex2, "ex2");
      return 0;
}

    e
L’ex´cution de ce programme renverra :
ex1 = j
ex2 = 4

4.4.3         e              e         e
         Une m´thode pour all´ger l’acc`s aux membres
Quand une union est dans une structure (comme c’est la cas dans l’exemple
  e e                                  e
pr´c´dent), on aura noter que l’acc`s aux membres de l’union est relativement
                                        e
lourd (ex : d.day.numero pour acc´der au membre numero). On peut all´ger        e
      e                                e       e
cette ´criture en utilisant les facilit´s du pr´processeur (voir le chapitre 7) :

#define L day.lettre
#define N day.numero
...
ex1.indicateur = CARAC;
                                     a
ex1.L = ’j’; //initialisation de ex1 ` jeudi


4.5      Les champs de bits
                          e
Il est possible en C de sp´cifier la longueur des champs d’une structure au bit
   e
pr`s si ce champ est de type entier (int ou unsigned int). Cela se fait en
   e                                                           e
pr´cisant le nombre de bits du champ avant le ”;” qui suit sa d´claration :
          type [membre] : nb_bits ;

                                                                       e
On utilise typiquement les champs de bits en programmation syst`me, pour
manipuler des registres particuliers de la machine etc... Par exemple, imaginons
le registre suivant :

                      ov           (inutilisé)                     privilege               masque

          Position:   15   14 13    12    11     10   9    8   7     6    5    4   3   2    1   0


           Fig. 4.4 – Exemple d’affectation de bits dans un registre

                     e                e
Ce registre peut se d´crire de la mani`re suivante :

struct registre {
      unsigned int         masque : 3;
      signed   int         privilege : 6;
      unsigned int         : 6;                      e
                                          /* inutilis´ */
      unsigned int         ov : 1;
};

                            e
Le champ masque sera cod´ sur 3 bits, privilege sur 6 bits etc... A noter
                                            e a       e                 e
que l’ordre dans lequel les champs sont plac´s ` l’int´rieur de ce mot d´pend
         e
de l’impl´mentation. On voit que le C accepte que l’on ne donne pas de nom

                                                      57
                                             e
aux champs de bits qui ne sont pas utilis´s. Le champ ov de la structure ne
peut prendre que les valeurs 0 ou 1. Aussi, si r est un objet de type struct
               e
registre, l’op´ration r.ov += 2 ; ne modifie pas la valeur du champ.
                               a
Voici les quelques contraintes ` respecter :
                                    e       e
– La taille d’un champ de bits doit ˆtre inf´rieure au nombre de bits d’un entier
  (long).
– un champ de bits n’a pas d’adresse ; on ne peut donc pas lui appliquer l’op´-e
  rateur &.


4.6     e
       D´finition de synonymes de types avec typedef
        e      e
Pour all´ger l’´criture des programmes, on peut affecter un nouvel identificateur
a         a                   e
` un type ` l’aide du mot-cl´ typedef :
                           typedef type synonyme ;
Exemple :

typedef unsigned char UCHAR;
typedef struct { double x, y } POINT;
typedef POINT *P_POINT; //pointeur vers un POINT

                                                    e        e
A partir de ce moment, l’identificateur UCHAR peut ˆtre utilis´ comme une ab-
  e                                                                 e
br´viation pour le type unsigned char, l’identificateur POINT peut ˆtre utilis´e
        e                                e                            e
pour sp´cifier le type de structure associ´ et P_POINT permet de caract´riser un
pointeur vers un POINT :

UCHAR c1, c2, tab[100];
POINT point;
P_POINT pPoint;




                                       58
Chapitre 5

Retour sur les fonctions

                                                                       a
La structuration de programmes en sous-programmes se fait en C ` l’aide de
                                         eae e                                e e
fonctions. L’utilisation de fonctions a d´j` ´t´ vu, par exemple avec celle pr´d´-
                         e
finies dans des biblioth`ques standards (comme printf de <stdio.h>, malloc
                                                       e
de <stdlib.h> etc...) ou encore la fonction d’entr´e main. Lorsque les listes
    ın´      ee         e                  e             e
chaˆ ees ont ´t´ abord´es au §4.3.5, on a ´galement d´fini des fonctions d’inser-
                           ee
tion et de suppression d’´l´ments.

On l’aura compris : comme dans la plupart des langages, on peut (doit ?) en
    e
C d´couper un programme en plusieurs fonctions. Il existe une unique fonction
                                          e
obligatoire : la fonction principale appel´e main.


5.1      e              e
        D´claration et d´finition d’une fonction
                                    e e           e
Comme on l’a vu au §1.4, le format g´n´ral d’une d´finition de fonction est de
la forme :

      type_resultat nom fonction (type1 arg1 , . . . , typen argn ) {
                 e
              <d´claration de variables locales >
              <liste d’instructions >
      }

             e
Dans cette d´finition de fonction, on rappelle que :
                                           e
– type_resultat correspond au type du r´sultat de la fonction.
– nom fonction est le nom qui identifie la fonction.
                               e                                      e
– type1 arg1 . . . typen argn d´finit les types et les noms des param`tres de
                                         e        e
  la fonction. Ces arguments sont appel´s param`tres formels, par opposition
             e                                e
  aux param`tres effectifs qui sont les param`tres avec lesquels la fonction est
                       e
  effectivement appel´e (voir §5.2).
                           e
On peut se contenter de d´clarer le prototype d’une fonction (sans le corps) :
      type_resultat nom fonction (type1 arg1 , . . . , typen argn ) ;

                 e
Dans ce cas, la d´finition de la fonction (avec le corps des instructions qui la

                                       59
compose) peut ˆtre d´clar´e plus loin dans le programme1 .
                e     e   e
                a     e
Contrairement ` la d´finition de la fonction, le prototype n’est donc pas suivi
                                                    a e
du corps de la fonction (contenant les instructions ` ex´cuter).
ATTENTION ! Le prototype est une instruction, il est donc suivi d’un point-
virgule !
                           e
Quelques remarques compl´mentaires :
                                         e
   1. Une fonction peut fournir comme r´sultat :
                        e
      – un type arithm´tique,
      – une structure (voir §4.3),
      – une union (voir §4.4),
      – un pointeur (voir chapitre 3),
                          e
      – void (voir ci-apr`s).
                                                 e
      Une fonction ne peut pas fournir comme r´sultat des tableaux, des chaˆınes
                e
      de caract`res ou des fonctions, mais il est cependant possible de renvoyer
                                   ee                                 ıne
      un pointeur sur le premier ´l´ment d’un tableau ou d’une chaˆ de ca-
          e
      ract`res.
                                         e
   2. Si une fonction ne fournit pas de r´sultat, il faut indiquer void comme
               e
      type du r´sultat.
      Ex : void print_liste(liste L){...}
                                      e               e
   3. Si une fonction n’a pas de param`tres, on peut d´clarer la liste des para-
        e
      m`tres comme (void) ou simplement comme ().
      Ex : int main(){...} // equivalent a ’int main(void){...}’
                          e                   a      e
   4. Il est interdit de d´finir des fonctions ` l’int´rieur d’une autre fonction
      (comme en Pascal).
                                 e
   5. En principe, l’ordre des d´finitions de fonctions dans le texte du pro-
                               o                             e     e   e
      gramme ne joue pas de rˆle, mais chaque fonction doit ˆtre d´clar´e ou
       e             e         e
      d´finie avant d’ˆtre appel´e.
              u                        e
Dans le cas o` type_resultat est diff´rent du type void, le corps de la fonc-
tion doit obligatoirement comporter une instruction return, dite instruction
          `
de retour a la fonction appelante. La syntaxe de cette instruction est :
                                 return expression ;

                                      a
La valeur de expression correspond ` la valeur que retourne la fonction et doit
      e
donc ˆtre de type type_resultat.
                                               ıtre
Plusieurs instructions return peuvent apparaˆ dans une fonction. Le retour
                                             e
au programme appelant sera alors provoqu´ par le premier return rencontr´     e
            e
lors de l’ex´cution (voir la fonction puissance de l’exemple qui suit). Ex :

/***Renvoit le produit de 2 entiers ***/
int produit (int a, int b) {
  return(a*b);
}
                                         ı
/*** Renvoit la valeur de a^n (version na¨ve) ***/
int puissance (int a, int n) {
   1
                 e                           e                            e          a
     mais cette d´finition doit respecter la d´claration du prototype : n’h´siter pas ` utiliser le
copier/coller du prototype !


                                               60
    if (n == 0) return 1;
    return a*puissance(a, n-1); //noter l’absence du ’else’
}

                                       e                e    e
Enfin, il faut noter que les variables ´ventuellement d´clar´es au sein d’une fonc-
                    a
tion seront locales ` celle-ci et n’auront de sens que dans le corps de la fonction.
      e
De mˆme, les identificateurs des arguments de la fonction (arg1 ,. . . ,argn ) n’ont
                  a      e
d’importance qu’` l’int´rieur de celle-ci.


5.2      Appel d’une fonction
L’appel d’une fonction se fait par l’expression :
                           nom fonction ( arg1 , . . . ,argn )

                             e
L’ordre et le type des param`tres effectifs de la fonction doivent concorder avec
           e          e                e                            e
ceux donn´s dans la d´finition de l’en-tˆte de la fonction. Les param`tres effectifs
peuvent ˆtre des expressions
         e                   2.

                   e                     e                            e    e
De plus, l’ordre d’´valuation des param`tres effectifs n’est pas assur´ et d´pend
                                e        e                      a
du compilateur. Il est donc d´conseill´, pour une fonction ` plusieurs para-
  e                             e              e                   e e
m`tres, de faire figurer des op´rateurs d’incr´mentation ou de d´cr´mentation
                                  e                     e
(++ ou --) dans les expressions d´finissant les param`tres effectifs.


5.3         e
         Dur´e de vie des identificateurs
                     e                                                e
Les variables manipul´es dans un programme C ne sont pas toutes trait´es de
    e         e                                             e      e
la mˆme mani`re. En particulier, elles n’ont pas toutes la mˆme dur´e de vie.
                     e
On distingue deux cat´gories de variables.
    1. Les variables permanentes (ou statiques) Une variable permanente occupe
                               e                     e
       un emplacement en m´moire qui reste le mˆme durant toute l’ex´cutione
                                                   e
       du programme. Cet emplacement est allou´ une fois pour toutes lors de la
                                       e
       compilation. La partie de la m´moire contenant les variables permanentes
                 e                     e         e
       est appel´e segment de donn´es. Par d´faut, les variables permanentes
                     e a e                                           e e
       sont initialis´es ` z´ro par le compilateur. Elles sont caract´ris´es par le
       mot-clef static.
                                                                      e
    2. Les variables temporaires se voient allouer un emplacement en m´moire de
         c                          e
       fa¸on dynamique lors de l’ex´cution du programme. Elles ne sont pas ini-
             e        e                              e          ee
       tialis´es par d´faut. Leur emplacement en m´moire est lib´r´ par exemple
       a               e
       ` la fin de l’ex´cution d’une fonction secondaire.
     e                                         e                           e
Par d´faut, les variables temporaires sont situ´es dans la partie de la m´moire
     e                                                                        e
appel´e segment de pile. Dans ce cas, la variable est dite automatique. Le sp´ci-
                                                          e
ficateur de type correspondant, auto, est rarement utilis´ puisqu’il ne s’applique
                                                            e
qu’aux variables temporaires qui sont automatiques par d´faut.
   2
                     e               e
     La virgule qui s´pare deux param`tres effectifs est un simple signe de ponctuation ; il ne
                  e
s’agit pas de l’op´rateur virgule.


                                             61
                                e          e        e
Une variable temporaire peut ´galement ˆtre plac´e dans un registre de la ma-
                                   e                                 e
chine. Un registre est une zone m´moire sur laquelle sont effectu´es les op´ra- e
                                                          e    `
tions machine. Il est donc beaucoup plus rapide d’acc´der a un registre qu’`     a
                            e
toute autre partie de la m´moire. On peut demander au compilateur de ran-
                     e      e                      a
ger une variable tr`s utilis´e dans un registre, ` l’aide de l’attribut de type
register.
                          e          e             e
Le nombre de registres ´tant limit´, cette requˆte ne sera satisfaite que s’il
                                                                     ee
reste des registres disponibles. Cette technique permettant d’acc´l´rer les pro-
                                            ee      a
grammes a aujourd’hui perdu tout son int´rˆt. Grˆce aux performances des op-
                       e e
timiseurs de code int´gr´s au compilateur (cf. options -O de gcc : voir §1.1.4),
il est maintenant plus efficace de compiler un programme avec une option d’op-
timisation que de placer certaines variables dans des registres.
        e                           e a           e         a      a
La dur´e de vie des variables est li´e ` leur port´e, c’est-`-dire ` la portion du
                                       e
programme dans laquelle elles sont d´finies.


5.4         e
        Port´e des variables
                 u     e                                    e
Selon l’endroit o` on d´clare une variable, celle-ci pourra ˆtre accessible (visible)
                                                                  a     e
partout dans le code ou seulement dans une partie de celui-ci (` l’int´rieur d’une
                                             e               e
fonction typiquement), on parle de la port´e (ou visibilit´) de la variable.
                           e    e                    e          a      a
Lorsqu’une variable est d´clar´e dans le code mˆme, c’est-`-dire ` l’ext´rieur e
de toute fonction ou de tout bloc d’instruction, elle est accessible de partout
dans le code (n’importe quelle fonction du programme peut faire appel ` cette a
variable). On parle alors de variable globale.
               e                   a      e
Lorsque l’on d´clare une variable ` l’int´rieur d’un bloc d’instructions (entre des
                    e           a      e                                     e    e
accolades), sa port´e se limite ` l’int´rieur du bloc dans lequel elle est d´clar´e.
On parle de variable locale.

5.4.1    Variables globales
                                             e   e
Un variable globale est donc une variable d´clar´e en dehors de toute fonction,
     e                a      e
au d´but du fichier, ` l’ext´rieur de toutes les fonctions et sont disponibles ` a
                                         e e                                e
toutes les fonctions du programme. En g´n´ral, les variables globales sont d´cla-
 e        e                e                                e
r´es imm´diatement derri`re les instructions #include au d´but du programme.
               e
Elles sont syst´matiquement permanentes.
                             e   e       e
Remarque : Les variables d´clar´es au d´but de la fonction principale main ne
                                               a
sont pas des variables globales, mais locales ` main !
Exemple :
                          e    e                            e         e
La variable STATUS est d´clar´e globalement pour pouvoir ˆtre utilis´e dans les
     e
proc´dures A et B.

#include <stdio.h>
int STATUS;

void A(...){
    ...
    if (STATUS>0)
        STATUS--;

                                         62
         else
             ...
         ...
}

void B(...) {
    ...
    STATUS++;
    ...
}

Conseils :
                                a                e                       e
– Les variables globales sont ` utiliser avec pr´caution, puisqu’elles cr´ent des
                                                     e
  liens invisibles entre les fonctions. La modularit´ d’un programme peut en
  souffrir et le programmeur risque de perdre la vue d’ensemble.
                          a
– Il faut faire attention ` ne pas cacher involontairement des variables globales
                                    e
  par des variables locales du mˆme nom. (voir section suivante)
                                  e
– Je vous conseille donc d’´crire nos programmes aussi ’localement’
  que possible.


5.4.2        Variables locales
                e    e                                                       a
Les variables d´clar´es dans un bloc d’instructions sont uniquement visibles `
     e                                                           a
l’int´rieur de ce bloc. On dit que ce sont des variables locales ` ce bloc.
                                  e                                   e
Exemple : la variable nom est d´finie localement dans le bloc ext´rieur de la
                                                      e a
fonction hello. Ainsi, aucune autre fonction n’a acc`s ` la variable nom :

void hello() {
    char nom[20];
    printf("Introduisez votre nom : ");
    scanf("%s",&nom);
    printf("Bonjour %s !\n", nom);
}

                          e    e `       e
Attention ! Une variable d´clar´e a l’int´rieur d’un bloc cache toutes les va-
            e
riables du mˆme nom des blocs qui l’entourent. Exemple :

          e
int X; //d´claration d’une variable globale
int fonction(int A) {
    double X; //variable locale qui cache la variable X globale
    ...
}

Remarque : bien qu’il soit possible en C3 de d´clarer des variables ` l’int´rieur
                                                e                      a     e
                                                e        e
d’une boucle ou d’un bloc conditionnel, il est d´conseill´ de le faire et toutes les
 e                                        e
d´clarations locales devront se faire au d´but des fonctions.
    3
        depuis la norme C99 en fait


                                        63
5.5      e        e
        R´cursivit´
     e              e                                       e
Les d´finitions par r´currence sont assez courantes en math´matiques. Par
                       e
exemple, on peut consid´rer la fameuse suite de Fibonacci :
                       
                        u0 = 0
                       
                         u =1
                        1
                         un = un−1 + un−2 ∀ n ≥ 2
                       

               e                         ee     e        e       a                e
Le fonctions d´finies en C ont la propri´t´ de r´cursivit´, c’est ` dire la capacit´
                   e
de s’appeler elle-mˆme.
          e             e        e     ee
Tous les d´tails de la r´cursivit´ ont ´t´ fournis au §2.3 page 24.


5.6                     e     a
        Passage de param`tres ` une fonction
           e                               ıtes
Les param`tres ou arguments sont les ’boˆ aux lettres’ d’une fonction. Elles
                  e           e          e                             e
acceptent les donn´es de l’ext´rieur et d´terminent les actions et le r´sultat de
la fonction.

5.6.1     e e     e
         G´n´ralit´s
                                         e     a          e
En ce qui concerne le passage de param`tres ` une proc´dure, le programmeur
a deux besoins fondamentaux :
           e                                       e
– soit il d´sire passer une valeur qui sera exploit´e par l’algorithme de la pro-
   e                                             e
  c´dure (c’est ce dont on a besoin quand on ´crit par exemple sin(x)). Une
          c                       e
  telle fa¸on de passer un param`tre s’appelle du passage par valeur ;
            e                 ee       a                          e a
– soit il d´sire passer une r´f´rence ` une variable, de mani`re ` permettre
  ` la proc´dure de modifier la valeur de cette variable. C’est ce dont on a
  a           e
                     e              e       e
  besoin quand on ´crit une proc´dure r´alisant le produit de deux matrices
                       u                         e
  prodmat(a,b,c) o` l’on veut qu’en fin d’ex´cution, la matrice c soit ´gale  e
  au produit matriciel des matrices a et b. prodmat a besoin des valeurs des
                             ee                                      c
  matrices a et b, et d’une r´f´rence vers la matrice c. Une telle fa¸on de passer
              e
  un param`tre s’appelle du passage par adresse.

Conversion automatique
                                                 e              e
Lors d’un appel, le nombre et l’ordre des param`tres doivent n´cessairement
                                    e                                   e
correspondre aux indications de la d´claration de la fonction. Les param`tres
                                                       e                 e
sont automatiquement convertis dans les types de la d´claration avant d’ˆtre
    e a
pass´s ` la fonction.
                                                   e                   e    e
Exemple : Le prototype de la fonction pow (biblioth`que <math.h>) est d´clar´
comme suit :
double pow(double x, double y);
Au cours des instructions,

   int A, B;
   ...
   A = pow (B, 2);

                                        64
           a                                           e              a
On assiste ` trois conversions automatiques : avant d’ˆtre transmis ` la fonction,
la valeur de B est convertie en double ; la valeur 2 est convertie en 2.0 . Comme
                             e                          e
pow est du type double, le r´sultat de la fonction doit ˆtre converti en int avant
  e          e a
d’ˆtre affect´e ` A.

5.6.2                    e
        Passage des param`tres par valeur
                             e
En C, le passage des param`tres se fait toujours par valeur, autrement dit les
                                                       e                     e
fonctions n’obtiennent que les valeurs de leurs param`tres et n’ont pas d’acc`s
                       e
aux variables elles-mˆmes.
            e                          a         e
Les param`tres d’une fonction sont ` consid´rer comme des variables locales
                  e                                          e
qui sont initialis´es automatiquement par les valeurs indiqu´es lors d’un appel.
        e                                                                  e
A l’int´rieur de la fonction, On peut donc changer les valeurs des param`tres
sans influencer les valeurs originales dans les fonctions appelantes.
Exemple :
                                               e               e
La fonction ETOILES dessine une ligne de N ´toiles. Le param`tre N est modifi´ e
a      e                              a      e
` l’int´rieur de la fonction mais pas ` l’ext´rieur :
#include <stdio.h>

void ETOILES(int N) {
    while (N>0) {
       printf("*");
       N--;
    }
    printf("\n");
}

int main() {
    int compteur=14;
    ETOILES(compteur);
    printf("Valeur de compteur: %i\n",compteur);
    return 0;
}

L’appel de ce programme renvoit :

**************
Valeur de compteur: 14

                                   ee       e
la valeur de compteur n’a donc pas ´t´ modifi´ par l’appel de la fonction
ETOILES.

5.6.3                    e
        Passage des param`tres par adresse
                                         e            e                   e
Comme on vient de le voir, tout param`tre est pass´ par valeur, et cette r`gle
                                                  e        e
ne souffre aucune exception. Cela pose le probl`me de r´aliser un passage de
       e
param`tre par adresse lorsque le programmeur en a besoin.
Pour changer la valeur d’une variable de la fonction appelante, on proc`de e
comme suit :
– la fonction appelante doit fournir l’adresse de la variable ;

                                       65
                    e        e                e
– la fonction appel´e doit d´clarer le param`tre comme pointeur.
                                     a
On peut alors atteindre la variable ` l’aide du pointeur.
                              e    e                e
Exemple : Supposons qu’on d´sire ´crire une proc´dure add, admettant trois pa-
    e                   e             e               e
ram`tres a, b et c. On d´sire que le r´sultat de l’ex´cution de add soit d’affecter
          e
au param`tre c la somme des valeurs des deux premiers param`tres. e
          e             e                  e          e
Le param`tre c ne peut ´videmment pas ˆtre pass´ par valeur, puisqu’on d´siree
                            e
modifier la valeur du param`tre effectif correspondant. Il faut donc programmer
               e
add de la mani`re suivante :

void add(int a, int b, int *c) {
        e             u                    e
/* c rep`re l’entier o` on veut mettre le r´sultat   */
   *c = a + b;
}
int main() {
  int i=10,j=14,k;
                                                        e
  /* on passe les valeurs de i et j comme premiers param`tres */
                                         e        e
  /* on passe l’adresse de k comme troisi`me param`tre        */
  add(i,j,&k);
}


5.6.4                                 e
           Passage de tableau en param`tre
                                                                 a
Comme il est impossible de passer ’la valeur’ de tout un tableau ` une fonction,
on fournit l’adresse d’un ´l´ment du tableau4 .
                          ee
     e e                                    ee
En g´n´ral, on fournit l’adresse du premier ´l´ment du tableau, qui est donn´ee
par le nom du tableau.
                        e                              e
Dans la liste des param`tres d’une fonction, on peut d´clarer un tableau par le
nom suivi de crochets,
           <type> <nom>[]
                                                ee
ou simplement par un pointeur sur le type des ´l´ments du tableau :
           <type> *<nom>
       ee              e e
On pr´f`rera la premi`re ´criture car il n’est pas possible dans la seconde de
                                                   e
savoir si le programmeur a voulu passer en param`tre un pointeur vers <type>
       a
(c’est ` dire un pointeur vers un seul <type>), ou au contraire si il a voulu
                         a
passer un tableau, c’est ` dire un pointeur vers une zone de n <type>.
Exemple :

#include <stdlib.h>

/* Initialise          e e
                   les ´l´ments d’un tableau */
void init_tab      (int tab[], int n){
  int i;
  for (i = 0;      i < n; i++)
     tab[i] =      i;
  return;
}

int main() {
  4
                                                                       ee
      Rappelons qu’un tableau est un pointeur constant (sur le premier ´l´ment du tableau)


                                             66
    int i, n = 5;
    int *tab;
    tab = (int*)malloc(n * sizeof(int));
    init(tab,n);
}

                                            e                          a
Remarque : Quand une fonction admet un param`tre de type tableau, il y `
deux cas possibles :
                    e                               e           e
    1. soit les diff´rents tableaux qui lui sont pass´s en param`tre effectif ont des
                   e                                     e            e          e
       tailles diff´rentes, et dans ce cas la taille doit ˆtre un param`tre suppl´-
                                                            e e
       mentaire de la fonction, comme dans l’exemple pr´c´dent ;
                   e                                 e           e
    2. soit les diff´rents tableaux qui lui sont pass´s en param`tre effectif ont
                 e                                              ıtre
       tous la mˆme taille, et dans ce cas la taille peut apparaˆ dans le type
                 e
       du param`tre effectif :
       #define NB_ELEM 10
       void init_tab (int tab[NB_ELEM]){{
          ...
       }


5.7               a                         e
         Fonction ` nombre variable de param`tres
                     e
Il est possible de d´clarer une fonction comme ayant un nombre variable de
       e          e                   e                          e
param`tres en ”d´clarant” les param`tres optionnels par l’unit´ lexicale ”...”
           a                                     a                  e
(3 points ` la suite). Une fonction peut avoir ` la fois des param`tres obliga-
                    e                          e
toires et des param`tres optionnels, les param`tres obligatoires apparaissant en
                 e                                     e
premier et l’unit´ lexicale ”...” apparaissant en derni`re position dans la liste
     e                     e
de d´claration des param`tres formels.
                                                                e
Dans le corps de la fonction on ne dispose pas de nom pour d´signer les para-
   e           e a
m`tres. L’acc`s ` ceux-ci ne peut se faire qu’en utilisant les macros suivantes
  e                      e
d´finies dans la biblioth`que standard <stdarg.h> :
                    e                                               a
va list permet de d´clarer une variable opaque au programmeur, ` passer en
            e
      param`tre aux autres macros. Cette variable s’appelle traditionnellement
                                                      e             e
      ap (pour argument pointer), et a pour but de rep´rer le param`tre effectif
      courant.
              e         e
va start doit ˆtre appel´e avant toute utilisation de va_arg. La macro va_start
                    e                                                 e
     a deux param`tres : la variable ap et le nom du dernier param`tre obli-
     gatoire de la fonction.
          e             e                                      a         e
va arg d´livre le param`tre effectif courant : le premier appel ` va_arg d´livre
                      e                               a           e
     le premier param`tre, puis chaque nouvel appel ` va_arg d´livre le para-
       e                                                   e
     m`tre suivant. La macro va_arg admet deux param`tres : la variable ap
                          e
     et le type du param`tre courant.
            e         e     e
va end doit ˆtre appel´e apr`s toutes les utilisations de va_arg. La macro
                                e
     va_end admet un seul param`tre : la variable ap.
             e                       a
Rien n’est pr´vu pour communiquer ` la fonction le nombre et le type des pa-
   e                      e                  e   a
ram`tres effectivement pass´s : c’est un probl`me ` la charge du programmeur.

                                        67
                       e                            e
Exemple Voici la d´finition d’une fonction de d´buggage sur le mod`le de  e
printf admettant un nombre variable d’arguments. L’affichage ne s’effectue
                   e ea                                            e      a
que si le niveau sp´cifi´ ` l’appel (par le parametre level) est inf´rieur ` une
variable globale DEBUG_LEVEL.

#define DEBUG_LEVEL
...
/**
 * Fonction de debuggage
 */
int trace (int level, char * chaine, ...) {
    va_list args;//les arguments du println
    int length = 0;
    va_start(args, chaine);//initialisation de la structure va_list a partir
                           //d’un parametre d’appel de la fonction
    if (level <= DEBUG_LEVEL) {
      while (chaine[length]!=0) {
        //traitement des argument %...
        if (chaine[length] == ’%’) {
          length++;//on passe au caractere suivant
          switch (chaine[length]) {
          case ’d’:
            printf("%d", va_arg(args, long));
            break;
          case ’i’:
            printf("%i", va_arg(args, long));
            break;
          case ’x’:
            printf("%x", va_arg(args, unsigned long));
            break;
          case ’X’:
            printf("%X", va_arg(args, unsigned long));
            break;
          case ’s’:
            printf("%s", va_arg(args, char *));
            break;
          }
        } else {
          //on a une chaine classique donc on l’affiche
          printf("%c", chaine[length]);
        }
        length++;
      }
      //on termine correctement l’utilisation d’une structure va_list
      va_end(args);
      printf("\n");//on va a la ligne!
      return 0;
    }
}

                                                             a
Une utilisation classique d’une telle fonction, on associera ` l’affichage d’infor-
                          e                        e e
mations de debuggage pr´cises un niveau level ´lev´ tandis que les affichages
                           e
de base auront un param`tre level faible. Ensuite, c’est l’utilisateur qui en

                                       68
                                                         e         e
faisant varier la valeur de DEBUG_LEVEL modifie la quantit´ et la pr´cision des
                    e a      e
informations affich´es ` l’ex´cution. Exemple :
/**
  * Insertion d’une valeur val dans un tableau tab de taille n
  * contenant n-1 premiers elements tries
  */
void insertion(int tab[], int n, int val) {
     int i=n;
     trace(1, "Debut de insertion - param n=%i, val=%i",n,val);
     trace(2, " Debut decalage des elements");
     while( i!=0 && val < tab[i-1]) {
         trace(3, "Decalage de tab[%i] en position %i",i-1,i);
         tab[i] = tab[i-1];
         i--;
     }
     trace(2, " Fin decalage des elements");
     trace(2, " Insertion de la valeur %i en position %i",val,i)
     tab[i] = val;
     trace(1, "Fin de insertion");
}
A l’appel de cette fonction, on aura le comportement suivant :
– si DEBUG_LEVEL = O : pas d’affichage ;
                               e
– si DEBUG_LEVEL = 1 : l’entr´e et la sortie de la fonction sera visible par un
                 e
  message affich´. Cela permet de savoir rapidement en cas de bug quelle fonc-
                   e
  tion pose probl`me ;
                                                      e
– si DEBUG_LEVEL = 2, on aura en plus l’affichage des ´tapes dans l’algorithme
        e
  utilis´ pour la fonction ;
                                           e
– si DEBUG_LEVEL > 2, on aura tous les d´tails possibles.


5.8    Pointeurs sur une fonction
                                                           e
Il est parfois utile de passer une fonction comme param`tre d’une autre fonc-
                  e                                            e
tion. Cette proc´dure permet en particulier d’utiliser une mˆme fonction pour
    e                                        e
diff´rents usages. Pour cela, on utilise un m´canisme de pointeur.
                                            a                e
Un pointeur sur une fonction correspond ` l’adresse du d´but du code de la
fonction. Un pointeur p_fonction sur une fonction ayant pour prototype
type fonction(type_1 arg_1,...,type_n arg_n);
     e
se d´clare par :
type (*p_fonction)(type_1,...,type_n);
              e
Ainsi, consid´rons une fonction operateur_binaire prenant pour param`tres   e
deux entiers a et b, ainsi qu’une fonction f de type int qui prend elle-mˆme e
                       e
deux entiers en param`tres. On suppose que operateur_binaire renvoit l’entier
f(a,b).
Le prototype de cette fonction sera :
int operateur_binaire(int a, int b, int (*f)(int, int))
Pour appeler la fonction operateur_binaire, on utilisera comme troisi`me     e
       e                                               e
param`tre effectif l’identificateur de la fonction utilis´e. Par exemple, si somme
est la fonction

                                      69
int somme(int a, int b) { return a+b; }
on appelle la fonction operateur_binaire pour la fonction somme par l’expres-
sion :
operateur_binaire(a,b,somme)
                                               e                e
A noter que la notation &somme n’est pas utilis´e comme param`tre effectif.
                              e           e
Pour appeler la fonction pass´e en param`tre dans le corps de la fonction
                         e                                               e
operateur_binaire, on ´crit (*f)(a, b). Ainsi, cette fonction pourrait s’´crire :

int operateur_binaire(int a, int b, int (*f)(int, int)) {
  return((*f)(a,b));
}

                                                      e
Les pointeurs sur les fonctions sont notamment utilis´s dans la fonction de tri
    ee                                                         ee
des ´l´ments d’un tableau qsort et dans la recherche d’un ´l´ment dans un
                                              e
tableau bsearch. Ces deux fonctions sont d´finies dans la librairie standard
<stdlib.h>.
Le prototype de la fonction de tri5 (algorithme quicksort) est ainsi :

void    qsort(void *tab, size_t nb_elements, size_t taille_elements,
              int(*compar)(const void *, const void *));

                                                                  e
Cette gymnastique sur les pointeurs de fonction n’est pas forc´ment triviale
                                                            e
mais reste utile dans certains cas, en particulier dans la d´finition de fonction
  e e                                  e     e     e e
”g´n´rique” comme la fonction qsort ´voqu´e pr´c´demment.




   5
                                                        ee
     Elle permet de trier les nb_elements premiers ´l´ments du tableau tab. Le param`tre  e
                                          ee                                       e
taille_elements donne la taille des ´l´ments du tableau. Le type size_t utilis´ ici est un
         e e                                                 e            e
type pr´d´fini dans <stddef.h>. Il correspond au type du r´sultat de l’´valuation de sizeof.
                               e e
La fonction qsort est param´tr´e par la fonction de comparaison compar de prototype :
int compar(void *a, void *b) ;
                  e                                                   e e
Les deux param`tres a et b de la fonction compar sont des pointeurs g´n´riques de type void *.
                    a                                              e        e
Ils correspondent ` des adresses d’objets dont le type n’est pas d´termin´. Cette fonction de
comparaison retourne
                                                    e                e
   – un entier qui vaut 0 si les deux objets point´s par a et b sont ´gaux ;
                                 e                                      e
   – une valeur strictement n´gative (resp. positive) si l’objet point´ par a est strictement
         e                e        a           e
      inf´rieur (resp. sup´rieur) ` celui point´ par b.



                                             70
Chapitre 6

Gestion des fichiers

                                                         e             e
Comme beaucoup de langages, le C offre la possibilit´ de lire et d’´crire des
      e
donn´es dans un fichier.
                             e        e a                                  e
Pour des raisons d’efficacit´, les acc`s ` un fichier se font par l’interm´diaire
          e                                                      e
d’une m´moire-tampon (on parle de buffer), ce qui permet de r´duire le nombre
      e        e    e
d’acc`s aux p´riph´riques (disque...).
Pour pouvoir manipuler un fichier, un programme a besoin d’un certain nombre
                                                 e                 u
d’informations : l’adresse de l’endroit de la m´moire-tampon o` se trouve le
                           e                             e
fichier, la position de la tˆte de lecture, le mode d’acc`s au fichier (lecture ou
e                                            e
´criture)... Ces informations sont rassembl´es dans une structure, dont le type,
               e                                                         e
FILE *, est d´fini dans <stdio.h>. Un objet de type FILE * est appel´ flot de
     e
donn´es (stream en anglais).
                     e                                       e
Avant de lire ou d’´crire dans un fichier, on notifie son acc`s par la commande
fopen. Cette fonction prend comme argument le nom du fichier, d´fini avece
        e
le syst`me d’exploitation le mode d’ouverture du fichier et initialise un flot de
      e                          e           e                               e
donn´es, qui sera ensuite utilis´ lors de l’´criture ou de la lecture. Apr`s les
                                                                    e    a a
traitements, on annule la liaison entre le fichier et le flot de donn´es grˆce ` la
fonction fclose.


6.1     Ouverture et fermeture de fichiers
6.1.1   Ouverture de fichiers : la fonction fopen
             e        e    a                  e                       e
Lorsqu’on d´sire acc´der ` un fichier, il est n´cessaire avant tout acc`s d’ouvrir
          a
le fichier ` l’aide de la fonction fopen.
                                                                              e
Cette fonction, de type FILE * ouvre un fichier et lui associe un flot de donn´es.
Sa syntaxe est :
                          fopen("nom-de-fichier","mode")
                   e
La valeur retourn´e par fopen est
                        e
– soit un flot de donn´es ;
                    e                               e
– soit NULL si l’ex´cution de cette fonction ne se d´roule pas normalement.
                                                               e
Je vous conseille donc de toujours tester si la valeur renvoy´e par la fonction
           e     a                e
fopen est ´gale ` NULL afin de d´tecter les erreurs d’ouverture de fichier (lecture
d’un fichier inexistant...).

                                       71
Le premier argument de fopen fournit donc le nom du fichier. Le second argu-
                         ıne           e          e                     e
ment, mode, est une chaˆ de caract`res qui sp´cifie le mode d’acc`s au fichier.
       e                          e      e
Les sp´cificateurs de mode d’acc`s diff`rent suivant le type de fichier consid´r´.  ee
On distingue
                                               e              o            a
– les fichiers textes, pour lesquels les caract`res de contrˆle (retour ` la ligne
                     ee                                               e
  ...) seront interpr´t´s en tant que tels lors de la lecture et de l’´criture ;
                                                e              o
– les fichiers binaires, pour lesquels les caract`res de contrˆle se sont pas inter-
     ee
  pr´t´s.
         e                  e          e
Les diff´rents modes d’acc`s sont list´s dans le tableau 6.1.

        "r"      ouverture   d’un   fichier   texte en lecture
        "w"      ouverture   d’un   fichier            e
                                             texte en ´criture
        "a"      ouverture   d’un   fichier            e         a
                                             texte en ´criture ` la fin
       "rb"      ouverture   d’un   fichier   binaire en lecture
       "wb"      ouverture   d’un   fichier              e
                                             binaire en ´criture
       "ab"      ouverture   d’un   fichier              e          a
                                             binaire en ´criture ` la fin
       "r+"      ouverture   d’un   fichier                     e
                                             texte en lecture/´criture
       "w+"      ouverture   d’un   fichier                     e
                                             texte en lecture/´criture
       "a+"      ouverture   d’un   fichier                     e       a
                                             texte en lecture/´criture ` la fin
       "r+b"     ouverture   d’un   fichier                        e
                                             binaire en lecture/´criture
       "w+b"     ouverture   d’un   fichier                        e
                                             binaire en lecture/´criture
       "a+b"     ouverture   d’un   fichier                        e      a
                                             binaire en lecture/´criture ` la fin


                                 e
       Tab. 6.1 – Les modes d’acc`s aux fichiers dans la fonction fopen



                    e
Conditions particuli`res et cas d’erreur
– Si le mode contient la lettre r, le fichier doit exister, sinon c’est une erreur.
– Si le mode contient la lettre w, le fichier peut ne pas exister. Dans ce cas, il
         ee                             ea
  sera cr´´, et si le fichier existait d´j`, son ancien contenu est perdu.
– Si le mode contient la lettre a, le fichier peut ne pas exister. Comme pour le
        e e                                          ee                         ea
  cas pr´c´dent, si le fichier n’existe pas, il est cr´´ ; si le fichier existe d´j`, son
  ancien contenu est conserv´. e
                                      e        a                    e
– Si un fichier est ouvert en mode ”´criture ` la fin”, toutes les ´critures se font `  a
                     e                                 e                       e
  l’endroit qui est ´tait la fin du fichier lors de l’ex´cution de l’ordre d’´criture.
                                                               e             e
  Cela signifie que si plusieurs processus partagent le mˆme FILE*, r´sultat de
                               e         a              e               e
  l’ouverture d’un fichier en ´criture ` la fin, leurs ´critures ne s’´craseront pas
  mutuellement.

Les modes de communication standard
                              e             e
Quand un programme est lanc´ par le syst`me, celui-ci ouvre trois fichiers cor-
respondant aux trois modes de communication standard : standard input, stan-
                                                         e e
dard output et standard error. Il y a trois constantes pr´d´finies dans stdio.h
qui correspondent aux trois fichiers :
                                       e        e
– stdin (standard input) : flot d’entr´e (par d´faut, le clavier) ;

                                             72
                                                  e       e
– stdout (standard output) : flot de sortie (par d´faut, l’´cran) ;
                                                                        e
– stderr (standard error) : flot d’affichage des messages d’erreur (par d´faut,
     e
   l’´cran).
                         e              e
Il est fortement conseill´ d’afficher syst´matiquement les messages d’erreur sur
                                              a e        e
stderr afin que ces messages apparaissent ` l’´cran mˆme lorsque la sortie
                    e
standard est redirig´e.

Utilisation typique de fopen
#include <stdio.h>
FILE *fp;
...
if ((fp = fopen("donnees.txt","r")) == NULL) {
                                                      e
   fprintf(stderr,"Impossible d’ouvrir le fichier donn´es en lecture\n");
   exit(1);
}


6.1.2   Fermeture de fichiers : la fonction fclose
                                   ee        ea
Elle permet de fermer le flot qui a ´t´ associ´ ` un fichier par la fonction fopen.
Sa syntaxe est :
                                    fclose(flot)

 u                                      e
o` flot est le flot de type FILE* retourn´ par la fonction fopen correspondante.
                                     e              e    e
La fonction fclose retourne 0 si l’op´ration s’est d´roul´e normalement et EOF
si il y a eu une erreur.


6.2             e                 e
        Les entr´es-sorties format´es
6.2.1                 e
        La fonction d’´criture en fichier fprintf
                              a                                        e
La fonction fprintf, analogue ` printf (voir §2.5.4 page 29), permet d’´crire
         e
des donn´es dans un flot. Sa syntaxe est :
     fprintf(flot,"cha^ne de contr^le", expression1 , . . . , expressionn );
                        ı             o

 u                         e          e                             e
o` flot est le flot de donn´es retourn´ par la fonction fopen. Les sp´cifications
                e                                       e
de format utilis´es pour la fonction fprintf sont les mˆmes que pour printf
puisque :
                    printf(...) ⇐⇒ fprintf(stdout,...)

6.2.2   La fonction de saisie en fichier fscanf
                             a
La fonction fscanf, analogue ` scanf (voir §2.5.5 page 31), permet de lire des
     e                                           a
donn´es dans un fichier. Sa syntaxe est semblable ` celle de scanf :
      fscanf(flot,"cha^ne de contr^le", arg1 , . . . , argn );
                        ı              o

 u                         e           e                e
o` flot est le flot de donn´es retourn´ par fopen. Les sp´cifications de format
              e
sont ici les mˆmes que celles de la fonction scanf.

                                       73
6.3                                    e
        Impression et lecture de caract`res dans un fi-
        chier
6.3.1              e                  e
        Lecture et ´criture par caract`re : fgetc et fputc
Similaires aux fonctions getchar et putchar (voir §2.5.1 et §2.5.2), les fonctions
                                                         e                e
fgetc et fputc permettent respectivement de lire et d’´crire un caract`re dans
un fichier.
                                       e
La fonction fgetc retourne le caract`re lu dans le fichier et la constante EOF
             e
lorsqu’elle d´tecte la fin du fichier. Son prototype est :
                   int fgetc(FILE* flot);

 u                                      e
o` flot est le flot de type FILE* retourn´ par la fonction fopen.
                   e             e                       e
La fonction fputc ´crit un caract`re dans le flot de donn´es :

int fputc(int caractere, FILE *flot)

                                              e
Elle retourne l’entier correspondant au caract`re lu (ou la constante EOF en cas
d’erreur).

6.3.2              e               e             e
        Lecture et ´criture optimis´es par caract`re : getc et putc
          e                               e
Il existe ´galement deux versions optimis´es des fonctions fgetc et fputc qui
           e    e
sont impl´ment´es par des macros. Il s’agit respectivement de getc et putc.
                           a
Leur syntaxe est similaire ` celle de fgetc et fputc :

int getc(FILE* flot);
int putc(int caractere, FILE *flot)

Ainsi, le programme suivant lit le contenu du fichier texte entree.txt, et le
              e             e
recopie caract`re par caract`re dans le fichier sortie.txt :

#include <stdio.h>
#include <stdlib.h>
#define ENTREE "entree.txt"
#define SORTIE "sortie.txt"

int main(void) {
    FILE *f_in, *f_out;
    int c;
    // Ouverture du fichier ENTREE en lecture
    if ((f_in = fopen(ENTREE,"r")) == NULL) {
        fprintf(stderr, "\nErreur: Impossible de lire %s\n",ENTREE);
        return(EXIT_FAILURE);
    }
    // Ouverture du fichier SORTIE en ecriture
    if ((f_out = fopen(SORTIE,"w")) == NULL) {
        fprintf(stderr, "\nErreur: Impossible d’ecrire dans %s\n",SORTIE);
        return(EXIT_FAILURE);
    }

                                       74
    // Recopie du contenu de ENTREE dans SORTIE
    while ((c = fgetc(f_in)) != EOF)
        fputc(c, f_out);
    // Fermeture des flots de donnees
    fclose(f_in);
    fclose(f_out);
    return(EXIT_SUCCESS);
}


On notera l’utilisation des constantes EXIT_SUCCESS (valant 0) et EXIT_FAILURE
              e                                                       e
(valant 1), d´finies dans la librairie <stdlib.h> et qui sont les param`tres pri-
   e e
vil´gi´s de la fonction exit (voir §9.19).


6.3.3                        e
        Relecture d’un caract`re
                                     e
Il est possible de replacer un caract`re dans un flot au moyen de la fonction
ungetc :

int ungetc(int carac, FILE *f);

                               e
Cette fonction place le caract`re carac (converti en unsigned char) dans le
                                    e                     e
flot f. En particulier, si carac est ´gal au dernier caract`re lu dans le flot, elle
            e                    e                  e e
annule le d´placement provoqu´ par la lecture pr´c´dente. Toutefois, ungetc
     e          e                             e
peut ˆtre utilis´e avec n’importe quel caract`re (sauf EOF). L’exemple suivant
permet d’illustrer le comportement de ungetc :

#include <stdio.h>
#include <stdlib.h>
#define ENTREE "entree.txt"

int main(void) {
    FILE *f_in;
    int c;
    if ((f_in = fopen(ENTREE,"r")) == NULL) {
      fprintf(stderr, "\nErreur: Impossible de lire le fichier %s\n",ENTREE);
      return(EXIT_FAILURE);
    }
    while ((c = fgetc(f_in)) != EOF) {
      if (c == ’0’)
        ungetc(’.’,f_in);
      putchar(c);
    }
    fclose(f_in);
    return(EXIT_SUCCESS);
}


                                                                        a
Sur le fichier entree.txt dont le contenu est 097023, ce programme affiche `
  e
l’´cran 0.970.23.

                                       75
6.3.4            e
         Les entr´es-sorties binaires : fread et fwrite
                      e                                            e
Les fonctions d’entr´es-sorties binaires permettent de transf´rer des donn´es     e
dans un fichier sans transcodage. En ce sens, elles sont plus portables car elles
e                                                                                 e
´crivent ce qu’on leur dit. Elles sont ainsi plus efficaces que les fonctions d’entr´e-
sortie standard.
                                                            e
Elles sont notamment utiles pour manipuler des donn´es de grande taille ou
                        e
ayant un type compos´. Leurs prototypes sont :

size_t fread(void *pointeur, size_t taille, size_t nbre, FILE *f);
size_t fwrite(void *pointeur, size_t taille, size_t nbre, FILE *f);

 u                             e            e a        e
o` pointeur est l’adresse du d´but des donn´es ` transf´rer, taille la taille des
       a        e
objets ` transf´rer et nbre leur nombre. Rappelons que le type size_t, d´fini e
                                             e           e
dans <stddef.h>, correspond au type du r´sultat de l’´valuation de sizeof
(voir §2.1.7 et §2.4.4).
                                e                                           e
La fonction fread lit les donn´es sur le flot f et la fonction fwrite les ´crit.
                                                  e         ee
Ces deux fonctions retournent le nombre de donn´es transf´r´es.
                                    e
Par exemple, le programme suivant ´crit un tableau d’entiers (contenant les 50
premiers entiers) avec fwrite dans le fichier sortie.txt, puis lit ce fichier avec
                        ee
fread et imprime les ´l´ments du tableau.

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

#define NB 50
#define F_SORTIE "sortie.txt"
int main(void) {
    FILE *f_in, *f_out;
    int *tab1, *tab2;
    int i;
    // allocation memoire des tableaux
    tab1 = (int*)malloc(NB * sizeof(int));
    tab2 = (int*)malloc(NB * sizeof(int));
    for (i = 0 ; i < NB; i++)
       tab1[i] = i;

    /* ecriture du tableau dans F_SORTIE */
    if ((f_out = fopen(F_SORTIE, "w")) == NULL) {
       fprintf(stderr, "\nImpossible d’ecrire dans %s\n",F_SORTIE);
       return(EXIT_FAILURE);
    }
    fwrite(tab1, NB * sizeof(int), 1, f_out);
    fclose(f_out);

    /* lecture dans F_SORTIE */
    if ((f_in = fopen(F_SORTIE, "r")) == NULL) {
       fprintf(stderr, "\nImpossible de lire dans %s\n",F_SORTIE);
       return(EXIT_FAILURE);
    }
    fread(tab2, NB * sizeof(int), 1, f_in);

                                         76
     fclose(f_in);
     for (i = 0 ; i < NB; i++)
        printf("%d\t",tab2[i]);
     printf("\n");
     return(EXIT_SUCCESS);
}

    ee                                  e a e
Les ´l´ments du tableau sont bien affich´s ` l’´cran. Par contre, on constate que
                                           e
le contenu du fichier sortie n’est pas encod´.

6.3.5    Positionnement dans un fichier : fseek, rewind et ftell
         e                      e                          e     a
Les diff´rentes fonctions d’entr´es-sorties permettent d’acc´der ` un fichier en
         e                  e                         e                a
mode s´quentiel : les donn´es du fichier sont lues ou ´crites les unes ` la suite
des autres.
       e                       e    a                                 a
Il est ´galement possible d’acc´der ` un fichier en mode direct, c’est-`-dire que
                         a
l’on peut se positionner ` n’importe quel endroit du fichier. La fonction fseek
                          a               e
permet de se positionner ` un endroit pr´cis et a pour prototype :

int fseek(FILE *flot, long deplacement, int origine);

                          e
La variable deplacement d´termine la nouvelle position dans le fichier. Il s’agit
      e                              a                 e
d’un d´placement relatif par rapport ` origine, compt´ en nombre d’octets.
La variable origine peut prendre trois valeurs :
                 e     a       e
    1. SEEK_SET (´gale ` 0) : d´but du fichier ;
                 e     a
    2. SEEK_CUR (´gale ` 1) : position courante ;
                 e     a
    3. SEEK_END (´gale ` 2) : fin du fichier.
La fonction
int rewind(FILE *flot);

                             e                        e           a
permet de se positionner au d´but du fichier. Elle est ´quivalente ` fseek(flot,
0, SEEK_SET) ;
La fonction
long ftell(FILE *flot);

retourne la position courante dans le fichier (en nombre d’octets depuis l’ori-
                                                                e e
gine). L’exemple suivant illustre l’utilisation des fonctions pr´c´dentes :

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

#define NB 50
#define F_SORTIE "sortie.txt"
int main(void) {
    FILE *f_in, *f_out;
    int *tab;
    int i;
    // Initialisation du tableau

                                       77
      tab = (int*)malloc(NB * sizeof(int));
      for (i = 0 ; i < NB; i++)
        tab[i] = i;
      /* ecriture du tableau dans F_SORTIE */
      if ((f_out = fopen(F_SORTIE, "w")) == NULL){
          fprintf(stderr, "\nImpossible d’ecrire dans %s\n",F_SORTIE);
          return(EXIT_FAILURE);
      }
      fwrite(tab, NB * sizeof(int), 1, f_out);
      fclose(f_out);
      /* lecture dans F_SORTIE */
      if ((f_in = fopen(F_SORTIE, "r")) == NULL) {
          fprintf(stderr, "\nImpossible de lire dans %s\n",F_SORTIE);
          return(EXIT_FAILURE);
      }
      /* on se positionne a la fin du fichier */
      fseek(f_in, 0, SEEK_END);
      printf("\n position %ld", ftell(f_in));
      /* deplacement de 10 int en arriere */
      fseek(f_in, -10 * sizeof(int), SEEK_END);
      printf("\n position %ld", ftell(f_in));
      fread(&i, sizeof(i), 1, f_in);
      printf("\t i = %d", i);
      /* retour au debut du fichier */
      rewind(f_in);
      printf("\n position %ld", ftell(f_in));
      fread(&i, sizeof(i), 1, f_in);
      printf("\t i = %d", i);
      /* deplacement de 5 int en avant */
      fseek(f_in, 5 * sizeof(int), SEEK_CUR);
      printf("\n position %ld", ftell(f_in));
      fread(&i, sizeof(i), 1, f_in);
      printf("\t i = %d\n", i);
      fclose(f_in);
      return(EXIT_SUCCESS);
}

    e                             a e
L’ex´cution de ce programme affiche ` l’´cran :

    position   200
    position   160   i = 40
    position   0     i = 0
    position   24    i = 6

                                                                            e
On constate en particulier que l’emploi de la fonction fread provoque un d´pla-
                      a                         a
cement correspondant ` la taille de l’objet lu ` partir de la position courante.




                                      78
Chapitre 7

Les directives du
  e
pr´processeur

       e                                  e e                    e
Le pr´processeur est un programme ex´cut´ lors de la premi`re phase de la
                                                                            a
compilation. Il effectue des modifications textuelles sur le fichier source ` par-
                          e                       e
tir de directives. Les diff´rentes directives au pr´processeur, introduites par le
       e
caract`re #, ont pour but :
– l’incorporation de fichiers source (#include),
       e
– la d´finition de constantes symboliques et de macros (#define),
– la compilation conditionnelle (#if, #ifdef,...).
                                   eaee
L’utilisation de ces directives a d´j` ´t´ introduite au §1.4 page 7. Il s’agit ici
     e                                       e
de d´tailler l’ensemble des directives du pr´processeur.


7.1     La directive #include
Elle permet d’incorporer dans le fichier source le texte figurant dans un autre
                         e                 e
fichier. Ce dernier peut ˆtre un fichier en-tˆte de la librairie standard (stdio.h,
                                                                         e
math.h,...) ou n’importe quel autre fichier. La directive #include poss`de deux
syntaxes voisines :
#include <nom-de-fichier>
                             e                         e              e     e
recherche le fichier mentionn´ dans un ou plusieurs r´pertoires syst`mes d´finis
           e
par l’impl´mentation (typiquement /usr/include/) :
#include "nom-de-fichier"
                               e                            u
recherche le fichier dans le r´pertoire courant (celui o` se trouve le fichier
                     e               e           a
source). On peut sp´cifier d’autres r´pertoires ` l’aide de l’option -I du compi-
lateur (voir chapitre 8).
           e                e e                e                         e
La premi`re syntaxe est g´n´ralement utilis´e pour les fichiers en-tˆte de la
                                                   o          e               ee
librairie standard, tandis que la seconde est plutˆt destin´e aux fichiers cr´´s
par l’utilisateur.


7.2     La directive #define
                                  e
La directive #define permet de d´finir des constantes symboliques (on parle
                          e                              e
aussi de macros sans param`tres) ou des macros avec param`tre.

                                        79
7.2.1    e
        D´finition de constantes symboliques
                   eaee       e e                                    e
Bien que cela a d´j` ´t´ vu pr´c´demment, rappelons que lorsque le pr´proces-
seur lit une ligne du type :
#define nom reste-de-la-ligne

il remplace dans toute la suite du source toute nouvelle occurrence de nom par
                                                       a
reste-de-la-ligne. Il n’y a aucune contrainte quand ` ce qui peut se trouver
                                            e
dans reste-de-la-ligne : on pourra ainsi ´crire

#define BEGIN {
#define END }

        e                                 e                                 a
L’utilit´ principale des macros sans param`tre est de donner un nom parlant `
                              a
une constante. Les avantages ` toujours donner un nom aux constantes sont les
suivants :
                                                e
  1. un nom bien choisi permet d’expliciter la s´mantique de la constante. Ex :
     #define NB_COLONNES 100.
                         e            a
  2. la constante chiffr´e se trouve ` un seul endroit, ce qui facilite la modi-
     fication du programme quand on veut changer la valeur de la constante
     (cas de la taille d’un tableau, par exemple).
  3. on peut expliciter facilement les relations entre constantes. Ex :
     #define NB_LIGNES 24
     #define NB_COLONNES 80
     #define TAILLE_MATRICE NB_LIGNES * NB_COLONNES

 e                                  a
D´finition de constantes symboliques ` l’invocation du compilateur
                                       e                                 a
Certains compilateurs permettent de d´finir des constantes symboliques ` l’in-
                                                 e
vocation du compilateur. Il est alors possible d’´crire un programme utilisant
                                e
une macro qui n’est nulle part d´finie dans le source.
           e
Ceci est tr`s pratique pour que certaines constantes critiques d’un programme
                                  e a       e
aient une valeur qui soit attribu´e ` l’ext´rieur du programme (comme lors
d’une une phase de configuration par exemple).
                                                                         e
Ci-dessous, un exemple pour gcc : la compilation du fichier prog.c en d´finis-
sant la constante symbolique NB_LIGNES de valeur 14 :
gcc -c -DNB_LIGNES=24 prog.c

                         e e
Constantes symboliques pr´d´finies
                                     e e               e             e       e
Il y a un certain nombre de macros pr´d´finies par le pr´processeur, r´capitul´es
dans le tableau 7.1.

7.2.2                        e
        Les macros avec param`tres
                    e         e               e
Une macro avec param`tres se d´finit de la mani`re suivante :
                             e
#define nom(liste-de-param`tres) corps-de-la-macro

                                      80
     Nom         Valeur de la macro                                       Type
   __LINE__           e
                 num´ro de la ligne courante du programme source          entier
   __FILE__      nom du fichier source en cours de compilation                ıne
                                                                          chaˆ
   __DATE__      la date de la compilation                                   ıne
                                                                          chaˆ
   __TIME__      l’heure de la compilation                                   ıne
                                                                          chaˆ
   __STDC__      1 si le compilateur est ISO, 0 sinon                     entier


                                                       e e
               Tab. 7.1 – Les constantes symboliques pr´d´finies


  u                                                         e e
o` liste-de-param`tres est une liste d’identificateurs s´par´s par des virgules.
                       e
Par exemple, avec la directive
#define MAX(a,b) (a > b ? a : b)
le processeur remplacera dans la suite du code toutes les occurences du type
              u
MAX(x,y) o` x et y sont des symboles quelconques par (x > y ? x : y)
                                           a
Une macro a donc une syntaxe similaire ` celle d’une fonction, mais son emploi
              e e
permet en g´n´ral d’obtenir de meilleures performances en temps d’ex´cution.e
                              e
La distinction entre une d´finition de constante symbolique et celle d’une macro
              e                          e                  e
avec param`tres se fait sur le caract`re qui suit imm´diatement le nom de
                           e                  e
la macro : si ce caract`re est une parenth`se ouvrante, c’est une macro avec
        e
param`tres, sinon c’est une constante symbolique. Il ne faut donc jamais mettre
                                                  e
d’espace entre le nom de la macro et la parenth`se ouvrante. L’erreur classique
e         e
´tant d’´crire par erreur :
#define CARRE (a) a * a
       ıne           e                                 e
la chaˆ de caract`res CARRE(2) sera alors remplac´e par : (a) a * a (2)
                            a                 e
Il faut toujours garder ` l’esprit que le pr´processeur n’effectue que des rem-
placements de chaˆ                  e                                  e
                    ınes de caract`res. En particulier, il est conseill´ de toujours
                         e                                      e
mettre entre parenth`ses le corps de la macro et les param`tres formels qui y
            e                         e                e
sont utilis´s. Par exemple, si l’on ´crit sans parenth`ses :
#define CARRE(a) a * a
     e
le pr´processeur remplacera CARRE(a + b) par a + b * a + b et non par
                                e                             e
(a + b) * (a + b). De mˆme, !CARRE(x) sera remplac´ par ! x * x et non
par !(x * x).
                e                 e
Enfin, il faut ˆtre attentif aux ´ventuels effets de bord que peut entraˆ  ıner l’usage
de macros. Par exemple, CARRE(x++) aura pour expansion (x++) * (x++).
     e              e                              e
L’op´rateur d’incr´mentation sera donc appliqu´ deux fois au lieu d’une.
                                        e          a                  e
En conclusion, les macros avec param`tres sont ` utiliser avec pr´caution et en
cas de doutes, il faut mieux s’en passer.


7.3     La compilation conditionnelle
La compilation conditionnelle a pour but d’incorporer ou d’exclure des parties
                                       e ee           e                     e
du code source dans le texte qui sera g´n´r´ par le pr´processeur, le choix ´tant
   e                e ea
bas´ sur un test ex´cut´ ` la compilation. Elle permet d’adapter le programme
       e       a                                   e
au mat´riel ou ` l’environnement sur lequel il s’ex´cute, ou d’introduire dans le
                                  e
programme des instructions de d´buggage.

                                         81
                                                 e                      e
Les directives de compilation conditionnelle se r´partissent en deux cat´gories,
                                      e              e
suivant le type de la condition invoqu´e qui est test´e :
– la valeur d’une expression
– l’existence ou l’inexistence de symboles

7.3.1               e a
        Condition li´e ` la valeur d’une expression
                    e e
Sa syntaxe la plus g´n´rale est :

#if condition-1
  partie-du-programme-1
#elif condition-2
  partie-du-programme-2
     ...
#elif condition-n
  partie-du-programme-n
#else
  partie-du-programme-else
#endif

Le nombre de #elif est quelconque et le #else est facultatif. Chaque condition-i
     e
doit ˆtre une expression constante.
                                                                       e
Lors de la compilation, une seule partie-du-programme-i sera compil´e : celle
                a         e
qui correspond ` la premi`re condition-i non nulle, ou bien la partie-du-programme-else
si toutes les conditions sont nulles.
                        e
Par exemple, on peut ´crire :

#define PROCESSEUR ALPHA
...
#if PROCESSEUR == ALPHA
    taille_long = 64;
#elif PROCESSEUR == PC
    taille_long = 32;
#endif

7.3.2               e a
        Condition li´e ` l’existence d’un symbole
Sa syntaxe est la suivante :

#ifdef symbole
  partie-du-programme-1
#else condition-2
  partie-du-programme-2
#endif
                  e                   u
Si symbole est d´fini au moment o` l’on rencontre la directive #ifdef, alors
                                        e
partie-du-programme-1 sera compil´e et partie-du-programme-2 sera igno-
 e                                                                     e
r´e. Dans le cas contraire, c’est partie-du-programme-2 qui sera compil´e. La
                                e e
directive #else est comme pr´c´demment facultative. Ce type de directive est
                                           e       e
utile pour rajouter des instructions destin´es au d´buggage du programme :

                                      82
#define DEBUG
  ....
#ifdef DEBUG
  for (i = 0; i < N; i++)
    printf("%d\n",i);
#endif /* DEBUG */

Il suffit alors de supprimer la directive #define DEBUG pour que les instructions
li´es au debuggage ne soient pas compil´es1 .
  e                                      e
      c                                                          a
De fa¸on similaire, on peut tester la non-existence d’un symbole ` l’aide de la
directive #ifndef :

#ifndef symbole
  partie-du-programme-1
#else condition-2
  partie-du-programme-2
#endif

On rencontre beaucoup cette directive dans le cadre de la compilation modulaire
                                                           e
(voir chapitre 8) puisqu’elle permet de s’assurer que les d´finitions d’instructions
                       e                    e
dans un fichier d’en-tˆte ne seront effectu´es qu’une seule fois.

7.3.3        e
         L’op´rateur defined
      e                           e        e                  e        e
L’op´rateur defined est un op´rateur sp´cial : il ne peut ˆtre utilis´ que dans
                                                        e         e
le contexte d’une commande #if ou #elif. Il peut ˆtre utilis´ sous l’une des
deux formes suivantes :
– defined nom
– defined ( nom )
    e                                        e                                 ee
Il d´livre la valeur 1 si nom est une macro d´finie, et la valeur 0 sinon. L’int´rˆt
          e                            e                               e
de cet op´rateur est de permettre d’´crire des tests portant sur la d´finition de
plusieurs macros, alors que #ifdef ne peut en tester qu’une :
#if defined(SOLARIS) || defined(SYSV)

7.3.4    La commande #error
La commande #error a la syntaxe suivante :
#error chaine
                                                e
La rencontre de cette commande provoquera l’´mission d’un message d’erreur
                                                        e              a
comprenant la chaine. Cette commande a pour utilit´ de capturer ` la compi-
                                                                  e
lation des conditions qui font que le programme ne peut pas s’ex´cuter sur cette
                                 u
plate-forme. Voici un exemple o` on teste que la taille des entiers est suffisante :

#include <limits.h>
#if INT_MAX < 1000000
#error "Entiers trop petits sur cette machine"
#endif
   1
                                             e                                    e
     A noter aussi que comme on l’a vu pr´cedemment, on peut remplacer cette derni`re
                                                              e
directive par l’option de compilation -DDEBUG, qui permet de d´finir ce symbole.


                                         83
7.3.5   La commande #pragma
                           e a          e            e                     e
La directive #pragma est li´e ` une impl´mentation sp´cifique et permet de d´-
                                 e                    e
finir, pour un compilateur donn´, une directive de pr´traitement. Sa syntaxe
est la suivante :

#pragma commande




                                     84
Chapitre 8

La programmation modulaire

  e            e                                                  eae
D`s que l’on ´crit un programme de taille importante ou destin´ ` ˆtre utilis´ e
et maintenu par d’autres personnes, il est indispensable de se fixer un certain
              e      e                                 e
nombre de r`gles d’´criture. En particulier, il est n´cessaire de fractionner le
                                                             e
programme en plusieurs fichiers sources, que l’on compile s´paremment.
      e        e
Ces r`gles d’´criture ont pour objectifs de rendre un programme lisible, por-
        e                               a              a
table, r´utilisable mais surtout facile ` maintenir et ` modifier.

     e                                  e                              e
L’id´e est alors de regrouper dans un mˆme fichier les instructions impl´mentant
                  e
des fonctionnalit´s similaires (par exemple, tab_management.c contiendra une
         e                  e
biblioth`que de fonctions g´rant les tableaux, file_management.c fournira une
         e                  e                                                 e
biblioth`que de fonctions g´rant les fichiers dans le contexte du projet effectu´
                             e
et main.c contiendra la d´finition de la fonction main). La programmation
                          a e                           e
modulaire consiste alors ` op´rer les manipulations n´cessaires permettant de
lier ces fichiers.


8.1               ee
        Principes ´l´mentaires
                                            e
Trois principes essentiels doivent guider l’´criture d’un programme C. Ces prin-
                                        e e          e
cipes s’appliquent en fait dans le cas g´n´ral du g´nie logiciel.
                                      e
  1. L’abstraction des constantes litt´rales
                                               e
     L’utilisation explicite de constantes litt´rales dans le corps d’une fonc-
     tion rend les modifications et la maintenance difficiles. Des instructions
     comme :
      fopen("mon_fichier", "r");
      perimetre = 2 * 3.14 * rayon;
           a                         e
      sont ` proscrire (il faudrait d´finir des constantes fournissant le nom du
                                            e
      fichier ou la valeur de π). Sauf cas tr`s particuliers, les constantes doivent
      e     e
      ˆtre d´finies comme des constantes symboliques au moyen de la directive
      #define.
  2. La factorisation du code
                  e                                     e             e
     Le but est d’´viter les duplications de code. La pr´sence d’une mˆme por-
                  a                                                   a e
     tion de code ` plusieurs endroits du programme est un obstacle ` d’´ven-

                                        85
                                                          e     e
      tuelles modifications. Les fonctions doivent donc ˆtre syst´matiquement
            e        e
      utilis´es pour ´viter la duplication de code. Il ne faut pas craindre de
       e
      d´finir une multitude de fonctions de petite taille.
3. La fragmentation du code
                                e                       e
   Pour des raisons de lisibilit´, il est pertinent de d´couper un programme
                                            e               e
   en plusieurs fichiers. En plus, cette r`gle permet de r´utiliser facilement
   une partie du code pour d’autres applications.
        e                                                            e
   On s´pare alors le programme en modules, chaque module impl´mentant
                           e
   des fonctions sur un th`me similaire et qui se traduiront physiquement
   par deux fichiers :
                         e
       (a) un fichier en-tˆte (on dit aussi de header) ayant l’extension .h et
                                                                e    e
           contenant le prototype des fonctions principales impl´ment´es dans
           ce module.
       (b) un fichier source ayant l’extension .c contenant non seulement le
                                  e   e                        e         e
           corps des fonctions d´clar´es dans le fichier en-tˆte mais ´galement
                                     e        e                      e
           celui des fonctions interm´diaires ´ventuellement utilis´es. Ce fichier
                  e                        e
           inclut ´videmment le fichier en-tˆte par le biais de la directive #include.
                     e
      Une possibilit´ est de placer une partie du code dans un fichier en-tˆte  e
      (on dit aussi de header) ayant l’extension .h que l’on inclut dans le fichier
                                          a
      contenant le programme principal ` l’aide de la directive #include.

      L’exemple suivant illustre ce principe sur un programme qui saisit deux
                                                                      ee
      entiers au clavier et affiche leur produit. Sur cet exemple, il a ´t´ choisi de
        e
      d´finir un module arithm´tique qui fournit la fonction effectuant le pro-
                                e
      duit1 . Ce module est donc impl´ment´ dans les fichiers arithm´tique.h
                                       e     e                           e
                                                             e
      et arithm´tique.c. Le programme est donc compos´ de trois fichiers :
                 e
      les 2 fichiers du module et le fichier principale contenant la fonction main
             e
      appel´ ici main.c :
      /*********************************************/
      /* fichier: main.c                           */
      /* saisit 2 entiers et affiche leur produit */
      /*********************************************/
      #include <stdlib.h>
      #include <stdio.h>
      #include "arithmetique.h" // Le module d’arithmetique

      int main(void) {
        int a, b, c;
        scanf("%d",&a);
        scanf("%d",&b);
                                                   e
        c = produit(a,b); // appel de la fonction d´finie dans arithmetique.h
        printf("\nle produit vaut %d\n",c);
        return EXIT_SUCCESS;
      }

      /********************************************************/
1
              e                         e
    Il s’agit ´videmment d’un exemple tr`s simpliste !


                                             86
      /* fichier: arithmetique.h                              */
           e         e              e
      /* G`re les op´rations arithm´tiques sur les entiers    */
                                        e
      /* (Ici, seul le produit est impl´ment´e                */
      /********************************************************/
      int produit(int a, int b);

      /*****************************************************/
      /* fichier: arithmetique.c                           */
                               e
      /* Corps des fonctions d´finies dans arithmetique.h */
      /*****************************************************/
      #include "arithmetique.h"
      int produit(int a, int b) {
         return(a * b);
      }
                                                e          e
      Remarquer que c’est exactement la proc´dure utilis´e pour les fonctions de
                                              e
      la librairie standard : les fichiers en-tˆte .h de la librairie standard sont
               e       e                                 e
      constitu´s de d´clarations de fonctions et de d´finitions de constantes
      symboliques (la seule information importante pour un utilisateur) et le
                                    e         e e
      corps des fonctions en lui-mˆme est s´par´.


8.2      Eviter les erreurs d’inclusions multiples
          e    e      e                            a e
Une derni`re r`gle, tr`s importante, consiste ` ´viter les erreurs de double
 e                                     a e
d´finition de fonctions qui se traduise ` l’´dition de lien par le message suivant
(avec gcc) :

toto.o:toto.c multiple definition of ’...’
toto.o: first defined here

                             e e                                         e
Cette erreur se produit en g´n´ral lors de l’inclusion multiple d’un mˆme fichier
    e
en-tˆte.
      e                e                 a e                       a         e
Pour ´viter cela, la m´thode consiste ` d´finir une constante ` la premi`re in-
                      e                                                          ea
clusion du fichier en-tˆte et d’ignorer le contenu de celui-ci si la constante a d´j`
ee e                       ea
´t´ d´finie (donc si on a d´j` fait une inclusion). Pour cela, on utilise la direc-
                                               e
tive #ifndef (voir 7.3.2). Il est recommand´ d’appeler la constante __TOTO_H
                                               e
pour le fichier toto.h En appliquant cette r`gle, le fichier arithmetique.h de
            e e
l’exemple pr´c´dent devient :

/********************************************************/
/* fichier: arithmetique.h                              */
     e         e              e
/* G`re les op´rations arithm´tiques sur les entiers    */
                                  e
/* (Ici, seul le produit est impl´ment´e                */
/********************************************************/
#ifndef __ARITHMETIQUE_H
#define __ARITHMETIQUE_H
int produit(int a, int b);

#endif

                                                    e    e     e
Je vous recommande de toujours appliquer cette derni`re r`gle d`s
                e
lors que vous cr´ez un fichier de header !

                                        87
8.3                     e   e
        La compilation s´par´e
Ce n’est pas le tout de bien fragmenter son code en plusieurs fichiers, encore
faut-il les compiler pour obtenir un executable.
      e               a e e
La m´thode consiste ` g´n´rer un fichier objet par module (option -c de gcc) :
gcc -O3 -Wall -I. -c module_1.c
gcc -O3 -Wall -I. -c module_2.c
   .
   .
   .
gcc -O3 -Wall -I. -c module_n.c
                   e e
Ces commandes g´n`rent n fichiers objets module_i.o.
                                            e                     e
L’option -I de gcc permet d’ajouter un r´pertoire en premi`re position de la
           e           u            e                  e              e
liste des r´pertoires o` sont cherch´s les fichiers en-tˆte. (ici, le r´pertoire cou-
rant, ./ : l’option -I est donc en fait facultative dans ce cas). Cette option
                                                e
est utile lorsque, pour des raisons de lisibilit´ dans l’architecture des fichiers
              e                        ee
sources, un r´pertoire Include est cr´´ pour contenir tous les fichiers en-tˆte   e
du programme. La compilation avec gcc comportera alors l’option -IInclude.

              e                                                  e
Une passe d’´dition de lien entre ces fichiers objets en ensuite n´cessaire pour
 e e        e
g´n´rer l’ex´cutable final toto.exe :
gcc -o toto.exe module_1.o module_2.o ... module_n.o



8.4      e   e      e
        R´sum´ des r`gles de programmation modulaire
       e                                         e
  1. D´couper le programme en modules impl´mentant des fonctions similaires.
  2. Chaque module se traduit en 2 fichiers sources :
                        e                    e                            a
     – un fichier en-tˆte module.h qui d´finit son interface, c’est ` dire le
                                                            e            e e
        prototype des fonctions du module qui sont export´es. Plus pr´cis´ment,
        ce fichier se compose :
               e                                                             e
        – des d´clarations des fonctions d’interface (celles qui sont export´es et
                     e
          donc utilis´es dans d’autres fichiers sources)
            e             e
        – d’´ventuelles d´finitions de constantes symboliques et de macros.
            e                            e
        – d’´ventuelles directives au pr´processeur (inclusion d’autres fichiers,
          compilation conditionnelle).
                                 e                              e              e
        Ce fichier doit respecter ´galement les conventions d’´critures propos´es
                      e                      e
        au §8.2 pour ´viter les erreurs de d´finitions multiples.
                                                                     e     e
     – un fichier module.c contenant le corps des fonctions impl´ment´es dans
        ce module. Ce fichier se compose :
                                                   e
        – de variables globales qui ne sont utilis´es que dans le fichier module.c ;
                                                         e
        – du corps des fonctions d’interface dont la d´claration se trouve dans
          module.h ;
            e                              a
        – d’´ventuelles fonctions locales ` module.c.
  3. Le fichier module.h est inclus dans le fichier module.c (via la directive
     #include) et dans tous les autres fichiers qui font appel aux fonctions
            e
     export´es par ce module.
                                 e         e e         e
  4. Chaque module est compil´ pour g´n´rer l’ex´cutable final (voir 8.3).

                                        88
     e                     e      e e        e                             e
Ces r`gles s’ajoutent aux r`gles g´n´rales d’´criture de programmes C abord´es
au §1.6 page 9.


8.5     L’outils ’make’
                                    e
Losrqu’un programme est fragment´ en plusieurs fichiers sources compil´s s´-  e e
                    e                             e
paremment, la proc´dure de compilation peut tr`s vite devenir longue et fasti-
dieuse.
                 e                                  a
Il est alors extr`mement pratique de l’automatiser ` l’aide de l’utilitaire ’make’
                                                    e
d’Unix. Une bonne utilisation de make permet de r´duire le temps de compila-
         e                                           e
tion et ´galement de garantir que celle-ci est effectu´e correctement.

8.5.1   Principe de base
    e                                                      e
L’id´e principale de make est d’effectuer uniquement les ´tapes de compilation
 e          a      e             e
n´cessaires ` la cr´ation d’un ex´cutable. Par exemple, si un seul fichier source
  ee         e                               e
a ´t´ modifi´ dans un programme compos´ de plusieurs fichiers, il suffit de
                                     e
recompiler ce fichier et d’effectuer l’´dition de liens. Les autres fichiers sources
                     e           e
n’ont pas besoin d’ˆtre recompil´s.
                                      e              e
La commande make recherche par d´faut dans le r´pertoire courant un fichier
                                        e         e
makefile ou Makefile. Ce fichier sp´cifie les d´pendances entre les diff´rentse
                             e
fichiers sources, objets et ex´cutables.
Voyons tout de suite les principales options de cette commande (les plus utiles ;
                e
pour plus de d´tails : man make)
                                                                   a
– make -f <nom_fichier> : utiliser le fichier <nom_fichier> ` la place du
  traditionnel fichier Makefile.
                        e                              e        e
– make -C <path> : ex´cuter le fichier Makefile situ´ dans le r´pertoire <path>.

8.5.2     e
        Cr´ation d’un Makefile
                             e                 e         e
Un fichier Makefile est compos´ d’une liste de r`gles de d´pendances entre
fichiers, de la forme :

                 e
cible: liste_de_d´pendances
 <TAB> commandes_a_effectuer

          e           e                      e ee
La premi`re ligne sp´cifie un fichier cible (g´n´r´ par commandes_a_effectuer)
                                 e        e e
et la liste des fichiers dont il d´pend (s´par´s par des espaces) : il s’agit donc
                                       e e
de la liste des ficiers requis pour la g´n´ration de cible.
Les lignes suivantes, qui doivent commencer par une tabulation (le caract`re  e
                                    a e              e e
TAB), indiquent les commandes ` ex´cuter pour g´n´rer le fichier cible. Ces
                           e e                              e
commandes ne seront ex´cut´es que si le fichier cible n’ex´cute pas ou si l’un
                 e                     e
des fichiers de d´pendance est plus r´cent que le fichier cible.
       c    e e        e
De fa¸on g´n´rale, l’´criture d’un fichier Makefile se rapproche largement de
  e
l’´criture d’un script shell :
– On peut placer des commentaires, qui commencent par # :
    #Ceci est un commentaire
                         e                            e
    Les commentaires s’´tendent sur une seule ligne (´quivalent du // en C99).

                                       89
– On peut declarer des variables.
                     e             c
  Une variable se d´clare de la fa¸on suivante : SRC = machin.c
  Ensuite, le contenu de cette variable (machin.c) est obtenu par l’appel $(SRC)
             e
Ainsi, consid´rons l’exemple du programme effectuant le produit de deux entiers
  e     e                       e e        e
pr´sent´ au §??. On souhaite g´n´rer l’ex´cutable toto.
                          a
– Pour une compilation ”` la main” et en suivant les recommendations du §1.1.4,
                e
  il faudrait ex´cuter les commandes :
  gcc -g3 -Wall -c produit.c
  gcc -g3 -Wall -c main.c
  gcc -g3 -Wall -o toto main.o produit.o
                                            e
  On l’aura compris : cela peut devenir tr`s vite long et lassant !
                                  e                                 e
– Un premier fichier Makefile g´rant cette compilation pourrait ˆtre :
  ## Premier exemple de Makefile
  # il faut generer l’executable toto
  all: toto

                          e
  # Le fichier produit.o d´pend de produit.c et de produit.h
  produit.o: produit.c produit.h
        gcc -g3 -Wall -c produit.c

                       e
  # Le fichier main.o d´pend de main.c et de produit.h
  main.o: main.c produit.h
        gcc -g3 -Wall -c main.c

                      e                  e
  # Generation de l’ex´cutable toto qui d´pend des fichiers objets
  toto: main.o produit.o
        gcc -g3 -Wall -o toto main.o produit.o
              e                                                           e
  Moyennant l’´criture de ce fichier de configuration, il suffira ensuite d’ex´cuter
  la commande make pour lancer la compilation du programme toto !

8.5.3   Utilisation de macros et de variables
On suppose maintenant qu’on souhaite en plus que les fichiers de header soient
    e           e                                                 e
plac´s dans le r´pertoire include/ et les fichiers objets dans le r´pertoire obj/.
                                        a               e
Les fichiers sources (.c) sont quand ` eux dans le r´pertoire courant et on
                            e             e
souhaite qu’il en soit de mˆme pour l’ex´cutable final (toto).
                  e e                                e
L’arborescence g´n´ral des fichiers et le graphe des d´pendances entre les fichiers
         e
est expos´ dans la figure 8.1.
                                                              e e
Avec une telle arborescence, on peut modifier le Makefile pr´c´dent pour g´rere
                                             c
la compilation du programme toto de la fa¸on suivante :
## Deuxieme exemple de Makefile - gestion d’une arborescence
all: toto

obj/produit.o: produit.c include/produit.h
        gcc -g3 -Wall -Iinclude/ -c produit.c -o obj/produit.o

obj/main.o: main.c include/produit.h
        gcc -g3 -Wall -Iinclude/ -c main.c -o obj/main.o

toto: obj/main.o obj/produit.o

                                       90
          ARBORESCENCE                   GRAPHE DES DEPENDANCES

                projet/
                                                                                     Exécutable
                                                           toto
                      include/

                             produit.h
                                                   .o               .o               Fichiers
                      obj/
                                                  main.o          produit.o          Objets
                             main.o

                             produit.o
                                          .c               .h              .c        Fichiers
                    main.c                                                           Sources
                                         main.c         produit.h        produit.c
                    produit.c

                    toto

                    Makefile



                                           e
     Fig. 8.1 – Arborescence et graphe de d´pendances entre les fichiers

         gcc -g3 -Wall -o toto obj/main.o obj/produit.o

On voit qu’un certain nombre d’informations sont redondantes. En particulier,
                          e                             a
si on change le nom d’un r´pertoire, il faut le changer ` plusieurs endroit dans
                     e
le Makefile d’ou l’id´e de centraliser ce type d’informations. On utilise pour
cela des variables :
## Troisieme exemple de Makefile - gestion d’une arborescence
# Utilisation de variables
EXE = toto
OBJ_DIR = obj
H_DIR = include
OPT = -g3 -Wall

all: $(EXE)

$(OBJ_DIR)/produit.o: produit.c $(H_DIR)/produit.h
        gcc $(OPT) -Iinclude/ -c produit.c -o $(OBJ_DIR)/produit.o

$(OBJ_DIR)/main.o: main.c $(H_DIR)/produit.h
        gcc $(OPT) -Iinclude/ -c main.c -o $(OBJ_DIR)/main.o

$(EXE): $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o
        gcc $(OPT) -o $(EXE) $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o

                             a
Mais la encore, on continue ` dupliquer un certain nombre d’informations. Pour
                                                 e e
cela, il existe un certain nombre de variables pr´d´finies, dont les les plus utiles
       e     e
sont r´sum´es dans le tableau 8.1.
A partir de ces variables, on obtient une version beaucoup plus simple du Ma-
          e e
kefile pr´c´dent :
## Quatrieme exemple de Makefile - gestion d’une arborescence
# Utilisation des variables predefinies
EXE = toto
OBJ_DIR = obj
H_DIR = include

                                         91
 $@      Le fichier cible (par exemple : obj/main.o)
 $*      Le fichier cible sans suffixe (ex : obj/main)
 $<                                         e
         Le premier fichier de la liste des d´pendances (par exemple : main.c)
 $?                                   e
         L’ensemble des fichiers de d´pendances


                                             e e
      Tab. 8.1 – Les principales variables pr´d´finies dans un Makefile

OPT = -Wall -g3

all: $(EXE)

$(OBJ_DIR)/produit.o: produit.c $(H_DIR)/produit.h
        gcc $(OPT) -I$(H_DIR)/ -c $< -o $@

$(OBJ_DIR)/main.o: main.c $(H_DIR)/produit.h
        gcc $(OPT) -I$(H_DIR)/ -c $< -o $@

$(EXE): $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o
        gcc $(OPT) -o $(EXE) $?

                      e                          e                  e
A noter que pour d´terminer facilement les d´pendances entre les diff´rents
fichiers, on peut utiliser l’option -MM de gcc. Par exemple,

% gcc -MM -Iinclude/ *.c
main.o: main.c include/produit.h
produit.o: produit.c include/produit.h

                                                                     e
On rajoute habituellement dans un fichier Makefile une cible appel´e clean
               e
permettant de d´truire tous les fichiers objets et une cible distclean qui sup-
      e               e           e ee
prime ´galement les ex´cutables g´n´r´s lors de la compilation.

clean:
        rm -f $(OBJ_DIR)/*.o
distclean: clean
        rm -f $(EXE)

                                                          e
La commande make clean permet donc de ”nettoyer” le r´pertoire courant. No-
                                                              e
tons que l’on utilise ici la commande rm avec l’option -f qui ´vite l’apparition
                                   a e
d’un message d’erreur si le fichier ` d´truire n’existe pas.

                                      a                                  e
Il y aurait encore beaucoup de choses ` dire sur les Makefile mais cela d´passe
                                   e                              a
largement le cadre de ce polycopi´. La meilleur documentation ` ce sujet se
        a
trouve ` l’URL http://www.gnu.org/software/make/manual/make.html.
                                                                     e
Pour conclure, on notera que tous les exemples de Makefile propos´s doivent
e           e e                                        e
ˆtre modifi´s d`s lors qu’un nouveau fichier est ajout´ au projet. Un exemple
              e e                     u                     e          e
de Makefile g´n´rique (dans le sens o` il n’a pas besoin d’ˆtre modifi´ lors de
                                                                    e
l’ajout de nouveau fichiers) est fourni an annexe B page 112. Pour r´utiliser ce
Makefile dans n’importe quel projet, il suffit d’adapter les variables $(INCL),
$(DIR_OBJ) et $(EXE). Pour plus d’informations, il suffit de taper make help.

                                       92
Chapitre 9

           e
La biblioth`que standard

                           e
Ce chapitre est un aide-m´moire qui donne la liste exhaustive de toutes les
                        e                          e
fonctions de la biblioth`que standard, accompagn´es la plupart du temps de
leurs prototypes.
                  e
Les fichiers d’en-tˆte de la librairie ANSI sont les suivants (ceux correspon-
       a                          e
dants ` de nouvelles fonctionnalit´s introduites dans la norme ANSI C99 sont
       e             e                    e
indiqu´es par un ast´risque entre parenth`ses (*).

        assert.h        inttypes.h(*)      signal.h        stdlib.h
        complex.h(*)    iso646.h(*)        stdarg.h        string.h
        ctype.h         limits.h           stdbool.h(*)    tgmath.h(*)
        errno.h         locale.h           stddef.h        time.h
        fenv.h(*)       math.h             stdint.h(*)     wchar.h(*)
        float.h         setjmp.h           stdio.h         wctype.h(*)

        e    e e
De mani`re g´n´rale, pour obtenir le maximum d’informations sur une com-
mande, on utilisera la commande man.


9.1    Diagnostics d’erreurs <assert.h>
              e
Cette biblioth`que ne fournit qu’une seule fontion, assert, qui permet de mettre
des assertions dans le source du programme. Son prototype est le suivant :
void assert(int expression);
`     e                     e                  e      a
A l’ex´cution, si le param`tre de assert s’´value ` faux, le programme est
     e
stopp´ sur terminaison anormale. Exemple :

#include <assert.h>
#include <stdio.h>

#define TAILLE 512

int calcul(int tab[], int i) {
    assert(i >= 0 && i < TAILLE);
    return 2 * tab[i] + 5;
}



                                      93
int main() {
  int tableau[TAILLE];
#ifndef NDEBUG
  printf("DEBUG: tableau[0] = %d\n", tableau[0]);
#endif
  printf("%d\n", calcul(tableau, 1024));
  return 0;
}

    e
L’ex´cution de ce programme renverra :

DEBUG: tableau[0] = 180613
a.out: toto.c:7: calcul: Assertion ‘i >= 0 && i < 512’ failed.
Abandon


9.2     Gestion des nombres complexes <complex.h>
                e                e
Cette biblioth`que permet de g´rer les nombres complexes, de la forme
                u                           a               e
z = a + i ∗ b, o` a, b ∈ R. On rappelle que√ est la partie r´elle de z, b sa partie
imaginaire et i le nombre imaginaire i = −1 (i2 = −1).
man complex pour des exemples de code utilisant ces nombres complexes.


9.3                            e
        Classification de caract`res et changements de
        casse <ctype.h>
                                                    ee               e       e
Toutes ces fonctions permettent de tester une propri´t´ sur le caract`re pass´
         e
en param`tre :

    Fonction    Prototype                                    e
                                         Teste si le param`tre est
     isalnum    int isalnum(int c)       une lettre ou un chiffre
     isalpha    int isalpha(int c)       une lettre
     isblank    int isblank(int c)       un espace ou une tabulation
     iscntrl    int iscntrl(int c)                 e
                                         un caract`re de commande
     isdigit    int isdigit(int c)                   e
                                         un chiffre d´cimal
     isgraph    int isgraph(int c)                 e
                                         un caract`re imprimable ou le blanc
     islower    int islower(int c)       une lettre minuscule
     isprint    int isprint(int c)                 e
                                         un caract`re imprimable (pas le blanc)
     ispunct    int ispunct(int c)       un signe de ponctuation
     isspace    int isspace(int c)                 e
                                         un caract`re d’espace blanc
     isupper    int isupper(int c)       une lettre majuscule
    isxdigit    int isxdigit(int c)                      e
                                         un chiffre hexad´cimal

           e
On dispose ´galement de deux fonctions de conversions majuscules/minuscules :

           Fonction    Prototype               Action
            tolower    int tolower(int c)      convertit c en minuscule
            toupper    int toupper(int c)      convertit c en majuscule.


                                        94
9.4     Valeur du dernier signal d’erreur <errno.h>
              e          e
Cette biblioth`que ne d´finit qu’une variable globale,
extern int errno;
                             e                  e                     e
dont la valeur est un num´ro d’erreur affect´ lors d’appels syst`mes (et par
                                                                               e
certaines fonctions de librairie). Cette valeur n’a de sens que si l’appel syst`me
               e
s’est mal pass´ et retourne une erreur (la valeur -1). Dans ce cas, la variable
errno indique quel type d’erreur s’est produite.
man errno fournit la liste des valeurs d’erreurs valides.


9.5                                a
        Gestion d’un environnement ` virgule flottante
        <fenv.h>
Le standard C99 a introduit via <fenv.h> la notion d’environnement a vir-`
                                         e              e     e
gule flottante afin de permettre une repr´sentation plus d´taill´e des conditions
                       e
d’erreurs dans l’arithm´tique en virgule flottante.
                     e         e
Deux variables syst`mes sont d´finies dans cet environnement :
                               e
  1. les flags de statut, utilis´s dans la gestion des exceptions en virgule flot-
     tante.
                         o         e                 e
  2. les modes de contrˆle qui d´terminent les diff´rents comportements de
             e                               e                      e
     l’arithm´tique en virgule flottante (la m´thode d’arrondi utilis´e par exemple).

9.5.1   Gestion des exceptions
                                                        e e
Toute erreur (exception) en virgule flottante est caract´ris´e par une constante
symbolique :
– FE_DIVBYZERO : division par 0 ;
                     e             e
– FE_INEXACT : le r´sultat de l’op´ration n’est pas exact ;
                  e         e
– FE_INVALID : r´sultat ind´fini ;
                    e
– FE_OVERFLOW : d´passement de capacit´ ; e
                         e
– FE_UNDERFLOW : sous-d´passement de capacit´.   e
                                              a
La constante FE_ALL_EXCEPT est un OU bit-`-bit de toutes les constantes pr´-   e
 e
c´dentes.
                                   e         e                            a
Les fonctions suivantes sont utilis´es pour g´rer ces exceptions. Toutes (` l’ex-
                                                        e
ception de fetestexcept) retournent 0 en cas de succ`s. L’argument excepts
                               e e
indique l’une des constantes pr´c´dentes.


    Fonction         Prototype                            Action
  feclearexcept      int feclearexcept(int excepts)       Efface les exceptions excepts
 fegetexceptflag     int fegetexceptflag(fexcept_t        Sauvegarde le statut d’exception
                     *flagp, int excepts)                    e e
                                                          sp´cifi´ dans *flagp
  feraiseexcept      int feraiseexcept(int excepts)         e
                                                          D´clenche l’exception expects
 fesetexceptflag     int fesetexceptflag(fexcept_t        Sauvegarde le statut de l’excep-
                     *flagp, int excepts)                 tion expects dans *flagp
   fetestexcept      int fetestexcept(int excepts)        renvoie les bits correspondants
                                                          aux exceptions courantes


                                       95
9.5.2   Gestion des arrondis
                           e                          e e
fenv.h supporte quatre m´thodes d’arrondis, caract´ris´es par les constantes
symboliques suivantes :
                                          e
– FE_DOWNWARD : arrondi vers la valeur inf´rieure la plus proche ;
– FE_TONEAREST : arrondi vers la valeur la plus proche ;
                              e
– FE_TOWARDZERO : partie enti`re ;
                                        e
– FE_UPWARD : arrondi vers la valeur sup´rieure la plus proche.
Les fonctions int fegetround() et int fesetround( int rounding_mode)
                                     e
permettent de lire et de changer la m´thode d’arrondi courante.

9.5.3   Gestion des environnements en virgule flottante.
                                                    e           e
L’environnement de travail en virgule flottante peut ˆtre manipul´ via les fonc-
tions suivantes sous forme d’un seul objet opaque de type fenv_t. L’environ-
              e            e   e
nement par d´faut est repr´sent´ par la constante FE_DFL_ENV.


   Fonction      Prototype                      Action
   fegetenv      int fegetenv(fenv_t *envp)     Sauve l’environnement courant dans *envp
 feholdexcept    int feholdexcept(fenv_t        idem, et efface tous les flags d’exceptions et
                 *envp)                         continue en mode ”sans interruption”
  fesetenv       int fesetenv(fenv_t *envp)                                a
                                                recharge l’environnement ` *envp
 feupdateenv     int feupdateenv(fenv_t                                         ea e
                                                idem et replace les exceptions d´j` pr´sentes
                 *envp)                         dans *envp


9.6                     e
        Intervalle et pr´cision des nombres flottants <float.h>
           e                                        e                  e
float.h d´finit des constantes symboliques qui caract´risent les types r´els, en
particulier :

          FLT_DIG       nombre de chiffres significatifs d’un float
          DBL_DIG       nombre de chiffres significatifs d’un double
          LDBL_DIG      nombre de chiffres significatifs d’un long double
        FLT_EPSILON     le plus petit nombre tel que 1.0 + x != 1.0
        DBL_EPSILON     le plus petit nombre tel que 1.0 + x != 1.0
        LDBL_EPSILON    le plus petit nombre tel que 1.0 + x != 1.0
          FLT_MAX       le plus grand nombre du type float
          DBL_MAX       le plus grand nombre du type double
          LDBL_MAX      le plus grand nombre du type long double
          FLT_MIN       le plus petit nombre du type float
          DBL_MIN       le plus petit nombre du type double
          LDBL_MIN      le plus petit nombre du type long double


9.7       e                                     e
        D´finitions de types entiers de taille fix´e <int-
        types.h>
           e                  e
La biblioth`que <inttypes.h> d´finit au moins les types suivants :

                                      96
        int8_t                     e
                   type entier sign´ sur 8 bits
        int16_t                    e
                   type entier sign´ sur 16 bits
        int32_t                    e
                   type entier sign´ sur 32 bits
        int64_t                    e
                   type entier sign´ sur 64 bits
        uint8_t                         e
                   type entier non sign´ sur 8 bits
       uint16_t                         e
                   type entier non sign´ sur 16 bits
       uint32_t                         e
                   type entier non sign´ sur 32 bits
       uint64_t                         e
                   type entier non sign´ sur 64 bits
       intptr_t               e
                   entier sign´ pouvant stocker un pointeur (une adresse)
       uintptr_t                  e
                   entier non sign´ pouvant stocker un pointeur (une adresse)

                             e
En pratique, ces types sont d´finis dans stdint.h (voir §9.17).


9.8               e
        Alias d’op´rateurs logiques et binaires <iso646.h>
            e                                 e
<iso646.h> d´finit des alias (synonymes) aux op´rateurs binaires et logiques :

              and          ⇐⇒      &&        and_eq      ⇐⇒      &=
              bitand       ⇐⇒      &         bitor       ⇐⇒       |
              compl        ⇐⇒      ~         not         ⇐⇒       !
              not_eq       ⇐⇒      !=        or          ⇐⇒      ||
              or_eq        ⇐⇒      |=        xor         ⇐⇒       ^
              xor_eq       ⇐⇒      ^=


9.9     Intervalle de valeur des types entiers <limits.h>
                      e                                      e
Le fichier <limits.h> d´finit les constantes symboliques qui pr´cisent les valeurs
                                    e        e   e
maximales et minimales qui peuvent ˆtre repr´sent´es par un type entier donn´.e

      Type         Minimum      Maximum                               e
                                               Maximum (types non-sign´s)
      char         CHAR_MIN      CHAR_MAX             UCHAR_MAX
   signed char     SCHAR_MIN    SCHAR_MAX
      short        SHRT_MIN      SHRT_MAX                USHRT_MAX
       int          INT_MIN       INT_MAX                 UINT_MAX
      long         LONG_MIN      LONG_MAX                ULONG_MAX
    long long      LLONG_MIN    LLONG_MAX                ULLONG_MAX

              e    e
<limits.h> d´finit ´galement la constanste CHAR_BIT, correspondant au nombre
de bits du type char.


9.10     Gestion de l’environnement local <locale.h>
                                      e
Il y a deux fonctions permettant de g´rer les conventions nationales concernant
  e                   e                                    e              e
l’´criture du point d´cimal dans les nombres, le signe repr´sentant l’unit´ mo-
  e
n´taire etc... Ces fonctions sont setlocale et localeconv (faire un man sur ces
                          e
fonctions pour plus de d´tails).

                                        97
9.11                        e
          Les fonctions math´matiques de <math.h>
Pour utiliser les fonctions de cette librairie, il faut en plus de l’inclusion de la
                                                                          e
librairie par la directive #include <math.h> ajouter l’option -lm a l’´dition de
lien. Ainsi, la compilation de prog.c utilisant la librairie <math.h> se fera par
la commande : gcc -g3 -Wall -lm prog.c -o prog
     e                     e
Le r´sultat et les param`tres de toutes ces fonctions sont de type double. Si
           e
les param`tres effectifs sont de type float, ils seront convertis en double par le
compilateur.

9.11.1                       e
           Fonctions trigonom´triques et hyperboliques
         Fonction     e
                     S´mantique               Fonction       e
                                                           S´mantique
           acos      arc cosinus                 cos       cosinus
           asin      arc sinus                   sin       sinus
           atan      arc tangente                tan       tangente
           cosh      cosinus hyperbolique       sinh       sinus hyperbolique
           tanh      tangente hyperbolique     atan2       arc tangente


9.11.2     Fonctions exponentielles et logarithmiques
          Fonction       e
                       S´mantique
            exp        exponentielle
           frexp       e          e
                       ´tant donn´ x, trouve n et p tels que x = n * 2p
           ldexp                                                  e
                       multiplie un nombre par une puissance enti`re de 2
            log        logarithme
           log10                    e
                       logarithme d´cimal
            modf                          e       e
                       calcule partie enti`re et d´cimale d’un nombre


9.11.3     Fonctions diverses
            Fonction       e
                         S´mantique
              ceil                                                 e
                         entier le plus proche par les valeurs sup´rieures
              fabs       valeur absolue
             floor                                                e
                         entier le plus proche par les valeurs inf´rieures
              fmod       reste de division
              pow        puissance
              sqrt                   e
                         racine carr´e


9.12      Branchements non locaux <setjmp.h>
                       e     e                          e
L’instruction goto pr´sent´e au §2.2.6 ne permet de r´aliser des branchements
                    e          e           e                         a    e
qu’au sein d’une mˆme proc´dure. Pour r´aliser des branchements ` l’ext´rieur
            e
d’une proc´dure, il faut utiliser setjmp et longjmp.
Prototype de ces fonctions :
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
        e e                                                            e
Plus pr´cis´ment, les fonctions setjmp et longjmp permettent de g´rer les er-
                                   e
reurs et les interruptions rencontr´es dans des routines bas-niveau. setjmp sau-

                                         98
                                                                             e
vegarde le contexte de pile et d’environnement dans env afin de l’utiliser ult´-
rieurement avec longjmp.
               e
Pour plus de d´tails, utiliser le man sur ces fonctions.


9.13      Manipulation des signaux <signal.h>
           e e                                        e
Lorsqu’un ´v`nement exceptionnel se produit, le syst`me d’exploitation peut
envoyer un signal aux processus. Ceci peut arriver en cas d’erreur grave (une
erreur d’adressage par exemple). Le tableau suivant fournit la liste de quelques
                         e                                 e
constantes symboliques d´finies dans <signal.h> et caract´risant ces erreurs :
   Signal     Signification
  SIGABRT     Fin anormale du programme (ex : utilisation de la fonction abort())
  SIGFPE                 a
              Exception ` la virgule flottante.
  SIGILL                     e             e
              Instruction ill´gale rencontr´e dans le code machine.
  SIGINT      Interruption par la touche Ctrl-C.
  SIGSEGV                                      e    e    a    e
              Violation de Segmentation : acc`s ill´gal ` la m´moire.
  SIGTERM                      e
              Terminer : requˆte pour terminer le programme.
                                               e
Deux fonctions permettent d’interagir avec le m´canisme des signaux :

 Fonct.     Prototype                          Action
 raise      int raise(int sig)                                                      e
                                               Envoie le signal sig au processus ex´cutant.
 signal     typedef void (*sigh_t)(int) ;         e        e
                                               pr´cise la r´ponse du programme au signal
            sigh_t signal(int signum,          signum. handler est un pointeur vers la fonc-
            sigh_t handler) ;                  tion effectuant le traitement de ce signal.
Comme toujours, le man reste la meilleur source d’informations sur ce sujet.


9.14                              e
          Nombre variable de param`tres <stdarg.h>
        e
Si on d´sire programmer une fonction avec un nombre variable de param`tres  e
(comme printf), on dispose des macros va_start, va_arg et va_end.
                                             e                  e     e
L’utilisation de ces macros et du type associ´ va_list est abord´ en d´tail dans
le §5.7 page 67.


9.15       e                    e
          D´finition du type bool´en <stdbool.h>
                                 e
La librairie <stdbool.h>, ajout´e avec la norme ANSI C99, introduit la notion
       e        e                e                             e
de bool´en et d´finit le type bool´en bool et les valeurs associ´es, true et false.


9.16       e
          D´finitions standards <stddef.h>
                                                 e e       e
Cette librairie introduit un ensemble de types pr´d´finis tr`s utiles qui sont
    e
list´s dans le tableau suivant :
    Type       Description
  ptrdiff_t        e                                      e e
               Diff´rence entre deux pointeurs (≃ int en g´n´ral)
   wchar_t             e               e   e
               Employ´ pour les caract`res ´tendus (voir §9.23)
   size_t                                                                e e
               Taille d’un objet en nombres d’octets (≃ unsigned int en g´n´ral)

                                        99
              e     e                               e
<stddef.h> d´finit ´galement le pointeur NULL, utilis´ lors de l’initialisation des
pointeurs (cf chapitre 3) ainsi que la fonction :
size_t offsetof(type, member-designator)
                     ıtre    e
qui permet de connaˆ le d´calage (en nombre d’octets) entre un champ d’une
                                          e
structure (member-designator) et le d´but de cette structure (type). Ainsi,
                e
si on suppose d´finie la structure toto ayant un champ next (voir §4.3.5), le
  e                                e
d´calage du champ data est donn´ par l’expression :
offsetof (struct toto, data)


9.17      e
         D´finitions de types entiers <stdint.h>
La libraisie <stdint.h> est un sous ensemble de la librairie <inttypes.h> (voir
                                          e e        e
§9.7) et en constitue donc une version all´g´e, adapt´e aux environnements em-
      e                                                   e                 e
barqu´s qui peuvent ne pas supporter les fonctions d’entr´es/sorties formatt´es.


9.18         e
         Entr´es-sorties <stdio.h>
9.18.1      Manipulation de fichiers

 Fonction     Prototype                              Action (voir chapitre 6)
   fopen      FILE *fopen(char *path, char *mode)    ouverture d’un fichier
   fclose     int fclose(FILE *fp)                   Fermeture d’un fichier
   fflush     int fflush(FILE *stream)               e                       e
                                                     ´criture des buffers en m´moire dans le fichier
  tmpfile     FILE *tmpfile (void)                     e
                                                     cr´ation d’un fichier temporaire


9.18.2          e                    e
            Entr´es et sorties format´es

 Fonction     Prototype                                        Action (voir §2.5/chapitre 6)
  fprintf     int fprintf(FILE *stream, char *format, ...)     e
                                                               ´criture sur un fichier
   fscanf     int fscanf(FILE *stream, char *format, ...)      lecture depuis un fichier
   printf     int printf(char *format, ...)                    e
                                                               ´criture sur la sortie standard
   scanf      int scanf(char *format, ...)                                           e
                                                               lecture depuis l’entr´e standard
  sprintf     int sprintf(char *s, char *format, ...)          e                    ıne         e
                                                               ´criture dans la chaˆ de caract`res s
   sscanf     int sscanf(char *s, char *format, ...)                                  ıne        e
                                                               lecture depuis la chaˆ de caract`res s


9.18.3                                     e
            Impression et lecture de caract`res

 Fonction     Prototype                             Action
   fgetc      int fgetc(FILE *stream)                                  e
                                                    lecture d’un caract`re depuis un fichier
   fputc      int fputc(int c, FILE *stream)        e                   e
                                                    ´criture d’un caract`re sur un fichier
    getc      int getc(FILE *stream)                e                                e
                                                    ´quivalent de fgetc mais optimis´e
    putc      int putc(int c, FILE *stream)         e                                e
                                                    ´quivalent de fputc mais optimis´e
  getchar     int getchar(void)                                        e
                                                    lecture d’un caract`re depuis stdin
  putchar     int putchar(int c)                    e                   e
                                                    ´criture d’un caract`re sur stdout
   fgets      char *fgets(char *s, FILE *stream)                     ıne          e
                                                    lecture d’une chaˆ de caract`res depuis un fichier
   fputs      int *fputs(char *s, FILE *stream)     e                 ıne          e
                                                    ´criture d’une chaˆ de caract`res sur un fichier
    gets      char *gets(char *s)                                    ıne          e
                                                    lecture d’une chaˆ de caract`res sur stdin
    puts      int *puts(char *s)                    e                 ıne          e
                                                    ´criture d’une chaˆ de caract`res sur stdout


                                       100
9.19        Utilitaires divers <stdlib.h>
9.19.1       Allocation dynamique
                    e
Ces fonctions sont d´crites dans le chapitre 3 et au §3.5.

 Fonct.        Prototype                                       Action
 calloc        void *calloc(size_t n, size_t size)             allocation dynamique de n*size oc-
                                                                                       a e
                                                               tects et initialisation ` z´ro.
 malloc        void *malloc(size_t size)                       allocation dynamique
 realloc       void *realloc(void *ptr, size_t size)                                           e
                                                               modifie la taille d’une zone pr´alable-
                                                                           e
                                                               ment allou´e par calloc ou malloc
   free        void free(void *ptr)                               e                e
                                                               lib`re une zone m´moire

9.19.2                        ınes de caract`res en nombres
             Conversion de chaˆ             e
                                                       ıne        e
Les fonctions suivantes permettent de convertir une chaˆ de caract`res en un
nombre.
 Fonct.       Prototype                          Action
  atof        double atof(char *chaine)          convertit chaine en un double
  atoi        int atoi(char *chaine)             convertit chaine en un int
  atol        long atol(char *chaine)            convertit chaine en un long int

                                 ee
Il semblerait que qu’il faille pr´f´rer maintenant les fonctions strtol, strtoll
et strtoq.

9.19.3        e e                           e
             G´n´ration de nombres pseudo-al´atoires
                                                        e
La fonction rand fournit un nombre entier pseudo-al´atoire dans l’intervalle
[0,RAND_MAX]. RAND_MAX est une constante pr´d´finie au moins ´gale ` 215 − 1.
                                                e e               e     a
    e                                                        e              e
L’al´a fournit par la fonction rand n’est toutefois pas de tr`s bonne qualit´.
                  e              e
La valeur retourn´e par rand d´pend de l’initialisation (germe ou seed en an-
           e e                    e      e     a        e                    e
glais) du g´n´rateur. Cette derni`re est ´gale ` 1 par d´faut mais elle peut ˆtre
       e a
modifi´e ` l’aide de la fonction srand.

 Fonct.      Prototype                               Action
  rand       int rand(void)                                                     e
                                                     fournit un nombre pseudo-al´atoire
 srand       void srand(unsigned int seed)                                         e e
                                                     modifie la valeur du germe du g´n´-
                                                                     e
                                                     rateur pseudo-al´atoire

                                                                           a
On utilise souvent la valeur de l’horloge interne de l’ordinateur (obtenue ` l’aide
de la librairie <time.h>, voir §9.22) pour fournir un ”bon”1 germe ` srand.
                                                                         a
Exemple :

#include <stdlib.h>         //see also ’man 3 rand’
#include <time.h>

/* initialize random generator */
void init_rand(){ srand(time(NULL)); }
  1
      Pas suffisamment bon cependant pour les applications cryptographiques


                                           101
/* return a random number between 1 and m */
unsigned long myRand(unsigned long m){
    return 1+(unsigned long)(((double) m)*rand()/(RAND_MAX+1.0));
}




9.19.4          e
          Arithm´tique sur les entiers


 Fonct.   Prototype                         Action
  abs     int abs(int n)                    valeur absolue d’un entier
  labs    long labs(long n)                 idem mais pour un long int
  div     div_t div(int a, int b)           quotient et reste de la division euclidienne de
                                            a par b.
  ldiv    ldiv_t ldiv(long a, long b)       idem pour les long int




Les structures div_t et ldiv_t disposent de deux champs, quot et rem, qui
              e
stockent les r´sultats des fonctions div et ldiv.




9.19.5    Recherche et tri


Les fonctions qsort (resp. bsearch) permettent de trier un tableau (resp. re-
            ee                       ea e
chercher un ´l´ment dans un tableau d´j` tri´). Pour leurs syntaxes : voir §5.8.




9.19.6    Communication avec l’environnement


 Fonct.   Prototype               Action
 abort    void abort(void)        terminaison anormale du programme
  exit    void exit(int etat)                                               o        e
                                  terminaison du programme ; rend le contrˆle au syst`me
                                  en lui fournissant la valeur etat (0 = fin normal).
 system   int system(char *s)       e                              e     e
                                  ex´cution de la commande syst`me d´finie par s.


                                      102
9.20                          ınes de caract`res <string.h>
           Manipulation de chaˆ             e

  Fonct.    Prototype                                 Action
 strcpy     char *strcpy(char *ch1,char *ch2)         copie ch2 dans ch1 ; retourne ch1.
 strncpy    char *strcpy(char *ch1,char *ch2,                                         e
                                                      idem mais ne copie que n caract`res.
            int n)
 strcat     char *strcat(char *ch1,char *ch2)                   a
                                                      copie ch2 ` la fin de ch1 ; retourne ch1.
 strncat    char *strncat(char *ch1,char *ch2,                                e             e
                                                      idem mais seuls n caract`res sont copi´s
            int n)
 strcmp     int strcmp(char *ch1,char *ch2)           compare ch1 et ch2 pour l’ordre lexicogra-
                                                      phique ;
 strncmp    int strcmp(char *ch1,char *ch2,                                   e               e
                                                      idem mais seuls n caract`res sont compar´s.
            int n)
 strchr     char *strchr(char *ch,char c)                                               e
                                                      retourne un pointeur sur la premi`re occu-
                                                                                      /
                                                      rence de c dans ch et NULL si c ∈ ch.
 strrchr    char *strchr(char *chaine,char c)                                           e
                                                      retourne un pointeur sur la derni`re occu-
                                                                                      /
                                                      rence de c dans ch et NULL si c ∈ ch
 strstr     char *strchr(char *ch1,char *ch2)                                           e
                                                      retourne un pointeur sur la premi`re occu-
                                                                                            /
                                                      rence de ch2 dans ch1 et NULL si ch2 ∈ ch1
 strlen     int strlen(char *chaine)                  retourne la longueur de chaine.


La librairie <string.h> fournit un ensemble de fonctions permettent de g´rer e
       ınes de caract`res pour :
les chaˆ              e
– copier : memcpy, memmove, strcpy, strncpy ;
          e
– concat´ner : strcat, strncat ;
– comparer : memcmp, strcmp, strcoll, strncmp ;
– transformer : strxfrm ;
– rechercher : memchr, strchr, strcspn, strpbrk, strrchr, strspn, strstr,
   strtok ;
– initialiser : memset ;
– mesurer : strlen ;
                                a                e
– obtenir un message d’erreur ` partir du num´ro de l’erreur : strerror.
                                         e
Concernant strcmp, on notera que le r´sultat de cette fonction est
                 e                  e       a
– une valeur n´gative si ch1 est inf´rieure ` ch2 (pour l’ordre lexicographique) ;
– 0 si ch1 et ch2 sont identiques ;
– une valeur positive sinon.



9.21                e e                             e
           Macros g´n´riques pour les fonctions math´ma-
           tiques <tgmath.h>
Cette librairie, introduite avec la norme ANSI C99, est un sur-ensemble des
                                             e
librairies <math.h> et de <complex.h> et d´finit pour chacune des fonctions
            a                                        e
communes ` ces deux librairie une version similaire d´finie sous forme de macro
                 e e                                         e
pour un type g´n´rique. La liste de ces fonctions est donn´e dans le tableau
suivant :

                                       103
                <math.h>      <complex.h>              e e
                                               Type-g´n´rique
                 Fonction       Function          Macro
                   acos()         cacos()           acos()
                   asin()         casin()           asin()
                   atan()         catan()          atan()
                  acosh()        cacosh()          acosh()
                  asinh()        casinh()          asinh()
                  atanh()        catanh()         atanh()
                    cos()          ccos()            cos()
                    sin()          csin()            sin()
                    tan()          ctan()            tan()
                   cosh()         ccosh()          cosh()
                   sinh()         csinh()           sinh()
                   tanh()         ctanh()          tanh()
                    exp()          cexp()           exp()
                    log()          clog()            log()
                   pow()          cpow()            pow()
                   sqrt()         csqrt()           sqrt()
                   fabs()          cabs()           fabs()

             e
Pour un compl´ment d’informations :
http://www.opengroup.org/onlinepubs/009695399/basedefs/tgmath.h.html


9.22       Date et heure <time.h>
Plusieurs fonctions permettent d’obtenir la date et l’heure. Le temps est re-
  e    e
pr´sent´ par des objets de type time_t ou clock_t, lesquels correspondent
 e e           a              `
g´n´ralement ` des int ou a des long int. Les fonctions de manipulation de
la date et de l’heure sont : clock, difftime, mktime, time, asctime, ctime,
gmtime, localtime, strftime.
                         e
Les plus utiles sont list´es dans le tableau suivant :

  Fonct.     Prototype                       Action
   time      time_t time(time_t *tp)                                                  e   e
                                             retourne dans *tp le nombre de secondes ´coul´es
                                             depuis le 1er janvier 1970, 0 heures G.M.T.
 difftime    double difftime(time_t t1,                      e
                                             retourne la diff´rence t1 - t2 en secondes.
             time_t t2)
  ctime      char *ctime(time_t *tp)                                   ıne         e
                                             convertit *tp en une chaˆ de caract`res explici-
                                                                               e e      e
                                             tant la date et l’heure (format pr´d´termin´).
  clock      clock_t clock(void)             renvoit le temps CPU (ms) depuis le dernier
                                             clock.


9.23                             e    e
           Manipulation de caract`res ´tendus <wchar.h>
           et <wctype.h>
                      e                                     e
Le type char est cod´ sur 8 bits et le restera parce qu’il d´signe la plus petite
    e         e
unit´ de donn´es adressable.
                                                                         e
Dans le cadre du design d’applications multilingues, on devra donc g´rer des
      e              ınes au format UNICODE.
caract`res et des chaˆ

                                      104
A cet effet, la norme ANSI C99 a introduit :
                    e     e
– un type de caract`re cod´ sur 16 bits wchar_t ;
                                       a
– un ensemble de fonctions similaires ` celles contenues dans <string.h> et
                 e   e
  <ctype.h> (d´clar´es respectivement dans <wchar.h> et <wctype.h>) ;
– un ensemble de fonctions de conversion entre les types char * et wchar_t *
    e    e
  (d´clar´es dans <stdlib.h>).
               e
Pour plus de d´tails :
http://www.opengroup.org/onlinepubs/007908799/xsh/wchar.h.html ou
http://www.freenix.fr/unix/linux/HOWTO/Unicode-HOWTO-5.html




                                    105
Annexe A

Etude de quelques exemples

A.1     Gestion des arguments de la ligne de commande
                                          e    e
Le code source suivant traite l’exemple pr´sent´ au §4.2.6 page 50.
Commande de compilation : gcc -O3 -Wall commande.c -o commande
/**
 * @file   command.c
 * @author Sebastien Varrette <Sebastien.Varrette@imag.fr>
 * @date   Tue Oct 4 2005
 *
 * @brief see command.h
    traitement des arguments de la ligne
    de commande utilisant la librairie <getopt.h>
                 e
    Exemple trait´: programme ’command’ au format d’appel
                   e e
    suivant (les ´l´ments entre crochets sont optionnels):

  ./command [-i arg_i] [-f arg_f] [-s arg_s] [-k arg_k] [-h] [-V] arg_file

  Les options de la ligne de commande sont donc:
                    e
    -i: permet de sp´cifier la valeur de arg_i, de type int
                    e
        Valeur par d´faut: 14
                    e
    -f: permet de sp´cifier la valeur de arg_f, de type float
                    e
        Valeur par d´faut: 1.5
                    e                                  ı            e
    -s: permet de sp´cifier la valeur de arg_s, une cha^ne de caract`res
            e                            e
        poss´dant au plus MAX_SIZE caract`res
                    e
        Valeur par d´faut: "On est super content!"
    -h: affiche l’aide (auteur, but du programme et usage) et sort du
        programme.
    -V: affiche la version du programme et sort

                        e                                   e
   arg_file est un param`tre obligatoire du programme qui sp´cifie
                          ı            e
       la valeur de la cha^ne de caract`res arg_file.
 */
 /********************************************************************************/
#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <assert.h>    /* for assert */
#include <string.h>
#include <getopt.h>

//#include "toto.h"


                                      106
#define VERSION 0.1    // source version
#define MAX_SIZE 256   // maximal size of strings, including ’\0’

void printHelp(char * command);                        // print help message
void printError(char * error_message, char * command); // print error message
void printVersion(char * command);                     // print version

/**
 * Entry point where the program begins its execution.
 * @param argc number of arguments of the command line
 * @param argv multi-array containing the command line arguments
 *             (argv[0] is the name of the program, argv[argc]=NULL)
 * @return status of the execution (0: correct execution)
 */
int main(int argc, char * argv[]){

    // Command line management
    extern char *optarg; // specific to getopt
    extern int optind;    //, opterr, optopt; // id.
    char c;

    // to deal with options
    char opt_i[MAX_SIZE] = "";
    char opt_f[MAX_SIZE] = "";
    long arg_i = 14;
    double arg_f = 1.5;
    char arg_s[MAX_SIZE] = "On est super content!";
    char arg_file[MAX_SIZE] = "";


    // Management of parameters in command line
    while ((c = getopt(argc, argv, "i:f:s:hV")) != -1) {
        switch(c) {
        case ’h’: printHelp(argv[0]);    return EXIT_SUCCESS;
        case ’V’: printVersion(argv[0]); return EXIT_SUCCESS;
        case ’i’:
            assert(strlen(optarg) < MAX_SIZE); // check size
            strncpy(opt_i,optarg,MAX_SIZE);    // copy current arg to opt_i
            // Minimal check for validity (opt_i isn’t a command line option)
            if (opt_i[0] == ’-’) printError("Bad Format for arg_i!",argv[0]);
            arg_i = strtol(opt_i, NULL, 10);   // last arg: base for convertion
            break;
        case ’f’:
            assert(strlen(optarg) < MAX_SIZE); // check size
            strncpy(opt_f,optarg,MAX_SIZE);    // copy current arg to opt_f
            // Minimal check for validity (opt_f isn’t a command line option)
            if (opt_f[0] == ’-’) printError("Bad Format for arg_f!",argv[0]);
            arg_f = strtod(opt_f, NULL);       // conversion
            break;
        case ’s’:
            assert(strlen(optarg) < MAX_SIZE); // check size
            strncpy(arg_s,optarg,MAX_SIZE);    // copy current arg to arg_s
            if (arg_s[0] == ’-’) printError("Bad Format for arg_i!",argv[0]);
            break;
        default: printError("Bad Format!",argv[0]);
        }



                                      107
    }

    // Now proceed to detect errors and/or set the values

    //   parameter arg_file is required. If not, print error and exit
    //   argc - optind == 0 : no arg_file
    //   argc - optind > 1 : to many parameters or error not detected
    if   ((argc - optind) != 1) printError("Bad Format",argv[0]);

    // required parameter: arg_file
    assert(strlen(argv[optind]) < MAX_SIZE);
    strncpy(arg_file,argv[optind],MAX_SIZE);

    //Print values
    printf("arg_i = %ld\n",arg_i);
    printf("arg_f = %f\n",arg_f);
    printf("Valeur de arg_s: %s\n",arg_s);
    printf("Valeur de arg_file: %s\n",arg_file);

    return EXIT_SUCCESS;
}


/**
 * Print the help message
 * @param command used (argv[0])
 */
void printHelp(char * command) {
    printf("NAME\n"                                                               );
    printf("      %s\n",command                                                   );
    printf("\nSYNOPSIS\n"                                                         );
    printf("      %s [-h] [-V]\n",command                                         );
    printf("      %s [-i arg_i] [-f arg_f] [-s arg_s] arg_file\n",command         );
    printf("\nDESCRIPTION\n"                                                      );
    printf("      -h : print help and exit\n"                                     );
    printf("      -V : print version and exit\n"                                  );
    printf("      -i arg_i : set arg_i (long)\n"                                  );
    printf("            Default value : 14\n"                                     );
    printf("      -f arg_f : set arg_f (float)\n"                                 );
    printf("            Default value : 1.5\n"                                    );
    printf("      -s arg_s : set arg_s (char *), a string with at most MAX_SIZE\n");
    printf("            characters. Default value : \"Toto\"\n"                   );
    printf("\nAUTHOR\n"                                                           );
    printf("      Sebastien Varrette <Sebastien.Varrette@imag.fr>\n"              );
    printf("      Web page : http://www-id.imag.fr/~svarrett/\n"                  );
    printf("\nREPORTING BUGS\n"                                                   );
    printf("      Please report bugs to <Sebastien.Varrette@imag.fr>\n"           );
    printf("\nCOPYRIGHT\n"                                                        );
    printf("      This is free software; see the source for copying conditions.\n");
    printf("      There is NO warranty; not even for MERCHANTABILITY or FITNESS\n");
    printf("      FOR A PARTICULAR PURPOSE."                                      );
    printf("\nSEE ALSO\n"                                                         );
    printf("      http://www-id.imag.fr/~svarrett/perso.html\n"                   );
}

/**
 * print error message on stderr



                                        108
 * @param error_message Error message to print
 * @param command       command used (argv[0])
 */
void printError(char * error_message, char * command) {
    fprintf(stderr, "[ERROR] %s\n",error_message);
    fprintf(stderr, "Use ’%s -h’ for help\n", command);
    exit(EXIT_FAILURE);
}
/**
 * print version
 * @param command used (argv[0])
 */
void printVersion(char * command) {
    fprintf(stderr, "This is %s version %f\n",command,VERSION);
    fprintf(stderr, "Please type ’%s -h’ for help\n", command);
}




                                     109
A.2                         ın´
        Exemple de liste chaˆ ee
                                          e    e
Le code source suivant traite l’exemple pr´sent´ au §4.3.5 page 53.
Commande de compilation : gcc -O3 -Wall toto.c -o toto

/*********************************************************
 Fichier: toto.c
 Auteur: Sebastien Varrette <Sebastien.Varrette@imag.fr>

                                       e
 Un exemple de gestion des listes chain´es
 *********************************************************/
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

    e
/* D´finition de la structure toto*/
struct toto {
    int data;
    struct toto *next;
};
//type liste synonyme du type pointeur vers une struct toto
typedef struct toto *liste;
/**************************************
      e       e e         e
 * Ins`re un ´l´ment en t^te de liste *
 **************************************/
liste insere(int element, liste Q) {
    liste L;
    L = (liste)malloc(sizeof(struct toto)); // allocation de la zone
                                                e         e
                                            // m´moire nec´ssaire
    L->data = element;
    L->next = Q;
    return L;
}
/***************************************
      e       e e
 * Ins`re un ´l´ment en queue de liste *
 ***************************************/
liste insereInTail(int element, liste Q) {
    liste L, tmp=Q;
    L = (liste)malloc(sizeof(struct toto)); // allocation de la zone
                                                e         e
                                            // m´moire nec´ssaire
    L->data = element;
    L->next = NULL;
    if (Q == NULL)
         return L;
    // maintenant tmp=Q est forcement non vide => tmp->next existe
    while (tmp->next != NULL) tmp = tmp->next; // deplacement jusqu’au
                                               // dernier elt de la liste
    tmp->next = L;
    return Q;
}
/***************************************
               e e         e
 * Supprime l’´l´ment en t^te de liste *
 ***************************************/
liste supprime_tete(liste L) {
    liste suivant = L;
                                   u
    if (L != NULL) { // pour etre s^r que L->next existe
        suivant= L->next;


                                      110
                     e                        e
       free(L); //lib´ration de l’espace allou´ pour une cellule
    }
    return suivant;
}
/*****************************
 * Supprime toute la liste L *
 *****************************/
liste supprime(liste L) {
    while (L != NULL)
                                                   e
        L = supprime_tete(L); //suppression de la t^te de liste
    return L;
}
/************************************
 * Affiche le contenu de la liste L *
 ************************************/
void print_liste(liste L) {
    liste tmp = L;
                                         e
    while (tmp != NULL) { //on aurait pu ´crire ’while (tmp)’
        printf("%d \t",tmp->data);
        tmp = tmp->next;
    }
    printf("NULL\n");
}

/********************************************************/
int main() {
    liste L;
    L = insere(14,insere(2,insere(3,insere(10,NULL))));
    L = insereInTail(15,(insereInTail(14,L)));
    printf("Impression de la liste:\nL = \t");
    print_liste(L);
    printf("Suppression de l’element en tete:\nL = \t");
    L = supprime_tete(L);
    print_liste(L);
    printf("Suppression de la liste:\nL = \t");
    L = supprime(L);
    print_liste(L);
    return 0;
}




                                      111
Annexe B

         e e
Makefile g´n´rique

####################################################################################
# Makefile (configuration file for GNU make - see http://www.gnu.org/software/make/)
#
# Compilation of files written in C/C++ (version 0.5)
#
# Author : Sebastien Varrette <Sebastien.Varrette@imag.fr>
#           Web page : http://www-id.imag.fr/~svarrett/
#
# --------------------------------------------------------------------------------
# This is a generic makefile in the sense that it doesn’t require to be
# modified when adding/removing new source files.
# --------------------------------------------------------------------------------
#
# Documentation on the Makefile utility may be found at
#                    http://www.gnu.org/software/make/manual/make.html
#
# Available Commands
# ------------------
# make            : Compile files, binary is generated in the current directory
# make force      : Force the complete re-compilation, even if not needed
# make clean      : Remove backup files generated by emacs and vi
# make realclean : Remove all generated files
# make doc        : Generate Doxygen documentation (in Doc/) see www.doxygen.org
# make help       : print help message
#
############################## Variables Declarations ##############################
# Name of the executable to generate --- TO BE ADAPTED ---
EXE           = toto
# Directory where header files (.h) and object files (.o) will be placed
INCLUDE_DIR = Include
OBJ_DIR       = Obj
# File entensions for C, C++ and header files
C_EXT         = c
CPP_EXT       = cpp
H_EXT         = h
# Source files
SRC           = $(wildcard *.$(C_EXT) *.$(CPP_EXT))
SRC_H         = $(wildcard *.$(H_EXT) $(INCLUDE_DIR)/*.$(H_EXT))
ALL_SRC       = $(SRC) $(SRC_H)
# Check avalibility of source files
ifeq ($(SRC),)
all:
        @echo "No source files available - I can’t handle the compilation"
        @echo "Please check the presence and/or extension of source files "
        @echo "(This makefile is configured to manage *.$(C_EXT) or *.$(CPP_EXT) - "\
                 "you may modify variables C_EXT and CPP_EXT to reflect your " \
                 "own programming style)"
else


                                          112
# Object files
OBJ           = $(patsubst %.$(C_EXT),%.o,$(SRC:.$(CPP_EXT)=.o))
ABSOBJ        = $(OBJ:%.o=$(OBJ_DIR)/%.o)
# Backup files generated by text editors
BACKUP_FILE = $(shell find . -name "*~")
# Doxygen stuff
DOC_DIR       = Doc
DOXYGEN       = $(shell which doxygen)
DOXYGEN_CONF = .doxygen.conf
YES_ATTRIBUTES := JAVADOC_AUTOBRIEF EXTRACT_ALL EXTRACT_PRIVATE EXTRACT_STATIC \
                  SOURCE_BROWSER GENERATE_MAN
# Compilator configuration
# Detect if you have a C or a C++ project through file extension
ifneq ($(filter %.c,$(SRC)),)
         CXX    = gcc
         YES_ATTRIBUTES := $(YES_ATTRIBUTES) OPTIMIZE_OUTPUT_FOR_C
else
         CXX    = g++
         SPECIAL_CPP_OPTION = -Wno-deprecated
endif
CXXFLAGS      = -g3 -O3 -Wall $(SPECIAL_CPP_OPTION) -I$(INCLUDE_DIR) -c
ADD_OPT       = #-lntl -lgmp -lm # Optionnal option for the linker
# Specifies the list of directories that make should search
VPATH         = $(INCLUDE_DIR):$(OBJ_DIR)
# dependance file used for make rules
MAKEDEP_FILE = .Makefile.dep
############################### Now starting rules ################################
# Required rule : what’s to be done each time
all :    $(MAKEDEP_FILE) $(EXE) TAGS

# Generate TAGS for emacs
TAGS : $(ALL_SRC)
        etags $<
        cp TAGS $(INCLUDE_DIR)/

# Clean Options
clean :
        @echo "Remove backup files generated by emacs and vi"
        rm -f $(BACKUP_FILE)

# Clean everything (including object files, binaries and documentation)
realclean : clean
        @echo "Remove object files"
        rm -f $(ABSOBJ)
        @echo "Remove generated executable"
        rm -f $(EXE)
        @if [ ! -z "$(DOC_DIR)" -a ! -z "$(DOXYGEN)" ]; then \
                echo "Remove documentation (’make doc’ to regenerate it)"; \
                rm -rf $(DOC_DIR)/*; \
        fi

# Force re-compilation, even if not required
force :
        touch $(ALL_SRC) $(ABSOBJ)
        @$(MAKE)

# Generate the dependance file
$(MAKEDEP_FILE) : $(ALL_SRC)
        $(CXX) $(SPECIAL_CPP_OPTION) -MM -I$(INCLUDE_DIR) $(SRC) > $@

include $(MAKEDEP_FILE)

# Generic description for compilation of object files
%.o : %.$(C_EXT)
        $(CXX) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@
%.o : %.$(CPP_EXT)



                                          113
        $(CXX) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@

# Generation of the final binary (see $(EXE))
$(EXE) : $(OBJ) $(ALL_SRC)
        $(CXX) -g -o $@ $(ABSOBJ) $(ADD_OPT)
        @$(MAKE) help

# Help rule -   print help message
help :
        @echo   ’+-----------------------------------------------------------------------------+’
        @echo   ’|                             Available Commands                              |’
        @echo   ’+----------------+------------------------------------------------------------+’
        @echo   ’| make           | Compile files, binary is generated in the current directory|’
        @echo   ’| make force     | Force the complete re-compilation, even if not required    |’
        @echo   ’| make clean     | Remove cache backup files generated by emacs and vi        |’
        @echo   ’| make realclean | Remove all generated files (including .o and binary)       |’
        @echo   ’| make doc       | Generate documentation using doxygen (see www.doxygen.org) |’
        @echo   ’| make help      | Print help message                                         |’
        @echo   ’+----------------+------------------------------------------------------------+’

# Test values   of variables - for debug purpose
test :
        @echo   "INCLUDE_DIR =    $(INCLUDE_DIR)"
        @echo   "OBJ_DIR      =   $(OBJ_DIR)"
        @echo   "EXE          =   $(EXE)"
        @echo   "SRC          =   $(SRC)"
        @echo   "SRC_H        =   $(SRC_H)"
        @echo   "ALL_SRC      =   $(ALL_SRC)"
        @echo   "OBJ          =   $(OBJ)"
        @echo   "BACKUP_FILE =    $(BACKUP_FILE)"
        @echo   "CXX          =   $(CXX)"
        @echo   "CXXFLAGS     =   $(CXXFLAGS)"
        @echo   "ADD_OPT      =   $(ADD_OPT)"
        @echo   "DOC_DIR      =   $(DOC_DIR)"
        @echo   "DOXYGEN      =   $(DOXYGEN)"
        @echo   "DOXYGEN_CONF =   $(DOXYGEN_CONF)"
        @echo   "YES_ATTRIBUTES   = $(YES_ATTRIBUTES)"
        @echo   "MAKEDEP_FILE =   $(MAKEDEP_FILE)"

# Documentation generation through doxygen
# First check if the $(DOXYGEN) and the $(DOC_DIR) directory exist
# Then Check $(DOXYGEN_CONF) availability;otherwise,generate one with ’doxygen -s -g’
#    The following attributes should be modified in the generated file:
#    - OUTPUT_DIRECTORY should be set to ’$(DOC_DIR)’, INPUT to ’. $(INCLUDE_DIR)’
#    - $(YES_ATTRIBUTES) attributes should be set to YES
#    - OPTIMIZE_OUTPUT_FOR_C should be set to YES if the project in in C
# Finally, launch documentation generation
doc :
ifeq ($(DOXYGEN),)
        @echo "Please install Doxygen to use this option!"
        @echo "(’apt-get install doxygen’ under Debian)"
else
        @if [ ! -d ./$(DOC_DIR) ]; then \
                echo "$(DOC_DIR)/ does not exist => creating $(DOC_DIR)/"; \
                mkdir -p ./$(DOC_DIR)/; \
        fi
        @if [ ! -f $(DOXYGEN_CONF) ]; then                                                   \
                echo "I don’t found the configuration file for Doxygen ($(DOXYGEN_CONF))";   \
                echo "Now generating one using ’$(DOXYGEN) -s -g $(DOXYGEN_CONF)’";          \
                $(DOXYGEN) -s -g $(DOXYGEN_CONF);                                            \
                echo "Now updating OUTPUT_DIRECTORY attribute to ’$(DOC_DIR)’";              \
                cat $(DOXYGEN_CONF) | sed -e "s/^\(OUTPUT_DIRECTORY \+= \+\).*/\1$(DOC_DIR)/"\
                   > $(DOXYGEN_CONF);                                                        \
                echo "Now updating INPUT attribute to ’. $(INCLUDE_DIR)’";                   \
                cat $(DOXYGEN_CONF) | sed -e "s/^\(INPUT \+= \+\).*/\1. $(INCLUDE_DIR)/"     \
                    > $(DOXYGEN_CONF);                                                       \



                                              114
                for attr in $(YES_ATTRIBUTES); do                                      \
                        echo "now updating $$attr to YES";                             \
                        cat $(DOXYGEN_CONF) | sed -e "s/^\($$attr \+= \+\).*/\1YES/"   \
                           > $(DOXYGEN_CONF);                                          \
                done; \
        fi
        $(DOXYGEN) $(DOXYGEN_CONF)
        @echo
        @echo Documentation generated in $(DOC_DIR)/
        @echo May be you can try to execute ’mozilla ./$(DOC_DIR)/html/index.html’
endif
endif




                                          115
Annexe C

    e
Le bˆtisier

                                        e
Cette annexe est une collection des bˆtises qu’il faut faire au moins une fois
                  e           e     e
dans sa vie pour ˆtre vaccin´. L’id´e est reprise de [Cas98]
          e
La caract´ristique de beaucoup de ces erreurs est de ne pas provoquer de mes-
                                                   e                      e
sage d’erreur du compilateur, rendant ainsi leur d´tection difficile. La diff´rence
                                        e
entre le texte correct et le texte erron´ est souvent seulement d’un seul carac-
 e         e
t`re. La d´couverte de telles erreurs ne peut donc se faire que par un examen
  e
tr`s attentif du source du programe.


C.1                       e
        Erreur avec les op´rateurs
C.1.1    Erreur sur une comparaison
  Ce que voulait le programmeur      :   Comparer a et b
                          ue
        Ce qu’il aurait dˆ ´crire    :   if (a == b)
                             e
                  Ce qu’il a ´crit   :   if (a = b)
               Ce qu’il a obtenu     :                       a        e       e
                                         une affectation de b ` a, le r´sultat ´tant la
                                                     e
                                         valeur affect´e.

                   ıne                       e
Pourquoi tant de haˆ ? L’affectation est un op´rateur et non pas une
instruction.

C.1.2    Erreur sur l’affectation
                              e e                                       e
C’est l’inverse de l’erreur pr´c´dente, mais qui reste beaucoup moins fr´quente.
  Ce que voulait le programmeur      :             a
                                         Affecter b ` a
                          ue
        Ce qu’il aurait dˆ ´crire    :   a = b
                             e
                  Ce qu’il a ´crit   :   a == b
               Ce qu’il a obtenu     :                       a
                                         La comparaison de a ` b.


C.2     Erreurs avec les macros
     e                         e e             e              e
Le m´canisme des macros est r´alis´ par le pr´processeur qui r´alise un traite-
                                                                    e     e
ment en amont du compilateur proprement dit. Ce concept est abord´ en d´tail
dans le chapitre 7. Comme le traitement des macros est un pur traitement
                                           a
textuel, sans aucun contexte, c’est un nid ` erreurs.

                                            116
C.2.1                              e
         Un #define n’est pas une d´claration
 Ce que voulait le programmeur :          e                                     e
                                      la d´finition de la constante MAX initialis´e a 10
                         ue
       Ce qu’il aurait dˆ ´crire :    #define MAX 10
                            e
                 Ce qu’il a ´crit :   #define MAX 10 ;

                                                                a
Cette erreur peut provoquer ou non une erreur de compilation ` l’utilisation de
la macro :
– L’utilisation x = MAX ; aura pour traduction x = 10 ; ;, ce qui est possible :
                                     e
   il y a une instruction nulle derri`re l’instruction x = 10 ;
                                                                        e e
– L’utilisation int t[MAX] ; aura pour expansion int t[10 ;] ; ce qui g´n`rera
   un message d’erreur.

C.2.2    Un #define n’est pas une initialisation
 Ce que voulait le programmeur :          e                                     e
                                      la d´finition de la constante MAX initialis´e a 10
                         ue
       Ce qu’il aurait dˆ ´crire :    #define MAX 10
                            e
                 Ce qu’il a ´crit :   #define MAX = 10 ;

                     e e            e   e a
Cette erreur sera g´n´ralement d´tect´e ` la compilation, malheureusement le
                         e                                            a u e
message d’erreur sera ´mis sur l’utilisation de la macro, et non pas l` o` r´side
          a            e
l’erreur, ` savoir la d´finition de la macro.

C.2.3                               e
         Erreur sur macro avec param`tres
                                      e                          e
La distinction entre macro avec param`tres et macro sans param`tre se fait
         e                    e                    e
sur la pr´sence d’une parenth`se ouvrante juste apr`s le nom de la macro,
                                                       e
sans aucun blanc entre les deux. Ceci peut amener des r´sultats surprenant ;
comparer les deux exemples suivants :

             e
            D´finition de la macro                  e
                                            param`tres        traduction
          #define add(a,b) (a + b)             a et b           (a + b)
          #define add (a,b) (a + b)            aucun         (a,b) (a + b)


C.2.4    Erreur avec les effets de bord
                                                                        e
Le corps d’une macro peut comporter plusieurs occurrences d’un param`tre. Si
a                               e                                  e
` l’utilisation de la macro on r´alise un effet de bord sur le param`tre effectif,
                        e e
cet effet de bord sera r´alis´ plusieurs fois. Exemple :
#define CARRE(a) ((a) * (a))
l’utilisation de CARRE(x++) aura comme traduction ((x++) * (x++)) et l’op´-  e
                       e
rateur ++ sera appliqu´ deux fois.


C.3     Erreurs avec l’instruction if
                                       e
L’instruction if ne comporte ni mot-cl´ introducteur de la partie then, ni ter-
minateur (pas de fi dans le style des if then else fi).
Ceci peut provoquer les erreurs suivantes :

                                          117
                 Ce que voulait le programmeur :      tester si a > b
                                         ue
                       Ce qu’il aurait dˆ ´crire :    if ( a > b)
                                                           a = b;
                                            e
                                 Ce qu’il a ´crit :   if ( a > b);
                                                           a = b;

        e
Le probl`me vient aussi du fait de l’existence de l’instruction nulle :

        Ce que voulait le programmeur :      tester si a > b
                                ue
              Ce qu’il aurait dˆ ´crire :    if ( a > b)
                                                  { if ( x > y) x = y; }
                                             else
                                                  ...
                                   e
                        Ce qu’il a ´crit :   if ( a > b)
                                                   if ( x > y) x = y;
                                             else
                                                  ...

                                   e
On rappelle qu’un else est raccroch´ au premier if.


C.4     Erreurs avec les commentaires
Il y a deux erreurs classiques avec les commentaires :
                                 e
  1. le programmeur oublie la s´quence fermante /*. Le compilateur ”mange”
                              a     e
     donc tout le texte jusqu’` la s´quence fermante du prochain commentaire.
  2. On veut enlever (en le mettant en commentaire) un gros bloc d’instruc-
     tions sans prendre garde au fait qu’il comporte des commentaires. Les
                                e            e ¸
     commentaires ne pouvant ˆtre imbriqu´s, ca n’aura pas l’effet escompt´     e
                              e
     par le programmeur. La m´thode classique pour enlever (tout en le laissant
                                                                    e
     dans le source) un ensemble d’instructions est d’utiliser le pr´processeur :
      #ifdef NOTDEFINED
      ...
      #endif


C.5                             e        e
        Erreurs avec les priorit´s des op´rateurs
           e        e                                                     e
Les priorit´s des op´rateurs sont parfois surprenantes. Les cas les plus gˆnants
sont les suivants :
             e        e           a            e      a              e
– La priorit´ des op´rateurs bit ` bit est inf´rieure ` celle des op´rateurs de
  comparaison.
                        e
      Le programmeur a ´crit                e
                                        il d´sirait              il a obtenu
         x & 0xff == 0xac           (x & 0xff) == 0xac       x & (0xff == 0xac)

            e       e            e              e       a             e
– La priorit´ des op´rateurs de d´calage est inf´rieure ` celle des op´rateurs
        e
  arithm´tiques.
                            e
          Le programmeur a ´crit               e
                                           il d´sirait         il a obtenu
               x << 4 + 0xf              (x << 4) + 0xf      x << (4 + 0xf)


                                         118
            e        e                           e       a             e
– La priorit´ de l’op´rateur d’affectation est inf´rieure ` celle des op´rateurs
                            e                     e                e
  de comparaison. Dans la s´quence ci-dessous, tr`s souvent utilis´e, toutes les
          e          e
  parenth`ses sont n´cessaire :
  while ((c = getchar()) != EOF) {
      ...
      }


C.6     Erreur avec l’instruction switch
C.6.1    Oubli du break
                                                  a
Sans l’instruction break, le programme continuera ` l’alternative suivante ce
                     e
qui peut poser probl`me. (voir §2.2.5).

C.6.2    Erreur sur le default
              a e               e
L’alternative ` ex´cuter par d´faut est introduite par l’etiquette default. Si
                                            e                           e
une faute de frappe est commise sur cette ´tiquette, l’alternative par d´faut ne
                        e                            e
sera plus reconnue : l’´tiquette sera prise pour une ´tiquette d’instruction sur
laquelle ne sera fait aucun goto (voir §2.2.6).

switch(a) {
     case 1 : a = b;
     defult : return(1);              /*                 e    e
                                             erreur non d´tect´e         */
}

                                                                          e
Version diabolique de cette erreur : si la lettre l de default est remplac´e par
                                   e
le chiffre 1, avec les fontes utilis´es pour imprimer les sources, qui verra la
    e
diff´rence entre l et 1 ?


C.7     Erreur sur les tableaux multidimensionnels
    ee        a               a                    e
La r´f´rence ` un tableau t ` deux dimensions s’´crit t[i][j] et non pas t[i,j]
comme dans d’autres langages de programmation.
Malheureusement, si on utilise par erreur la notation t[i,j] et selon le contexte
                            e          e
d’utilisation, elle pourra ˆtre accept´e par le compilateur. En effet, dans cette
                               e           e             e            e
expression la virgule est l’op´rateur qui d´livre comme r´sultat l’op´rande droite
   e         e     e    e                          e                e
apr`s avoir ´valu´ l’op´rande gauche. Comme l’´valuation de l’op´rande gauche
    e                                     e
ne r´alise ici aucun effet de bord, cette ´valuation est inutile , donc t[i,j] est
e           a                                                          a
´quivalent ` t[j] qui est l’adresse du sous-tableau correspondant ` l’index j.


C.8                                 e   e
        Erreur avec la compilation s´par´e
                                             e                 e
Une erreur classique est d’avoir un tableau d´fini dans une unit´ de compilation :
int tab[10] ;
                      e               ee                           e
et d’utiliser comme d´claration de r´f´rence dans une autre unit´ de compila-
tion :

                                       119
extern int * tab ;
                                               e
Rappelons que int tab[] et int *t ne sont ´quivalents que dans le seul cas
         e
de param`tre formel de fonction.
                                     e              ee
Dans le cas qui nous occupe ici, la d´claration de r´f´rence correcte est :
extern int tab[] ;




                                   120
C.9    Liens utiles
                          e
En plus des ouvrages cit´s dans la bibliographie, voici quelques sites web qui
                   e                  e                          e
m’ont servis pour r´aliser ce polycopi´ et/ou qui m’ont parus int´ressants :
– [Cas98] : http://www-clips.imag.fr/commun/bernard.cassagne/Introduction_
  ANSI_C.html
– http://fr.wikibooks.org/wiki/Programmation_C
– [Can03] : http://www-rocq.inria.fr/codes/Anne.Canteaut/COURS_C/
                                                            e
– [Fab02] : http://www.ltam.lu/Tutoriel_Ansi_C/ (un tr`s bon tutorial en
  ligne).




                                  121
Bibliographie

[Can03] Anne Canteaut.   ”Programmation en Langage C”.  INRIA -
        projet CODES, 2003. http://www-rocq.inria.fr/codes/Anne.
        Canteaut/COURS_C/.
[Cas98]   Bernard Cassagne. ”Introduction au Langage C”. Laboratoire CLIPS
          UJF/CNRS, 1997-1998.      http://www-clips.imag.fr/commun/\\
          bernard.cassagne/Introduction\_ANSI\_C.html.
[Fab02]                            a
          F. Faber. Introduction ` la programmation en ANSI-C. Technical
                              u
          report, Tutorial, Aoˆ t 2002. http://www.ltam.lu/Tutoriel_Ansi_
          C/.
[Her01]   Matthieu Herrb. Guide superflu de programmation en langage C.
          Technical report, CNRS/LAAS, Dec. 2001. http://www.laas.fr/
          ~matthieu/cours/c-superflu/index.
[KR88]    B.W. Kernighan and D.M. Ritchie. The C Programming Language.
          Prentice-Hall, Englewood Cliffs, New Jersey, USA, 1988. 2nd edition.
[Pil04]             c
          Jean-Fran¸ois Pillou. Introduction au Langage C. Technical re-
                                  e
          port, Tutorial, Encyclop´die Informatique Libre, 2004. http://www.
          commentcamarche.net/c/cintro.php3.
[PKP03] Peter Prinz and Ulla Kirch-Prinz. C Pocket Reference. O’Reilly &
        Associates, 2003.
[Ste90]   David Stevenson. ”IEEE Std 754-1985 IEEE Standard for Binary
          Floating-Point Arithmetic”. Technical report, IEEE Standards Asso-
          ciation, 1990.




                                     122

								
To top