le langage c - elektroniqueet du fichier inclus. 2n´eanmoins, les directives pour le...

115
Universit´ e de la M´ editerran´ ee Facult´ e des Sciences de Luminy Le langage C Henri Garreta Table des mati` eres 1 ´ El´ ements de base 4 1.1 Structure g´ en´ erale d’un programme ................................... 4 1.2 Consid´ erations lexicales ......................................... 4 1.2.1 Pr´ esentation du texte du programme .............................. 4 1.2.2 Mots-cl´ es ............................................. 5 1.2.3 Identificateurs .......................................... 5 1.2.4 Op´ erateurs ............................................ 5 1.3 Constantes litt´ erales ........................................... 6 1.3.1 Nombres entiers ......................................... 6 1.3.2 Nombres flottants ........................................ 6 1.3.3 Caract` eres et chaˆ ınes de caract` eres ............................... 6 1.3.4 Expressions constantes ...................................... 7 1.4 Types fondamentaux ........................................... 8 1.4.1 Nombres entiers et caract` eres .................................. 8 1.4.2 Types ´ enum´ er´ es ......................................... 10 1.4.3 Nombres flottants ........................................ 10 1.5 Variables ................................................. 10 1.5.1 Syntaxe des d´ eclarations ..................................... 10 1.5.2 Visibilit´ e des variables ...................................... 11 1.5.3 Allocation et dur´ ee de vie des variables ............................ 12 1.5.4 Initialisation des variables .................................... 12 1.5.5 Variables locales statiques .................................... 13 1.5.6 Variables critiques ........................................ 14 1.5.7 Variables constantes et volatiles ................................. 14 1.6 Variables, fonctions et compilation s´ epar´ ee ............................... 15 1.6.1 Identificateurs publics et priv´ es ................................. 15 1.6.2 eclaration d’objets externes .................................. 15 2 Op´ erateurs et expressions 17 2.1 en´ eralit´ es ................................................ 17 2.1.1 Lvalue et rvalue ......................................... 17 2.1.2 Priorit´ e des op´ erateurs ...................................... 18 2.2 Pr´ esentation d´ etaill´ ee des op´ erateurs .................................. 18 2.2.1 Appel de fonction () ....................................... 18 2.2.2 Indexation [] ........................................... 19 2.2.3 election . ............................................ 20 2.2.4 election dans un objet point´ e -> ................................ 20 2.2.5 egation ! ............................................. 20 2.2.6 Compl´ ement`a1 ~ ........................................ 21 2.2.7 Les c´ el` ebres ++ et -- ....................................... 21 2.2.8 Moins unaire - .......................................... 22 2.2.9 Indirection * ........................................... 22 2.2.10 Obtention de l’adresse & ..................................... 22 2.2.11 Op´ erateur sizeof ........................................ 23 2.2.12 Conversion de type (“cast” operator) .............................. 23 2.2.13 Op´ erateurs arithm´ etiques .................................... 25 2.2.14 D´ ecalages << >> ......................................... 25 2.2.15 Comparaisons ==!= < <= > >= ................................ 26 2.2.16 Op´ erateurs de bits & | ^ ..................................... 27

Upload: others

Post on 27-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

Universite de la Mediterranee Faculte des Sciences de Luminy

Le langage C

Henri Garreta

Table des matieres

1 Elements de base 41.1 Structure generale d’un programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2 Considerations lexicales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.2.1 Presentation du texte du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2.2 Mots-cles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2.3 Identificateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2.4 Operateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.3 Constantes litterales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3.1 Nombres entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3.2 Nombres flottants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3.3 Caracteres et chaınes de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3.4 Expressions constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.4 Types fondamentaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.4.1 Nombres entiers et caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.4.2 Types enumeres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.4.3 Nombres flottants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.5 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.5.1 Syntaxe des declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.5.2 Visibilite des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.5.3 Allocation et duree de vie des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.5.4 Initialisation des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.5.5 Variables locales statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.5.6 Variables critiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.5.7 Variables constantes et volatiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

1.6 Variables, fonctions et compilation separee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.6.1 Identificateurs publics et prives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.6.2 Declaration d’objets externes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2 Operateurs et expressions 172.1 Generalites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.1.1 Lvalue et rvalue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172.1.2 Priorite des operateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.2 Presentation detaillee des operateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.2.1 Appel de fonction () . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.2.2 Indexation [] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.2.3 Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.2.4 Selection dans un objet pointe -> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.2.5 Negation ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.2.6 Complement a 1 ~ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.2.7 Les celebres ++ et -- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.2.8 Moins unaire - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.2.9 Indirection * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.2.10 Obtention de l’adresse & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.2.11 Operateur sizeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.2.12 Conversion de type (“cast” operator) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.2.13 Operateurs arithmetiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252.2.14 Decalages << >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252.2.15 Comparaisons == != < <= > >= . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262.2.16 Operateurs de bits & | ^ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

Page 2: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

TABLE DES MATIERES TABLE DES MATIERES

2.2.17 Connecteurs logiques && et || . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.2.18 Expression conditionnelle ? : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.2.19 Affectation = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292.2.20 Autres operateurs d’affectation += *= etc. . . . . . . . . . . . . . . . . . . . . . . . . . . . 302.2.21 L’operateur virgule , . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

2.3 Autres remarques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.3.1 Les conversions usuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.3.2 L’ordre d’evaluation des expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322.3.3 Les operations non abstraites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3 Instructions 333.1 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333.2 Presentation detaillee des instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

3.2.1 Blocs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343.2.2 Instruction-expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343.2.3 Etiquettes et instruction goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353.2.4 Instruction if...else... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353.2.5 Instructions while et do...while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363.2.6 Instruction for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373.2.7 Instruction switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383.2.8 Instructions break et continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393.2.9 Instruction return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

4 Fonctions 414.1 Syntaxe ANSI ou “avec prototype” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

4.1.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.1.2 Type de la fonction et des arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.1.3 Appel des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.1.4 Declaration “externe” d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

4.2 Syntaxe originale ou “sans prototype” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434.2.1 Declaration et definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434.2.2 Appel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.2.3 Coexistence des deux syntaxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

4.3 Arguments des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.3.1 Passage des arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.3.2 Arguments de type tableau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.3.3 Arguments par adresse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464.3.4 Arguments en nombre variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5 Objets structures 495.1 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

5.1.1 Cas general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495.1.2 Initialisation des tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495.1.3 Chaınes de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

5.2 Structures et unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.2.1 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.2.2 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535.2.3 Champs de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

5.3 Enumerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545.4 Declarateurs complexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.4.1 Cas des declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.4.2 Pointeurs et tableaux constants et volatils . . . . . . . . . . . . . . . . . . . . . . . . . . . 575.4.3 La declaration typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585.4.4 Cas des types desincarnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

6 Pointeurs 606.1 Generalites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

6.1.1 Declaration et initialisation des pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 606.1.2 Les pointeurs generiques et le pointeur NULL . . . . . . . . . . . . . . . . . . . . . . . . . . 61

6.2 Les pointeurs et les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626.2.1 Arithmetique des adresses, indirection et indexation . . . . . . . . . . . . . . . . . . . . . 62

2 c© H. Garreta, 2003

Page 3: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

TABLE DES MATIERES TABLE DES MATIERES

6.2.2 Tableaux dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646.2.3 Tableaux multidimensionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656.2.4 Tableaux multidimensionnels dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . 666.2.5 Tableaux de chaınes de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686.2.6 Tableaux multidimensionnels formels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696.2.7 Tableaux non necessairement indexes a partir de zero . . . . . . . . . . . . . . . . . . . . 70

6.3 Les adresses des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726.3.1 Les fonctions et leurs adresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726.3.2 Fonctions formelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726.3.3 Tableaux de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746.3.4 Flou artistique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

6.4 Structures recursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756.4.1 Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756.4.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766.4.3 Structures mutuellement recursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

7 Entrees-sorties 797.1 Flots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

7.1.1 Fonctions generales sur les flots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807.1.2 Les unites standard d’entree-sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

7.2 Lecture et ecriture textuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827.2.1 Lecture et ecriture de caracteres et de chaınes . . . . . . . . . . . . . . . . . . . . . . . . . 827.2.2 Ecriture avec format printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847.2.3 Lecture avec format scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 867.2.4 A propos de la fonction scanf et des lectures interactives . . . . . . . . . . . . . . . . . . 887.2.5 Les variantes de printf et scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

7.3 Operations en mode binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 907.3.1 Lecture-ecriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 907.3.2 Positionnement dans les fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

7.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 927.4.1 Fichiers “en vrac” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 927.4.2 Fichiers binaires et fichiers de texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 927.4.3 Fichiers en acces relatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

7.5 Les fichiers de bas niveau d’UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

8 “Genie logiciel” 968.1 Le preprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

8.1.1 Inclusion de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 968.1.2 Definition et appel des “macros” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 978.1.3 Compilation conditionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

8.2 La modularite de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008.2.1 Fichiers en-tete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1018.2.2 Exemple : stdio.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

8.3 Deux ou trois choses bien pratiques... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1048.3.1 Les arguments du programme principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1048.3.2 Branchements hors fonction : setjmp.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1068.3.3 Interruptions : signal.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

8.4 La bibliotheque standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1098.4.1 Aide a la mise au point : assert.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1098.4.2 Fonctions utilitaires : stdlib.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1098.4.3 Traitement de chaınes : string.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1118.4.4 Classification des caracteres : ctype.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1118.4.5 Fonctions mathematiques : math.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1128.4.6 Limites propres a l’implantation : limits.h, float.h . . . . . . . . . . . . . . . . . . . . 112

28 fevrier [email protected]

c© H. Garreta, 2003 3

Page 4: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE

1 Elements de base

1.1 Structure generale d’un programme

La transformation d’un texte ecrit en langage C en un programme executable par l’ordinateur se fait endeux etapes : la compilation et l’edition de liens. La compilation est la traduction des fonctions ecrites en C endes procedures equivalentes ecrites en langage machine. Le compilateur lit toujours un fichier source1 et produitun fichier objet.

Chaque fichier objet est incomplet, insuffisant pour etre execute, car il contient des appels de fonctionsou des references a des variables qui ne sont pas definies dans le meme fichier (pensez a la fonction printf).L’edition de liens est l’operation par laquelle plusieurs fichiers objets sont mis ensemble pour se completermutuellement : un fichier apporte des definitions de fonctions et de variables auxquelles un autre fichier faitreference, et reciproquement. L’editeur de liens (ou linker) prend en entree plusieurs fichiers objets et produitun unique fichier executable. L’editeur de liens est independant du langage de programmation utilise pour ecrireles fichiers sources, qui peuvent meme avoir ete ecrits dans des langages differents.

Chaque fichier source entrant dans la composition d’un programme executable est fait d’une succession d’unnombre quelconque d’elements independants, qui sont :

– des directives pour le preprocesseur (lignes commencant par #),– des constructions de types (struct, union, enum, typedef),– des declarations de variables et de fonctions externes,– des definitions de variables,– des definitions de fonctions.

Seules les expressions des deux dernieres categories font grossir le fichier objet : les definitions de fonc-tions laissent leur traduction en langage machine, tandis que les definitions de variables se traduisent par desreservations d’espace, eventuellement garni de valeurs initiales. Les autres directives et declarations s’adressentau compilateur et il n’en reste pas de trace lorsque la compilation est finie.

En C on n’a donc pas une structure syntaxique englobant tout, comme la construction � Program ... end. � dulangage Pascal ; un programme n’est qu’une collection de fonctions assortie d’un ensemble de variables globales.D’ou la question : par ou l’execution doit-elle commencer ? La convention est la suivante : parmi les fonctionsdonnees il doit en exister une dont le nom est main. C’est par elle que l’execution commencera ; le lancement duprogramme equivaut a l’appel de cette fonction par le systeme d’exploitation. Notez bien que, a part cela, mainest une fonction comme les autres, sans aucune autre propriete specifique. En particulier, les variables internesa main sont locales, tout comme celles des autres fonctions.

Pour finir cette entree en matiere, voici la version C du celebre programme-qui-dit-bonjour, sans lequel onne saurait commencer un cours de programmation :

#include <stdio.h>

int main() {printf("Bonjour\n");return 0;

}

1.2 Considerations lexicales

1.2.1 Presentation du texte du programme

Le programmeur est maıtre de la disposition du texte du programme. Des blancs, des tabulations et dessauts a la ligne peuvent etre places a tout endroit ou cela ne coupe pas un identificateur, un nombre ou unsymbole compose2.

Les commentaires commencent par /* et se terminent par */ :

1Comme nous le verrons, si la directive #include est utilisee, alors le fichier lu par le compilateur est la reunion du fichier initialet du fichier inclus.

2Neanmoins, les directives pour le preprocesseur (cf. section 8.1) doivent comporter un # dans la premiere position de la ligne.Cela ne constitue pas une exception a la regle donnee ici, car le preprocesseur n’est pas le compilateur C et ne travaille pas sur lasyntaxe du langage.

4 c© H. Garreta, 2003

Page 5: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.2 Considerations lexicales

/* Ce texte est un commentaire et sera donc ignore par lecompilateur */

Les commentaires ne peuvent pas etre imbriques : ecrit dans un programme, le texte � /* voici un grand/* et un petit */ commentaire */ � est errone, car seul � /* voici un grand /* et un petit */ � seravu comme un commentaire par le compilateur.

Les langages C et C++ cohabitant dans la plupart des compilateurs actuels, le compilateur accepte egalementcomme un commentaire tout texte compris entre le signe // et la fin de la ligne ou ce signe apparaıt :

// Ceci est un commentaire a la mode C++.

Le caractere anti-slash \ precedant immediatement un saut a la ligne masque ce dernier : la ligne suivanteest consideree comme devant etre concatenee a la ligne courante. Cela est vrai en toute circonstance, y comprisa l’interieur d’une chaıne de caracteres. Par exemple, le texte

message = "anti\constitutionnellement";

est compris comme ceci : � message = "anti constitutionnellement" ; �

1.2.2 Mots-cles

Les mots suivants sont reserves. Leur fonction est prevue par la syntaxe de C et ils ne peuvent pas etreutilises dans un autre but :

auto break case char const continue default dodouble else enum extern float for goto ifint long register return short signed sizeof staticstruct switch typedef union unsigned void volatile while

1.2.3 Identificateurs

Un identificateur est une suite de lettres et chiffres contigus, dont le premier est une lettre. Lorsque seulle compilateur est concerne, c’est-a-dire lorsqu’il s’agit d’identificateurs dont la portee est incluse dans un seulfichier (nous dirons de tels identificateurs qu’ils sont prives) :

– en toute circonstance une lettre majuscule est tenue pour differente de la lettre minuscule correspondante ;– dans les identificateurs, le nombre de caracteres discriminants est au moins de 31.

Attention, lorsqu’il s’agit d’identificateurs externes, c’est-a-dire partages par plusieurs fichiers sources, il estpossible que sur un systeme particulier l’editeur de liens sous-jacent soit trop rustique pour permettre le respectde ces deux prescriptions.

Le caractere (appele � blanc souligne �) est considere comme une lettre ; il peut donc figurer a n’importequelle place dans un identificateur. Cependant, par convention un programmeur ne doit pas utiliser des identi-ficateurs qui commencent par ce caractere. Cela assure qu’il n’y aura jamais de conflit avec les noms introduits(a travers les fichiers � .h �) pour les besoins des bibliotheques, car ces noms commencent par un tel blancsouligne.

1.2.4 Operateurs

Symboles simples :

( ) [ ] . ! ~ < > ? := , + - * / % | & ^

Symboles composes :

-> ++ -- <= >= == != && || << >>+= -= *= /= %= <<= >>= |= &= ^=

Tous ces symboles sont reconnus par le compilateur comme des operateurs. Il est interdit d’inserer descaracteres blancs a l’interieur d’un symbole compose. En outre, il est conseille d’encadrer par des blancs touteutilisation d’un operateur. Dans certaines circonstances cette regle est plus qu’un conseil, car sa non-observancecree une expression ambigue.

c© H. Garreta, 2003 5

Page 6: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.3 Constantes litterales 1 ELEMENTS DE BASE

1.3 Constantes litterales

1.3.1 Nombres entiers

Les constantes litterales numeriques entieres ou reelles suivent les conventions habituelles, avec quelquesparticularites.

Les constantes litterales sont sans signe : l’expression −123 est comprise comme l’application de l’operateurunaire − a la constante 123 ; mais puisque le calcul est fait pendant la compilation, cette subtilite n’a aucuneconsequence pour le programmeur. Notez aussi qu’en C original, comme il n’existe pas d’operateur + unaire, lanotation +123 est interdite.

Les constantes litterales entieres peuvent aussi s’ecrire en octal et en hexadecimal :– une constante ecrite en octal (base 8) commence par 0 (zero) ;– une constante ecrite en hexadecimal (base 16) commence par 0x ou 0X.

Voici par exemple trois manieres d’ecrire le meme nombre :

27 033 0x1B

Detail a retenir : on ne doit pas ecrire de zero non significatif a gauche d’un nombre : 0123 ne representepas la meme valeur que 123.

Le type d’une constante entiere est le plus petit type dans lequel sa valeur peut etre representee. Ou, plusexactement :

– si elle est decimale : si possible int, sinon long, sinon unsigned long ;– si elle est octale ou hexadecimale : si possible int, sinon unsigned int, sinon unsigned long.

Certains suffixes permettent de changer cette classification :– U, u : indique que la constante est d’un type unsigned ;– L, l : indique que la constante est d’un type long.

Exemples : 1L, 0x7FFFU. On peut combiner ces deux suffixes : 16UL.

1.3.2 Nombres flottants

Une constante litterale est l’expression d’un nombre flottant si elle presente, dans l’ordre :– une suite de chiffres decimaux (la partie entiere),– un point, qui joue le role de virgule decimale,– une suite de chiffres decimaux (la partie fractionnaire),– une des deux lettres E ou e,– eventuellement un signe + ou -,– une suite de chiffres decimaux.

Les trois derniers elements forment l’exposant. Exemple : 123.456E-78.

On peut omettre :– la partie entiere ou la partie fractionnaire, mais pas les deux,– le point ou l’exposant, mais pas les deux.

Exemples : .5e7, 5.e6, 5000000., 5e6

Une constante flottante est supposee de type double, a moins de comporter un suffixe explicite :– les suffixes F ou f indiquent qu’elle est du type float ;– les suffixes L ou l indiquent qu’elle est du type long double.

Exemples : 1.0L, 5.0e4f

1.3.3 Caracteres et chaınes de caracteres

Une constante de type caractere se note en ecrivant le caractere entre apostrophes. Une constante de typechaıne de caracteres se note en ecrivant ses caracteres entre guillemets. Exemples, trois caracteres :

’A’ ’2’ ’"’

Quatre chaınes de caracteres :

"A" "Bonjour a tous !" "" "’"

6 c© H. Garreta, 2003

Page 7: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.3 Constantes litterales

On peut faire figurer n’importe quel caractere, meme non imprimable, dans une constante caractere ouchaıne de caracteres en utilisant les combinaisons suivantes, appelees sequences d’echappement :

\n nouvelle ligne (LF)\t tabulation (HT)\b espace-arriere (BS)\r retour-chariot (CR)\f saut de page (FF)\a signal sonore (BELL)\\ \’ ’" "\d3d2d1 le caractere qui a pour code le nombre octal d3d2d1. S’il commence par un ou deux zeros

et si cela ne cree pas une ambiguıte, on peut aussi le noter \d2d1 ou \d1

Par exemple, la chaıne suivante definit la suite des 9 caracteres3 A, escape (de code ASCII 27), B, ”, C, sautde page, D, \ et E :

"A\033B\"C\fD\\E"

Une constante de type caractere appartient au type char, c’est-a-dire entier represente sur un octet. Lavaleur d’une constante caractere est le nombre qui represente le caractere de maniere interne ; de nos jours ils’agit presque toujours du code ASCII4.

Une constante de type chaıne de caracteres represente une suite finie de caracteres, de longueur quelconque.Le codage interne d’une chaıne de caracteres est le suivant (voyez la figure 1) :

– les caracteres constituant la chaıne sont ranges en memoire, de maniere contigue, dans l’ordre ou ilsfigurent dans la chaıne ;

– un caractere nul est ajoute immediatement apres le dernier caractere de la chaıne, pour en indiquer la fin ;– la constante chaıne represente alors, a l’endroit ou elle est ecrite, l’adresse de la cellule ou a ete range le

premier caractere de la chaıne.

B n j u ro 0\o

"Bonjour"

Fig. 1 – Representation de la chaıne "Bonjour"

Par consequent, une constante chaıne de caracteres a pour type celui d’un tableau de caracteres (c’est-a-dire� char[] �) et pour valeur l’adresse d’une cellule de la memoire.

Par caractere nul on entend le caractere dont le code interne est 0 ; on peut le noter indifferemment 0, ’\000’ou ’\0’ (mais certainement pas ’0’) ; il est utilise tres frequemment en C. Notez que, dans une expression,’\0’ est toujours interchangeable avec 0.

1.3.4 Expressions constantes

Une expression constante est une expression de l’un des types suivants :– toute constante litterale ; exemples : 1, ’A’, "HELLO", 1.5e-2 ;– une expression correcte formee par l’application d’un operateur courant (arithmetique, logique, etc.) a une

ou deux expressions constantes ; exemples : -1, ’A’ - ’a’, 2 * 3.14159265, "HELLO" + 6 ;– l’expression constituee par l’application de l’operateur & (operateur de calcul de l’adresse, voyez la sec-

tion 2.2.10) a une variable statique, a un champ d’une variable statique de type structure ou a un elementd’un tableau statique dont le rang est donne par une expression constante ; exemples : &x, &fiche.nom,&table[50] ;

– l’expression constituee par l’application de l’operateur sizeof a un descripteur de type. Exemples :sizeof(int), sizeof(char *) ;

– l’expression constituee par l’application de l’operateur sizeof a une expression quelconque, qui ne serapas evaluee ; exemples : sizeof x, sizeof(2 * x + 3).

3Nous verrons qu’en fait cette chaıne comporte un caractere de plus qui en marque la fin.4En standard le langage C ne prevoit pas le codage Unicode des caracteres.

c© H. Garreta, 2003 7

Page 8: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.4 Types fondamentaux 1 ELEMENTS DE BASE

Les expressions constantes peuvent etre evaluees pendant la compilation. Cela est fait a titre facultatif parles compilateurs de certains langages. En C ce n’est pas facultatif : il est garanti que toute expression constante(et donc toute sous-expression constante d’une expression quelconque) sera effectivement evaluee avant quel’execution ne commence. En termes de temps d’execution, l’evaluation des expressions constantes est doncentierement � gratuite �.

1.4 Types fondamentaux

Types de baseNombres entiers

AnonymesPetite taille

– signes char– non signes unsigned char

Taille moyenne– signes short– non signes unsigned short

Grande taille– signes long– non signes unsigned long

Nommesenum

Nombres flottantsSimples floatGrande precision doublePrecision encore plus grande long double

Types derivesTableaux [ ]Fonctions ( )Pointeurs *Structures structUnions union

Tab. 1 – Les types du langage C

Le tableau 1 presente l’ensemble des types connus du compilateur C. L’organisation generale de cet ensembleest evidente : on dispose de deux sortes de types de base, les nombres entiers et les nombres flottants, et d’unefamille infinie de types derives obtenus en appliquant quelques procedes recursifs de construction soit a destypes fondamentaux soit a des types derives definis de la meme maniere.

Cette organisation revele un trait de l’esprit de C : le pragmatisme l’emporte sur l’esthetisme, parfois memesur la rigueur. Dans d’autres langages, les caracteres, les booleens, les constantes symboliques, etc., sont codesde maniere interne par des nombres, mais ce fait est officiellement ignore par le programmeur, qui reste obligede considerer ces donnees comme appartenant a des ensembles disjoints. En C on a fait le choix oppose, laissantau programmeur le soin de realiser lui-meme, a l’aide des seuls types numeriques, l’implantation des types deniveau superieur.

1.4.1 Nombres entiers et caracteres

La classification des types numeriques obeit a deux criteres :

– Si on cherche a representer un ensemble de nombres tous positifs on pourra adopter un type non signe ;au contraire si on doit representer un ensemble contenant des nombres positifs et des nombres negatifs ondevra utiliser un type signe5.

5On dit parfois qu’une donnee � est un entier signe � ou � est un entier non signe �. C’est un abus de langage : le caractere signeou non signe n’est pas un attribut d’un nombre (un nombre donne est positif ou negatif, c’est tout) mais de l’ensemble de nombresqu’on a choisi de considerer et, par extension, de toute variable censee pouvoir representer n’importe quelle valeur de cet ensemble.

8 c© H. Garreta, 2003

Page 9: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.4 Types fondamentaux

– Le deuxieme critere de classification des donnees numeriques est la taille requise par leur representation.Comme precedemment, c’est un attribut d’un ensemble, et donc d’une variable devant representer toutelement de l’ensemble, non d’une valeur particuliere. Par exemple, le nombre 123 considere comme unelement de l’ensemble {0 ... 65535} est plus encombrant que le meme nombre 123 quand il est considerecomme un element de l’ensemble {0 ... 255}.

Avec N chiffres binaires (ou bits) on peut representer :– soit les 2N nombres positifs 0, 1, ... 2N − 1 (cas non signe) ;– soit les 2N nombres positifs et negatifs −2N−1, ... 2N−1 − 1 (cas signe).

De plus, la representation signee et la representation non signee des elements communs aux deux domaines(les nombres 0, 1, ... 2N−1 − 1) coıncident.

Le type caractere. Un objet de type char peut etre defini, au choix, comme– un nombre entier pouvant representer n’importe quel caractere du jeu de caracteres de la machine utilisee ;– un nombre entier occupant la plus petite cellule de memoire adressable separement6. Sur les machines

actuelles les plus repandues cela signifie generalement un octet (8 bits).

Le plus souvent, un char est un entier signe ; un unsigned char est alors un entier non signe. Lorsque leschar sont par defaut non signes, la norme ANSI prevoit la possibilite de declarer des signed char.

On notera que la signification d’un char en C, un entier petit, est tres differente de celle d’un char en Pascal(dans ce langage, l’ensemble des caracteres et celui des nombres sont disjoints). En C, ch etant une variable detype char, rien ne s’oppose a l’ecriture de l’expression ch - ’A’ + 32 qui est tout a fait homogene, puisqueentierement faite de nombres.

Le caractere � impossible �. Toutes les valeurs qu’il est possible de ranger dans une variable de typechar sont en principe des caracteres legaux. Or la plupart des programmes qui lisent des caracteres doiventetre capables de manipuler une valeur supplementaire, distincte de tous les � vrais � caracteres, signifiant � lafin des donnees �. Pour cette raison, les variables et fonctions qui representent ou renvoient des caracteres sontsouvent declarees int, non char : n’importe quelle valeur appartenant au type int mais n’appartenant pas autype char peut alors servir d’indicateur de fin de donnees. Par exemple, une telle valeur est definie dans lefichier stdio.h, c’est la constante symbolique EOF.

Les entiers courts et longs. Il est garanti que toute donnee representable dans le type short estrepresentable aussi dans le type long7 (en bref : un long n’est pas plus court qu’un short !), mais la tailleexacte des donnees de ces types n’est pas fixee par la norme du langage.

De nos jours on trouve souvent :

unsigned short : 16 bits pour representer un nombre entier compris entre 0 et 65.535short : 16 bits pour representer un nombre entier compris entre -32.768 et 32.767unsigned long : 32 bits pour representer un nombre entier entre 0 et 4.294.967.296long : 32 bits pour representer un entier entre -2.147.483.648 et 2.147.483.647

Le type int. En principe, le type int correspond a la taille d’entier la plus efficace, c’est-a-dire la plusadaptee a la machine utilisee. Sur certains systemes et compilateurs int est synonyme de short, sur d’autres ilest synonyme de long.

Le type int peut donc poser un probleme de portabilite8 : le meme programme, compile sur deux machinesdistinctes, peut avoir des comportements differents. D’ou un conseil important : n’utilisez le type int que pourdes variables locales destinees a contenir des valeurs raisonnablement petites (inferieures en valeur absolue a32767) . Dans les autres cas il vaut mieux expliciter char, short ou long selon le besoin.

A propos des booleens. En C il n’existe donc pas de type booleen specifique. Il faut savoir qu’a toutendroit ou une expression booleenne est requise (typiquement, dans des instructions comme if ou while) on

6A retenir : un objet de type char est � unitaire � aussi bien du point de vue des tailles que de celui des adresses. Quelle que soitla machine utilisee, le compilateur C fera en sorte que le programmeur voie ces objets de la maniere suivante : si t est un tableaude char, la taille (au sens de l’operateur sizeof, cf. section 2.2.11) de t[0] vaut une unite de taille, et l’ecart entre les adresses det[1] et t[0] vaut une unite d’adressage. On peut dire que ces proprietes definissent le type char (ou, si vous preferez, les unites detaille et d’adressage).

7Si on considere un type comme l’ensemble de ses valeurs, on a donc les inclusions larges char ⊆ short ⊆ long (et aussi float⊆ double ⊆ long double).

8Un programme ecrit pour une machine ou un systeme A est dit portable s’il suffit de le recompiler pour qu’il tourne correctementsur une machine differente B. Par exemple, � putchar(’A’) ; � est une maniere portable d’obtenir l’affichage du caractere A, tandisque � putchar(65) ; � est (sur un systeme utilisant le code ASCII) une maniere non portable d’obtenir le meme affichage. Etreportable est un critere de qualite et de fiabilite important. On invoque l’efficacite pour justifier l’ecriture de programmes nonportables ; l’experience prouve que, lorsque son ecriture est possible, un programme portable est toujours meilleur qu’un programmenon portable pretendu equivalent.

c© H. Garreta, 2003 9

Page 10: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.5 Variables 1 ELEMENTS DE BASE

peut faire figurer n’importe quelle expression ; elle sera tenue pour vraie si elle est non nulle, elle sera considereefausse sinon. Ainsi, dans un contexte conditionnel,

expr(c’est-a-dire expr � vraie �) equivaut a

expr != 0(expr differente de 0). Inversement, lorsqu’un operateur (egalite, comparaison, etc.) produit une valeur booleenne,il rend 0 pour faux et 1 pour vrai.

Signalons aux esthetes que le fichier <types.h> comporte les declarations :

enum { false, true };typedef unsigned char Boolean;

qui introduisent la constante false valant 0, la constante true valant 1 et le type Boolean comme le type lemoins encombrant dans lequel on peut representer ces deux valeurs.

1.4.2 Types enumeres

Un type enumere, ou enumeration, est constitue par une famille finie de nombres entiers, chacun associea un identificateur qui en est le nom. Mis a part ce qui touche a la syntaxe de leur declaration, il n’y a pasgrand-chose a dire a leur sujet. La syntaxe de la declaration des enumerations est expliquee a la section 5.3. Parexemple, l’enonce :

enum jour_semaine { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche };

introduit un type enumere, appele enum jour semaine, constitue par les constantes lundi valant 0, mardivalant 1, mercredi valant 2, etc. Ainsi, les expressions mardi + 2 et jeudi representent la meme valeur.

Les valeurs d’un type enumere se comportent comme des constantes entieres ; elles font donc double emploiavec celles qu’on definit a l’aide de #define (cf. section 8.1.2). Leur unique avantage reside dans le fait quecertains compilateurs detectent parfois, mais ce n’est pas exige par la norme, les melanges entre objets de typesenumeres distincts ; ces types sont alors le moyen d’augmenter la securite des programmes.

A propos des types enumeres voyez aussi la section 5.3.

1.4.3 Nombres flottants

La norme ANSI prevoit trois types de nombres flottants : float (simple precision), double (double precision)et long double (precision etendue). La norme ne specifie pas les caracteristiques de tous ces types. Il est garantique toute valeur representable dans le type float est representable sans perte d’information dans le type double,et toute valeur representable dans le type double l’est dans le type long double.

Typiquement, sur des systemes de taille moyenne, un float occupe 32 bits et un double 64, ce qui donnepar exemple des float allant de -1.70E38 a -0.29E-38 et de 0.29E-38 a 1.70E38 avec 7 chiffres decimauxsignificatifs, et des double allant de -0.90E308 a -0.56E-308 et de 0.56E-308 a 0.90E308 avec 15 chiffresdecimaux significatifs.

Les long double correspondent generalement aux flottants de grande precision manipules par certainscoprocesseurs arithmetiques ou les bibliotheques de sous-programmes qui les simulent. Mais il n’est pas excluque sur un systeme particulier un long double soit la meme chose qu’un double.

1.5 Variables

1.5.1 Syntaxe des declarations

La forme complete de la declaration d’une variable sera expliquee a la section 5.4. Dans le cas le plus simpleon trouve

specification var-init , var-init , ... var-init ;

10 c© H. Garreta, 2003

Page 11: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.5 Variables

ou specification est de la forme :

autoregisterstaticexternrien

constvolatile

rien

unsignedsignedrien

charshortlongint

floatdouble

long double

et chaque var-init est de la forme :

identificateur{

= expressionrien

}

Exemples :

int x, y = 0, z;extern float a, b;static unsigned short cpt = 1000;

Les declarations de variables peuvent se trouver :– en dehors de toute fonction, il s’agit alors de variables globales ;– a l’interieur d’un bloc, il s’agit alors de variables locales ;– dans l’en-tete d’une fonction, il s’agit alors d’arguments formels, places

– soit dans les parentheses de l’en-tete (fonction definie en syntaxe ANSI avec un prototype),– soit entre le nom de la fonction et le { initial (fonction definie en syntaxe originale ou sans prototype).

Exemple. Avec prototype :

long i = 1;

int une_fonction(int j) {short k;...

}

Sans prototype :

long i = 1;

int une_fonction(j)int j;

{short k;...

}

Ci-dessus, i est une variable globale, k une variable locale et j un argument formel de une fonction.

1.5.2 Visibilite des variables

La question de la visibilite des identificateurs (c’est-a-dire � quels sont les identificateurs auxquels on peutfaire reference en un point d’un programme ? �) est reglee en C comme en Pascal, avec une simplification (lesfonctions ne peuvent pas etre imbriquees les unes dans les autres) et une complication (tout bloc peut comporterses propres definitions de variables locales).

Un bloc est une suite de declarations et d’instructions encadree par une accolade ouvrante “{” et l’accoladefermante “}” correspondante. Le corps d’une fonction est lui-meme un bloc, mais d’autres blocs peuvent etreimbriques dans celui-la.

Variables locales. Tout bloc peut comporter un ensemble de declarations de variables, qui sont alorsdites locales au bloc en question. Une variable locale ne peut etre referencee que depuis l’interieur du bloc ouelle est definie ; en aucun cas on ne peut y faire reference depuis un point exterieur a ce bloc. Dans le bloc ou ilest declare, le nom d’une variable locale masque toute variable de meme nom definie dans un bloc englobant lebloc en question.

c© H. Garreta, 2003 11

Page 12: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.5 Variables 1 ELEMENTS DE BASE

Toutes les declarations de variables locales a un bloc doivent etre ecrites au debut du bloc, avant la premiereinstruction.

Arguments formels. Pour ce qui concerne leur visibilite, les arguments formels des fonctions sont considerescomme des variables locales du niveau le plus haut, c’est-a-dire des variables declarees au debut du bloc le plusexterieur9. Un argument formel est accessible de l’interieur de la fonction, partout ou une variable locale plusprofonde ne le masque pas. En aucun cas on ne peut y faire reference depuis l’exterieur de la fonction.

Variables globales. Le nom d’une variable globale ou d’une fonction peut etre utilise depuis n’importequel point compris entre sa declaration (pour une fonction : la fin de la declaration de son en-tete) et la findu fichier ou la declaration figure, sous reserve de ne pas etre masquee par une variable locale ou un argumentformel de meme nom.

La question de la visibilite inter-fichiers est examinee a la section 1.6. On peut noter d’ores et deja qu’elle nese pose que pour les variables globales et les fonctions, et qu’elle concerne l’edition de liens, non la compilation,car le compilateur ne traduit qu’un fichier source a la fois et, pendant la traduction d’un fichier, il ne � voit �pas les autres.

1.5.3 Allocation et duree de vie des variables

Les variables globales sont toujours statiques, c’est-a-dire permanentes : elles existent pendant toute laduree de l’execution. Le systeme d’exploitation se charge, immediatement avant l’activation du programme, deles allouer dans un espace memoire de taille adequate, eventuellement garni de valeurs initiales.

A l’oppose, les variables locales et les arguments formels des fonctions sont automatiques : l’espace corres-pondant est alloue lors de l’activation de la fonction ou du bloc en question et il est rendu au systeme lorsquele controle quitte cette fonction ou ce bloc. Certains qualifieurs (static, register, voir les sections 1.5.5 et1.5.6) permettent de modifier l’allocation et la duree de vie des variables locales.

Remarque. On note une grande similitude entre les variables locales et les arguments formels des fonctions :ils ont la meme visibilite et la meme duree de vie. En realite c’est presque la meme chose : les arguments formelssont de vraies variables locales avec l’unique particularite d’etre automatiquement initialises (par les valeurs desarguments effectifs) lors de l’activation de la fonction.

1.5.4 Initialisation des variables

Variables statiques. En toute circonstance la declaration d’une variable statique peut indiquer une valeurinitiale a ranger dans la variable. Cela est vrai y compris pour des variables de types complexes (tableaux oustructures). Exemple :

double x = 0.5e3;int t[5] = { 11, 22, 33, 44, 55 };

Bien que la syntaxe soit analogue, une telle initialisation n’a rien en commun avec une affectation commecelles qui sont faites durant l’execution du programme. Il s’agit ici uniquement de preciser la valeur qui doitetre deposee dans l’espace alloue a la variable, avant que l’execution ne commence. Par consequent :

– la valeur initiale doit etre definie par une expression constante (calculable durant la compilation) ;– une telle initialisation est entierement gratuite, elle n’a aucune incidence ni sur la taille ni sur la duree du

programme executable produit.

Les variables statiques pour lesquelles aucune valeur initiale n’est indiquee sont remplies de zeros. L’in-terpretation de ces zeros depend du type de la variable.

Variables automatiques. Les arguments formels des fonctions sont automatiquement initialises lors deleur creation (au moment de l’appel de la fonction) par les valeurs des arguments effectifs. Cela est la definitionmeme des arguments des fonctions.

La declaration d’une variable locale peut elle aussi comporter une initialisation. Mais il ne s’agit pas de lameme sorte d’initialisation que pour les variables statiques : l’initialisation represente ici une affectation tout afait ordinaire. Ainsi, placee a l’interieur d’un bloc, la construction

int i = exp; /* declaration + initialisation */

equivaut au couple

9Par consequent, on ne doit pas declarer un argument formel et une variable locale du niveau le plus haut avec le meme nom.

12 c© H. Garreta, 2003

Page 13: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.5 Variables

int i; /* declaration */...i = exp ; /* affectation */

Par consequent :– l’expression qui donne la valeur initiale n’a pas a etre constante, puisqu’elle est evaluee a l’execution,

chaque fois que la fonction ou le bloc est active ;– une telle initialisation � coute � le meme prix que l’affectation correspondante, c’est-a-dire le temps

d’evaluation de l’expression qui definit la valeur initiale.

Les variables automatiques pour lesquelles aucune valeur initiale n’est indiquee sont allouees avec une valeurimprevisible.

Remarque. Dans le C original, une variable automatique ne peut etre initialisee que si elle est simple(c’est-a-dire autre que tableau ou structure). Cette limitation ne fait pas partie du C ANSI.

1.5.5 Variables locales statiques

Le qualifieur static, place devant la declaration d’une variable locale, produit une variable qui est– pour sa visibilite, locale ;– pour sa duree de vie, statique (c’est-a-dire permanente).

Elle n’est accessible que depuis l’interieur du bloc ou elle est declaree, mais elle est creee au debut del’activation du programme et elle existe aussi longtemps que dure l’execution de celui-ci. Exemple :

void bizarre1(void) {static int cpt = 1000;printf("%d ", cpt);cpt++;

}

Lorsque la declaration d’une telle variable comporte une initialisation, il s’agit de l’initialisation d’une va-riable statique : elle est effectuee une seule fois avant l’activation du programme. D’autre part, une variablelocale statique conserve sa valeur entre deux activations consecutives de la fonction. Ainsi, trois appels succes-sifs de la fonction ci-dessus produisent l’affichage des valeurs 1000, 1001, 1002. On aurait pu obtenir un effetanalogue avec le programme

int cpt = 1000; void

bizarre2(void) {printf("%d ", cpt);cpt++;

}

mais ici la variable cpt est globale et peut donc etre modifiee inconsiderement par une autre fonction, ou entreren conflit avec un autre objet de meme nom, tandis que dans la premiere version elle n’est visible que depuisl’interieur de la fonction et donc a l’abri des manipulations maladroites. On notera pour finir que la versionsuivante est erronee :

void bizarre3(void) {int cpt = 1000;printf("%d ", cpt);cpt++;

}

En effet, tous les appels de bizarre3 afficheront la meme valeur 1000.

Attention. Malgre tout le bien qu’on vient d’en dire, les variables locales statiques ont une particularitepotentiellement fort dangereuse : il en existe une seule instance pour toutes les activations de la fonction danslaquelle elles sont declarees. Ainsi, dans l’exemple suivant :

void fonction_suspecte(void) {static int i;...

α fonction_suspecte(); β...

}

c© H. Garreta, 2003 13

Page 14: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.5 Variables 1 ELEMENTS DE BASE

la valeur de la variable i avant et apres l’appel de fonction suspecte (c’est-a-dire aux points α et β) peutne pas etre la meme, car la deuxieme activation de fonction suspecte accede aussi a i. Cela est tout a faitinhabituel pour une variable locale. Consequence a retenir : les variables locales statiques se marient mal avecla recursivite.

1.5.6 Variables critiques

Le qualifieur register precedant une declaration de variable informe le compilateur que la variable enquestion est tres frequemment accedee pendant l’execution du programme et qu’il y a donc lieu de prendretoutes les dispositions utiles pour en accelerer l’acces. Par exemple, dans certains calculateurs de telles variablessont logees dans un registre de l’unite centrale de traitement (CPU) plutot que dans la memoire centrale ; decette maniere l’acces a leur valeur ne met pas en œuvre le bus de la machine.

Les variables ainsi declarees doivent etre locales et d’un type simple (nombre, pointeur). Elles sont automa-tiquement initialisees a zero chaque fois qu’elles sont creees. Le compilateur accorde ce traitement special auxvariables dans l’ordre ou elles figurent dans les declarations. Lorsque cela n’est plus possible (par exemple, parceque tous les registres de la CPU sont pris) les declarations register restantes sont ignorees. Il convient doncd’appliquer ce qualifieur aux variables les plus critiques d’abord. Exemple :

char *strcpy(char *dest, char *srce) {register char *d = dest, *s = srce;while ((*d++ = *s++) != 0)

;return dest;

}

Attention. L’utilisation du qualifieur register est interessante lorsque l’on doit utiliser un compilateurrustique, peu � optimisateur �. Or de nos jours les compilateurs de C ont fini par devenir tres perfectionnes etintegrent des algorithmes d’optimisation, parmi lesquels la determination des variables critiques et leur allocationdans les registres de la CPU. Il s’avere alors que le programmeur, en appliquant le qualifieur register a sesvariables preferees (qu’il croit critiques alors qu’elles ne le sont pas reellement), gene le travail du compilateuret obtient un programme moins efficace que s’il n’avait jamais utilise ce qualifieur.

1.5.7 Variables constantes et volatiles

Le qualifieur const place devant une variable ou un argument formel informe le compilateur que la variableou l’argument en question ne changera pas de valeur tout au long de l’execution du programme ou de l’activationde la fonction. Ce renseignement permet au compilateur d’optimiser la gestion de la variable, la nature exacted’une telle optimisation n’etant pas specifiee. Par exemple un compilateur peut juger utile de ne pas allouerdu tout une variable qualifiee const et de remplacer ses occurrences par la valeur initiale10 indiquee lors de ladeclaration. Il est conseille de toujours declarer const les variables et les arguments formels qui peuvent l’etre.

Note. C’est regrettable mais, pour la plupart des compilateurs, une variable qualifiee const n’est pas touta fait une expression constante au sens de la section 1.3.4. En particulier, pour ces compilateurs une variable,meme qualifiee const, ne peut pas etre utilisee pour indiquer le nombre d’elements dans une declaration detableau.

Le C ANSI introduit aussi les notions de pointeur constant et de pointeur sur constante, expliquees a lasection 5.4.2.

Le sens du qualifieur volatile depend lui aussi de l’implantation. Il diminue le nombre d’hypotheses, etdonc d’optimisations, que le compilateur peut faire sur une variable ainsi qualifiee. Par exemple toute variabledont la valeur peut etre modifiee de maniere asynchrone (detection d’interruption, organe d’entree-sortie, etc.)doit etre qualifiee volatile, sur les systemes ou cela a un sens. Cela previent le compilateur que sa valeur peutchanger mysterieusement, y compris dans une section du programme qui ne comporte aucune reference a cettevariable.

Les compilateurs sont tenus de signaler toute tentative decelable de modification d’une variable const. Misa part cela, sur un systeme particulier ces deux qualifieurs peuvent n’avoir aucun autre effet. Ils n’appartiennentpas au C original.

10La declaration d’une variable const doit necessairement comporter une initialisation car sinon, une telle variable ne pouvantpas etre affectee par la suite, elle n’aurait jamais de valeur definie.

14 c© H. Garreta, 2003

Page 15: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1 ELEMENTS DE BASE 1.6 Variables, fonctions et compilation separee

1.6 Variables, fonctions et compilation separee

1.6.1 Identificateurs publics et prives

Examinons maintenant les regles qui regissent la visibilite inter-fichiers des identificateurs. La question neconcerne que les noms de variables et de fonctions, car les autres identificateurs (noms de structures, de types,etc.) n’existent que pendant la compilation et ne peuvent pas etre partages par deux fichiers. Il n’y a pas deprobleme pour les variables locales, dont la visibilite se reduit a l’etendue de la fonction ou du bloc contenantleur definition. Il ne sera donc question que des noms des variables globales et des noms des fonctions.

Jargon. Identificateurs publics et prives. Un nom de variable ou de fonction defini dans un fichier source etpouvant etre utilise dans d’autres fichiers sources est dit public. Un identificateur qui n’est pas public est appeleprive.

Regle 1.– Sauf indication contraire, tout identificateur global est public ;– le qualifieur static, precedant la declaration d’un identificateur global, rend celui-ci prive.

On prendra garde au fait que le qualifieur static n’a pas le meme effet quand il s’applique a un identificateurlocal (static change la duree de vie, d’automatique en statique, sans toucher a la visibilite) et quand il s’appliquea un identificateur global (static change la visibilite, de publique en privee, sans modifier la duree de vie).

Lorsqu’un programme est decompose en plusieurs fichiers sources il est fortement conseille, pour ne pas direobligatoire, d’utiliser le qualifieur static pour rendre prives tous les identificateurs qui peuvent l’etre. Si on nesuit pas cette recommandation on verra des fichiers qui etaient corrects separement devenir errones lorsqu’ilssont relies, uniquement parce qu’ils partagent a tort des identificateurs publics.

1.6.2 Declaration d’objets externes

Nous ne considerons donc desormais que les noms publics. Un identificateur reference dans un fichier alorsqu’il est defini dans un autre fichier est appele externe. En general, les noms externes doivent faire l’objet d’unedeclaration : le compilateur ne traitant qu’un fichier a la fois, les proprietes de l’objet externe doivent etreindiquees pour que la compilation puisse avoir lieu correctement.

Jargon. Definition et declaration d’une variable ou d’une fonction. Aussi bien une declaration qu’unedefinition d’un nom de variable ou de fonction est une formule qui specifie la classe syntaxique (variable oufonction) et les attributs (type, valeur initiale, etc.) de l’identificateur en question. En plus de cela :

– une definition produit la creation de l’objet dont l’identificateur est le nom ;– une declaration se limite a indiquer que l’objet en question a du etre cree dans un autre fichier qui sera

fourni lors de l’edition de liens.

(� Creer � une variable ou une fonction c’est reserver l’espace correspondant, rempli par l’eventuelle valeurinitiale de la variable ou par le code de la fonction).

Regle 2.– Toute variable doit avoir ete definie (c’est-a-dire declaree normalement) ou declaree externe avant son

utilisation ;– une fonction peut etre referencee alors qu’elle n’a encore fait l’objet d’aucune definition ni declaration

externe ; elle est alors supposee etre– externe,– a resultat entier (int),– sans prototype (cf. section 4.2) ;

– par consequent, si une fonction n’est pas a resultat entier alors elle doit etre soit definie soit declareeexterne avant son appel, meme si elle est ulterieurement definie dans le fichier ou figure l’appel.

La declaration externe d’une variable s’obtient en faisant preceder une declaration ordinaire du mot-cleextern. Exemple :

extern unsigned long n;

Dans un autre fichier cette variable aura ete definie :unsigned long n;

La declaration externe d’une variable doit etre identique, au mot extern pres, a sa definition. Sauf pour lesdeux points suivants :

– une declaration externe ne doit pas comporter d’initialisateur (puisque la declaration externe n’alloue pasla variable),

c© H. Garreta, 2003 15

Page 16: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

1.6 Variables, fonctions et compilation separee 1 ELEMENTS DE BASE

– dans une declaration externe de tableau, il est inutile d’indiquer la taille de celui-ci (puisque la declarationexterne n’alloue pas le tableau).

Exemple. Dans le fichier ou sont definies les variables n et table, on ecrira :

unsigned long n = 1000;int table[100];

Dans un autre fichier, ou ces variables sont uniquement referencees, on ecrira :

extern unsigned long n;extern int table[];

La declaration externe d’une fonction s’obtient en ecrivant l’en-tete de la fonction, precede du mot externet suivi d’un point-virgule ; le mot extern est facultatif. Exemple : definition de la fonction

double sqr(double x) {return x * x;

}

Declaration externe dans un autre fichier :

double sqr(double x);ou

double sqr(double);

ou l’un ou l’autre de ces enonces, precede du mot extern.

En syntaxe originale (c’est-a-dire � sans prototype �) il faut en outre ne pas ecrire les arguments formels.Definition :

double sqr(x)double x;{return x * x;}

Declaration externe dans un autre fichier :

double sqr();

Regle 3. Dans l’ensemble des fichiers qui constituent un programme, chaque nom public :– doit faire l’objet d’une et une seule definition ;– peut etre declare externe (y compris dans le fichier ou il est defini) un nombre quelconque de fois.

Cette regle volontariste est simple et elle exprime la meilleure facon de programmer. Il faut savoir cependantque chaque systeme tolere des ecarts, qui revelent surtout la rusticite de l’editeur de liens sous-jacent. La clartedes concepts et la fiabilite des programmes y perdent beaucoup.

Un comportement frequent est le suivant : appelons momentanement � declaration-definition � une expressiongenerale de la forme

externopt declaration{

= initialisateurrien

};

Nous pouvons donner la regle relachee :

Regle 3’. Dans l’ensemble des fichiers qui constituent un programme, chaque nom public peut faire l’objetd’un nombre quelconque de declarations-definitions, mais :

– il doit y avoir au moins une declaration-definition sans le mot-cle extern ;– il peut y avoir au plus une declaration-definition comportant un initialisateur.

Des techniques et conseils pour ecrire des programmes modulaires en C sont exposes a la section 8.2.

16 c© H. Garreta, 2003

Page 17: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS

2 Operateurs et expressions

2.1 Generalites

Dans cette section nous etudions les operateurs et les expressions du langage C. Les expressions simples sontles constantes litterales (0, ’A’, 0.31416e1), les constantes symboliques (lundi, false) et les noms de variables(x, nombre). Les operateurs servent a construire des expressions complexes, comme 2 * x ou sin(0.31416e1).Les proprietes d’une expression complexe decoulent essentiellement de la nature de l’operateur qui chapeautel’expression.

On peut distinguer les expressions pures des expressions a effet de bord. Dans tous les cas une expressionrepresente une valeur. Une expression pure ne fait que cela ; l’etat du systeme est le meme avant et apres sonevaluation. Au contraire, une expression a effet de bord modifie le contenu d’une ou plusieurs variables. Parexemple, l’expression y + 1 est pure, tandis que l’affectation x = y + 1 (qui en C est une expression) est a effetde bord, car elle modifie la valeur de la variable x. Comme nous le verrons, en C un grand nombre d’expressionssont a effet de bord.

Remarque 1. Les operateurs dont il sera question ici peuvent aussi apparaıtre dans les declarations, pourla construction des types derives (tableaux, pointeurs et fonctions) comme dans la declaration complexe :

char (*t[20])();

La signification des expressions ainsi ecrites est alors tres differente de celle des expressions figurant dans lapartie executable des programmes, mais on peut signaler d’ores et deja que toutes ces constructions obeissent auxmemes regles de syntaxe, notamment pour ce qui concerne la priorite des operateurs et l’usage des parentheses.La question des declarations complexes sera vue a la section 5.4.

Remarque 2. C’est une originalite de C que de considerer les � designateurs � complexes (les objets pointes,les elements des tableaux, les champs des structures, etc.) comme des expressions construites avec des operateurset obeissant a la loi commune, notamment pour ce qui est des priorites. On se souvient qu’en Pascal les signesqui permettent d’ecrire de tels designateurs (c’est-a-dire les selecteurs [], ^ et .) n’ont pas statut d’operateur. Ilne viendrait pas a l’idee d’un programmeur Pascal d’ecrire 2 + (t[i]) afin de lever une quelconque ambiguıtesur la priorite de + par rapport a celle de [], alors qu’en C de telles expressions sont habituelles. Bien sur, dans2 + (t[i]) les parentheses sont superflues, car la priorite de l’operateur [] est superieure a celle de +, mais cen’est pas le cas dans (2 + t)[i], qui est une expression tout aussi legitime que la precedente.

2.1.1 Lvalue et rvalue

Toute expression possede au moins deux attributs : un type et une valeur. Par exemple, si i est une variableentiere valant 10, l’expression 2 * i + 3 possede le type entier et la valeur 23. Dans la partie executable desprogrammes on trouve deux sortes d’expressions :

– Lvalue (expressions signifiant � le contenu de... �). Certaines expressions sont representees par une formulequi determine un emplacement dans la memoire. La valeur de l’expression est alors definie comme lecontenu de cet emplacement. C’est le cas des noms des variables, des composantes des enregistrements etdes tableaux, etc. Une lvalue possede trois attributs : une adresse, un type et une valeur. Exemples : x,table[i], fiche.numero.

– Rvalue (expressions signifiant � la valeur de... �). D’autres expressions ne sont pas associees a un empla-cement de la memoire : elles ont un type et une valeur, mais pas d’adresse. C’est le cas des constantes etdes expressions definies comme le resultat d’une operation arithmetique. Une rvalue ne possede que deuxattributs : type, valeur. Exemples : 12, 2 * i + 3.

Toute lvalue peut etre vue comme une rvalue ; il suffit d’ignorer le contenant (adresse) pour ne voir que lecontenu (type, valeur). La raison principale de la distinction entre lvalue et rvalue est la suivante : seule unelvalue peut figurer a gauche du signe = dans une affectation. Cela justifie les appellations � left value � et � rightvalue �.

Les sections suivantes preciseront, pour chaque sorte d’expression complexe, s’il s’agit ou non d’une lvalue.Pour les expressions simples, c’est-a-dire les constantes litterales et les identificateurs, la situation est la suivante :

– sont des lvalue :– les noms des variables simples : nombres et pointeurs,– les noms des variables d’un type struct ou union

– ne sont pas des lvalue :– les constantes,– les noms des variables de type tableau,

c© H. Garreta, 2003 17

Page 18: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

– les noms des fonctions.

2.1.2 Priorite des operateurs

C comporte de tres nombreux operateurs. La plupart des caracteres speciaux designent des operations etsubissent les memes regles syntaxiques, notamment pour ce qui concerne le jeu des priorites et la possibilite deparenthesage. La table 2 montre l’ensemble de tous les operateurs, classes par ordre de priorite.

prior operateurs sens de l’associativite15 () [] . -> →14 ! ~ ++ -- -un *un &un sizeof (type) ←13 *bin / % →12 + -bin →11 << >> →10 < <= > >= →9 == != →8 &bin →7 ^ →6 | →5 && →4 || →3 ? : ←2 = *= /= %= += -= <<= >>= &= ^= |= ←1 , →

Tab. 2 – Operateurs du langage C

Remarque. Dans certains cas un meme signe, comme -, designe deux operateurs, l’un unaire (a un argu-ment), l’autre binaire (a deux arguments). Dans le tableau 2, les suffixes un et bin precisent de quel operateuril s’agit.

Le signe → indique l’associativite � de gauche a droite � ; par exemple, l’expression x - y - z signifie (x -y) - z. Le signe ← indique l’associativite de � droite a gauche � ; par exemple, l’expression x = y = z signifiex = (y = z).

Notez que le sens de l’associativite des operateurs precise la signification d’une expression, mais non lachronologie de l’evaluation des sous-expressions.

2.2 Presentation detaillee des operateurs

2.2.1 Appel de fonction ()

Operation : application d’une fonction a une liste de valeurs. Format :exp0 ( exp1 , ... expn )

exp1 , ... expn sont les arguments effectifs de l’appel de la fonction. exp0 doit etre de type fonction rendant unevaleur de type T ou bien11 adresse d’une fonction rendant une valeur de type T. Alors exp0 ( exp1 , ... expn )possede le type T. La valeur de cette expression decoule de la definition de la fonction en question (a l’interieurde la fonction, cette valeur est precisee par une ou plusieurs instructions � return exp ; �).

L’expression exp0 ( exp1 , ... expn ) n’est pas une lvalue. Exemple :y = sqr(2 * x) + 3;

sachant que sqr est le nom d’une fonction rendant un double, l’expression sqr(2 * x) a le type double. Uncoup d’œil au corps de la fonction (cf. section 1.6.2) pourrait nous apprendre que la valeur de cette expressionn’est autre que le carre de son argument, soit ici la valeur 4x2.

Les contraintes supportees par les arguments effectifs ne sont pas les memes dans le C ANSI et dans le Coriginal (ceci est certainement la plus grande difference entre les deux versions du langage).

En C ANSI, si la fonction a ete definie ou declaree avec prototype (cf. section 4.1) :11Cette double possibilite est commentee a la section 6.3.4.

18 c© H. Garreta, 2003

Page 19: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

– le nombre des arguments effectifs doit correspondre a celui des arguments formels12 ;– chaque argument effectif doit etre compatible, au sens de l’affectation, avec l’argument formel correspon-

dant ;– la valeur de chaque argument effectif subit eventuellement les memes conversions qu’elle subirait dans

l’affectationargument formel = argument effectif

En C original, ou en C ANSI si la fonction n’a pas ete definie ou declaree avec prototype :– aucune contrainte (de nombre ou de type) n’est imposee aux arguments effectifs ;– tout argument effectif de type char ou short est converti en int ;– tout argument effectif de type float est converti en double ;– les autres arguments effectifs ne subissent aucune conversion.

Par exemple, avec la fonction sqr definie a la section 1.6.2, l’appely = sqr(2);

est errone en C original (la fonction recoit la representation interne de la valeur entiere 2 en � croyant � quec’est la representation interne d’un double) mais il est correct en C ANSI (l’entier 2 est converti en double aumoment de l’appel).

Toutes ces questions sont reprises plus en detail a la section 4.

Attention. On notera que les parentheses doivent apparaıtre, meme lorsque la liste des arguments est vide.Leur absence ne provoque pas d’erreur, mais change completement le sens de l’expression. Cela constitue unpiege assez vicieux tendu aux programmeurs dont la langue maternelle est Pascal.

2.2.2 Indexation []

Definition restreinte (element d’un tableau). Operation : acces au ieme element d’un tableau. Format :

exp0 [ exp1 ]

exp0 doit etre de type � tableau d’objets de type T �, exp1 doit etre d’un type entier. Alors exp0[exp1] est detype T ; cette expression designe l’element du tableau dont l’indice est donne par la valeur de exp1.

Exemple. L’expression t[i] designe le (i+1)eme element du tableau t.

Deux details auxquels on tient beaucoup en C :– le premier element d’un tableau a toujours l’indice 0 ;– il n’est jamais verifie que la valeur de l’indice dans une reference a un tableau appartient a l’intervalle

0...N − 1 determine par le nombre N d’elements alloues par la declaration du tableau. Autrement dit, iln’y a jamais de � test de debordement �.

En C, les tableaux sont toujours a un seul indice ; mais leurs composantes peuvent etre a leur tour destableaux. Par exemple, un element d’une matrice rectangulaire sera note :

m[i][j]

Une telle expression suppose que m[i] est de type � tableau d’objets de type T � et donc que m est de type� tableau de tableaux d’objets de type T �. C’est le cas, par exemple, si m a ete declaree par une expression dela forme (NL et NC sont des constantes) :

double m[NL][NC];

Definition complete (indexation au sens large). Operation : acces a un objet dont l’adresse est donneepar une adresse de base et un deplacement. Format :

exp0 [ exp1 ]

exp0 doit etre de type � adresse d’un objet de type T �, exp1 doit etre de type entier. Alors exp0[exp1] designel’objet de type T ayant pour adresse (voir la figure 2) :

exp0 + exp1× taille(T)

Il est clair que, si exp0 est de type tableau, les deux definitions de l’indexation donnees coıncident. Exemple :si t est un tableau d’entiers et p un � pointeur vers entier � auquel on a affecte l’adresse de t[0], alors lesexpressions suivantes designent le meme objet :

t[i] p[i] *(p + i) *(t + i)

Dans un cas comme dans l’autre, l’expression exp0[exp1] est une lvalue, sauf si elle est de type tableau.12Il existe neanmoins un moyen pour ecrire des fonctions avec un nombre variable d’arguments (cf. section 4.3.4)

c© H. Garreta, 2003 19

Page 20: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

exp0exp1

Fig. 2 – L’indexation

2.2.3 Selection .

Operation : acces a un champ d’une structure ou d’une union. Format :

exp . identif

exp doit posseder un type struct ou union, et identif doit etre le nom d’un des champs de la structure ou del’union en question. En outre, exp doit etre une lvalue. Alors, exp.identif designe le champ identif de l’objetdesigne par exp.

Cette expression est une lvalue, sauf si elle est de type tableau.

Exemple. Avec la declaration

struct personne {long int num;struct {

char rue[32];char *ville;

} adresse;} fiche;

les expressionsfiche.num fiche.adresse fiche.adresse.rue etc.

designent les divers champs de la variable fiche. Seules les deux premieres sont des lvalue.

2.2.4 Selection dans un objet pointe ->

Operation : acces au champ d’une structure ou d’une union pointee. Format :

exp->identif

exp doit posseder le type � adresse d’une structure ou d’une union � et identif doit etre le nom d’un des champsde la structure ou de l’union en question. Dans ces conditions, exp->identif designe le champ identif de lastructure ou de l’union dont l’adresse est indiquee par la valeur de exp.

Cette expression est une lvalue, sauf si elle est de type tableau.

Ainsi, exp->identif est une autre maniere de noter (*exp).identif (remarquez que les parentheses sontindispensables).

Par exemple, avec la declaration :

struct noeud {int info;struct noeud *fils, *frere;

} *ptr;

les expressions suivantes sont correctes :

ptr->info ptr->fils->frere ptr->fils->frere->frere

2.2.5 Negation !

Operation : negation logique. Format :!exp

20 c© H. Garreta, 2003

Page 21: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

Aucune contrainte. Cette expression designe une valeur de l’ensemble {0, 1}, definie par :

!exp ≡{

1, si exp = 00, si exp 6= 0

Cette expression n’est pas une lvalue.

Remarque. Bien que cela ne soit pas exige par le langage C, on evitera de � nier � (et plus generalement decomparer a zero) des expressions d’un type flottant (float, double). A cause de l’imprecision des calculs avecde tels nombres, l’egalite a zero d’un flottant n’est souvent que le fruit du hasard.

2.2.6 Complement a 1 ~

Operation : negation bit a bit. Format :~exp

exp doit etre d’un type entier. Cette expression designe l’objet de meme type que exp qui a pour codage internela configuration de bits obtenue en inversant chaque bit du codage interne de la valeur de exp : 1 devient 0, 0devient 1.

Cette expression n’est pas une lvalue.

Remarque. Le complement a un n’est pas une operation abstraite (cf. section 2.3.3). La portabilite d’unprogramme ou cet operateur figure n’est donc pas assuree.

2.2.7 Les celebres ++ et --

Il existe deux operateurs unaires ++ differents : l’un est postfixe (ecrit derriere l’operande), l’autre prefixe(ecrit devant).

1. Operation : post-incrementation. Format :exp++

exp doit etre de type numerique (entier ou flottant) ou pointeur. Ce doit etre une lvalue. Cette expression estcaracterisee par :

– un type : celui de exp ;– une valeur : la meme que exp avant l’evaluation de exp++ ;– un effet de bord : le meme que celui de l’affectation exp = exp + 1.

2. Operation : pre-incrementation. Format :++exp

exp doit etre de type numerique (entier ou flottant) ou pointeur. Ce doit etre une lvalue. Cette expression estcaracterisee par :

– un type : celui de exp ;– une valeur : la meme que exp apres l’evaluation de exp++ ;– un effet de bord : le meme que celui de l’affectation exp = exp + 1.

Les expressions exp++ et ++exp ne sont pas des lvalue.

Exemple.L’affectation equivaut ay = x++ ; y = x ; x = x + 1 ;y = ++x ; x = x + 1 ; y = x ;

L’operateur ++ beneficie de l’arithmetique des adresses au meme titre que +. Ainsi, si exp est de type� pointeur vers un objet de type T �, la quantite effectivement ajoutee a exp par l’expression exp++ depend dela taille de T.

Il existe de meme deux operateurs unaires -- donnant lieu a des expressions exp-- et --exp. L’explicationest la meme, en remplacant +1 par −1.

Application : realisation d’une pile. Il est tres agreable de constater que les operateurs ++ et -- et lamaniere d’indexer les tableaux en C se combinent harmonieusement et permettent par exemple la realisationsimple et efficace de piles par des tableaux, selon le schema suivant :

c© H. Garreta, 2003 21

Page 22: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

– declaration et initialisation d’une pile (OBJET est un type predefini, dependant du probleme particulierconsidere ; MAXPILE est une constante representant un majorant du nombre d’elements dans la pile) :

OBJET espace[MAXPILE];int nombreElements = 0;

– operation � empiler la valeur de x � :

espace[nombreElements++] = x;

– operation � depiler une valeur et la ranger dans x � :

x = espace[--nombreElements];

On notera que, si on procede comme indique ci-dessus, la variable nombreElements possede constammentla valeur que son nom suggere : le nombre d’elements effectivement presents dans la pile.

2.2.8 Moins unaire -

Operation : changement de signe. Format :-exp

exp doit etre une expression numerique (entiere ou reelle). Cette expression represente l’objet de meme typeque exp dont la valeur est l’opposee de celle de exp. Ce n’est pas une lvalue.

2.2.9 Indirection *

Operation : acces a un objet pointe. On dit aussi � dereference �. Format :*exp

exp doit etre une expression de type � adresse d’un objet de type T �. *exp represente alors l’objet de type Tayant pour adresse la valeur de exp.

L’expression *exp est une lvalue.

Remarque. On prendra garde au fait qu’il existe un bon nombre d’operateurs ayant une priorite superieurea celle de *, ce qui oblige souvent a utiliser des parentheses. Ainsi par exemple les expressions Pascal e↑[i] ete[i]↑ doivent respectivement s’ecrire, en C, (*e)[i] et *(e[i]), la deuxieme pouvant aussi s’ecrire *e[i].

2.2.10 Obtention de l’adresse &

Operation : obtention de l’adresse d’un objet occupant un emplacement de la memoire. Format :&exp

exp doit etre une expression d’un type quelconque T. Ce doit etre une lvalue. L’expression &exp a pour type� adresse d’un objet de type T � et pour valeur l’adresse de l’objet represente par exp. L’expression &exp n’estpas une lvalue.

Ainsi, si i est une variable de type int et p une variable de type � pointeur vers un int �, alors a la suitede l’instruction

p = &i;

i et *p designent le meme objet.

Exemple 1. L’utilisation la plus frequente de cet operateur est l’obtention de l’adresse d’un objet en vue deson passage a une fonction qui le modifie :

scanf("%d %lf %s", &i, &t[j], &p->nom);

Exemple 2. Une autre utilisation elegante de cet operateur est la creation de composantes � fixes � dansles structures chaınees. Le programme suivant declare une liste chaınee circulaire representee par le pointeurentree et reduite pour commencer a un unique maillon qui est son propre successeur (voir figure 3) ; d’autresmaillons seront crees dynamiquement durant l’execution :

22 c© H. Garreta, 2003

Page 23: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

entree

en_tete_fixe

Fig. 3 – Maillon fixe en tete d’une liste chaınee

struct en_tete {long taille;struct en_tete *suivant;

} en_tete_fixe = { 0, &en_tete_fixe };

struct en_tete *entree = &en_tete_fixe;

Remarque. Une expression reduite a un nom de tableau, a un nom de fonction ou a une constante chaınede caracteres est consideree comme une constante de type adresse ; l’operateur & applique a de telles expressionsest donc sans objet. Un tel emploi de & devrait etre considere comme errone, mais beaucoup de compilateurs secontentent de l’ignorer.

2.2.11 Operateur sizeof

Operation : calcul de la taille correspondant a un type. Premiere forme :

sizeof ( descripteur-de-type )

Cette expression represente un nombre entier qui exprime la taille qu’occuperait en memoire un objet possedantle type indique (les descripteurs de types sont expliques a la section 5.4).

Deuxieme forme :sizeof exp

exp est une expression quelconque, qui n’est pas evaluee par sizeof. L’expression sizeof exp represente lataille qu’occuperait en memoire un objet possedant le meme type que exp.

Dans un cas comme dans l’autre sizeof... est une expression constante. En particulier, ce n’est pas unelvalue.

La taille des objets est exprimee en nombre d’octets. Plus exactement, l’unite choisie est telle que la valeurde sizeof(char) soit 1 (on peut aussi voir cela comme une definition du type char).

Remarque 1. Dans le C original, le type de l’expression sizeof... est int. Dans le C ANSI, ce type peutchanger d’un systeme a un autre. Pour cette raison il est defini, sous l’appellation size t, dans le fichierstddef.h. Il est recommande de declarer de type size t toute variable (resp. toute fonction) devant contenir(resp. devant renvoyer) des nombres qui representent des tailles.

Remarque 2. Lorsque son operande est un tableau (ou une expression de type tableau) la valeur renduepar sizeof est l’encombrement effectif du tableau. Par exemple, avec la declaration

char tampon[80];

l’expression sizeof tampon vaut 80, meme si cela paraıt en contradiction avec le fait que tampon peut etre vucomme de type � adresse d’un char �. Il en decoule une propriete bien utile : quel que soit le type du tableaut, la formule

sizeof t / sizeof t[0]

exprime toujours le nombre d’elements de t.

2.2.12 Conversion de type (“cast” operator)

Operation : conversion du type d’une expression. Format :

( type2 ) exp

type2 represente un descripteur de type ; exp est une expression quelconque. L’expression ci-dessus designe unelement de type type2 � proche � de l’element represente par exp.

c© H. Garreta, 2003 23

Page 24: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

L’expression (type2)exp n’est pas une lvalue.

Les conversions legitimes (et utiles) sont13 :– Entier vers un entier plus long (ex. char → int). Le codage de exp est etendu de telle maniere que la

valeur representee soit inchangee.– Entier vers un entier plus court (ex. long → short). Si exp est representable dans le type de destination,

sa valeur est la meme apres conversion. Sinon, la valeur de exp est purement et simplement tronquee (unetelle troncation, sans signification abstraite, est rarement utile).

– Entier signe vers entier non signe, ou le contraire. C’est une conversion � sans travail � : le compilateurse borne a interpreter autrement la valeur de exp, sans effectuer aucune transformation de son codageinterne.

– Flottant vers entier : la partie fractionnaire de la valeur de exp est supprimee. Par exemple, le flottant3.14 devient l’entier 3. Attention, on peut voir cette conversion comme une realisation de la fonctionmathematique � partie entiere �, mais uniquement pour les nombres positifs : la conversion de -3.14donne -3, non -4.

– Entier vers flottant. Sauf cas de debordement (le resultat est alors imprevisible), le flottant obtenu estcelui qui approche le mieux l’entier initial. Par exemple, l’entier 123 devient le flottat 123.0.

– Adresse d’un objet de type T1 vers adresse d’un objet de type T2. C’est une conversion sans travail :le compilateur change l’interpretation de la valeur de exp, sans effectuer aucune transformation de soncodage interne.Danger ! Une telle conversion est entierement placee sous la responsabilite du programmeur, le compilateurl’accepte toujours.

– Entier vers adresse d’un objet de type T. C’est encore une conversion sans travail : la valeur de exp estinterpretee comme un pointeur, sans transformation de son codage interne.Danger ! Une telle conversion est entierement placee sous la responsabilite du programmeur. De plus, si larepresentation interne de exp n’a pas la taille voulue pour un pointeur, le resultat est imprevisible.

Les conversions ou le type de exp ou le type type2 est un type struct ou union sont interdites.

Exemple. Une utilisation classique de l’operateur de conversion de type entre types pointeurs consiste as’en servir pour � voir � un espace memoire donne par son adresse comme possedant une certaine structure alorsqu’en fait il n’a pas ete ainsi declare :

– declaration d’une structure :

struct en_tete {long taille;struct en_tete *suivant;

};

– declaration d’un pointeur � generique � :

void *ptr;

– imaginez que –pour des raisons non detaillees ici– ptr possede a un endroit donne une valeur qu’il estlegitime de considerer comme l’adresse d’un objet de type struct en tete (alors que ptr n’a pas cetype-la). Voici un exemple de manipulation cet objet :

((struct en_tete *) ptr)->taille = n;

Bien entendu, une telle conversion de type est faite sous la responsabilite du programmeur, seul capable de� prouver � qu’a tel moment du programme ptr pointe bien un objet de type struct en tete.

Remarque. En toute rigueur, le fait que l’expression determinee par un operateur de conversion de type nesoit pas une lvalue interdit des expressions qui auraient pourtant ete pratiques, comme

((int) x)++;

Cependant, beaucoup de compilateurs acceptent cette expression, la traitant comme

x = (le type de x)((int) x + 1) ;

13Attention. L’expression (type)exp represente une valeur obtenue a partir de celle de exp. Dans le cas ou exp est une variable,il ne faut pas oublier que, contrairement a ce que pourraient suggerer certaines explications donnees ici, l’expression (type)exp nemodifie pas exp.

24 c© H. Garreta, 2003

Page 25: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

2.2.13 Operateurs arithmetiques

Ce sont les operations arithmetiques classiques : addition, soustraction, multiplication, division et modulo(reste de la division entiere). Format :

exp1

+-*/%

exp2

Aucune de ces expressions n’est une lvalue.

Avant l’evaluation de l’expression, les operandes subissent les conversions � usuelles � (cf. section 2.3.1). Lelangage C supporte l’arithmetique des adresses (cf. section 6.2.1).

A propos de la division. En C on note par le meme signe la division entiere, qui prend deux operandesentiers (short, int, long, etc.) et donne un resultat entier, et la division flottante dans laquelle le resultat estflottant (float, double). D’autre part, les regles qui commandent les types des operandes et du resultat desexpressions arithmetiques (cf. section 2.3.1), et notamment la regle dite � du plus fort �, ont la consequenceimportante suivante : dans l’expression

expr1 / expr2

– si expr1 et expr2 sont toutes deux entieres alors � / � est traduit par l’operation � division entiere �14, etle resultat est entier,

– si au moins une des expressions expr1 ou expr2 n’est pas entiere, alors l’operation faite est la divisionflottante des valeurs de expr1 et expr2 toutes deux converties dans le type double. Le resultat est unevaleur double qui approche le rationnel expr1

expr2du mieux que le permet la la precision du type double.

Il resulte de cette regle un piege auquel il faut faire attention : 1/2 ne vaut pas 0.5 mais 0. De meme, dans

int somme, nombre;float moyenne;...moyenne = somme / 100;

la valeur de moyenne n’est pas ce que ce nom suggere, car somme et 100 sont tous deux entiers et l’expressionsomme / 100 est entiere, donc tronquee (et il ne faut pas croire que l’affectation ulterieure a la variable flottantemoyenne pourra retrouver les decimales perdues). Dans ce cas particulier, la solution est simple :

moyenne = somme / 100.0;

Meme probleme si le denominateur avait ete une variable entiere, comme dans

moyenne = somme / nombre;

ici la solution est un peu plus compliquee :

moyenne = somme / (double) nombre;

2.2.14 Decalages << >>

Operation : decalages de bits. Format :

exp1

{<<>>

}exp2

exp1 et exp2 doivent etre d’un type entier (char, short, long, int...). L’expression exp1 << exp2 (resp. exp1 >>exp2) represente l’objet de meme type que exp1 dont la representation interne est obtenue en decalant les bitsde la representation interne de exp1 vers la gauche15 (resp. vers la droite) d’un nombre de positions egal a lavaleur de exp2. Autrement dit,

14On prendra garde au fait que si les operandes ne sont pas tous deux positifs la division entiere du langage C ne coincide pasavec le � quotient par defaut � (quotient de la � division euclidienne � des matheux).

Ici, si q est le quotient de a par b alors |q| est le quotient de |a| par |b|. Par exemple, la valeur de (−17)/5 ou de 17/(−5) est −3alors que le quotient par defaut de −17 par 5 est plutot −4 (−17 = 5× (−4) + 3).

15Par convention la droite d’un nombre code en binaire est le cote des bits de poids faible (les unites) tandis que la gauche estcelui des bits de poids forts (correspondant aux puissances 2k avec k grand).

c© H. Garreta, 2003 25

Page 26: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

exp1 << exp2

est la meme chose que((exp1 << 1) << 1) ... << 1

sin on peut considerer que la formule << 1 apparaıt exp2 fois dans l’expression ci-dessus. Remarque analoguepour le decalage a droite >>.

Les bits sortants sont perdus. Les bits entrants sont :– dans le cas du decalage a gauche, des zeros ;– dans le cas du decalage a droite : si exp1 est d’un type non signe, des zeros ; si exp1 est d’un type signe,

des copies du bit de signe16.Autrement dit, si on suppose que la valeur de exp1 est codee sur huit bits, notes

b7b6b5b4b3b2b1b0

(chaque bi vaut 0 ou 1), alorsexp1 << 1 ≡ b6b5b4b3b2b1b00exp1 >> 1 ≡ 0b7b6b5b4b3b2b1 (cas non signe)exp1 >> 1 ≡ b7b7b6b5b4b3b2b1 (cas signe)

Avant l’evaluation du resultat, les operandes subissent les conversions usuelles (cf. section 2.3.1). Ces ex-pressions ne sont pas des lvalue.

Remarque. Les operateurs de decalage de bits ne sont pas des operations abstraites (cf. section 2.3.3). Laportabilite d’un programme ou ils figurent n’est donc pas assuree.

2.2.15 Comparaisons == != < <= > >=

Il s’agit des comparaisons usuelles : egal, different, inferieur, inferieur ou egal, superieur, superieur ou egal.Format :

exp1

==!=<<=>>=

exp2

exp1 et exp2 doivent etre d’un type simple (nombre ou pointeur). Cette expression represente l’un des elements(de type int) 1 ou 0 selon que la relation indiquee est ou non verifiee par les valeurs de exp1 et exp2.

Avant la prise en compte de l’operateur, les operandes subissent les conversions usuelles (cf. section 2.3.1).Ces expressions ne sont pas des lvalue.

Notez que, contrairement a ce qui se passe en Pascal, ces operateurs ont une priorite superieure a celle desconnecteurs logiques, ce qui evite beaucoup de parentheses disgracieuses. Ainsi, en C, l’expression

0 <= x && x < 10

a une syntaxe correcte.

A propos de � etre vrai � et � etre non nul �. Comme on l’a dit (cf. section 1.4.1), le type booleenn’existe pas en C. N’importe quelle expression peut occuper la place d’une condition ; elle sera tenue pour faussesi elle est nulle, pour vraie dans tous les autres cas. Une consequence de ce fait est la suivante : a la place de

if (i != 0) etc.while (*ptchar != ’\0’) etc.for (p = liste; p != NULL; p = p->suiv) etc.

on peut ecrire respectivementif (i) etc.while (*ptchar) etc.for (p = liste; p; p = p->suiv) etc.

On admet generalement qu’a cause de proprietes techniques des processeurs, ces expressions raccourciesrealisent une certaine optimisation des programmes. C’est pourquoi les premiers programmeurs C, qui ne dispo-saient que de compilateurs simples, peu optimisateurs, ont pris l’habitude de les utiliser largement. On ne peut

16De cette maniere le decalage a gauche correspond (sauf debordement) a la multiplication par 2exp2 , tandis que le decalage adroite correspond a la division entiere par 2exp2 , aussi bien si exp1 est signee que si elle est non signee

26 c© H. Garreta, 2003

Page 27: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

plus aujourd’hui conseiller cette pratique. En effet, les compilateurs actuels sont suffisamment perfectionnespour effectuer de leur propre chef cette sorte d’optimisations et il n’y a plus aucune justification de l’emploi detelles comparaisons implicites, qui rendent les programmes bien moins expressifs.

Attention. La relation d’egalite se note ==, non =. Voici une faute possible chez les nouveaux venus a Cen provenance de Pascal qui, pour traduire le bout de Pascal if a = 0 then etc., ecrivent

if (a = 0) etc.

Du point de vue syntaxique cette construction est correcte, mais elle est loin d’avoir l’effet escompte. En tantqu’expression, a = 0 vaut 0 (avec, comme effet de bord, la mise a zero de a). Bien sur, il fallait ecrire

if (a == 0) etc.

2.2.16 Operateurs de bits & | ^

Ces operateurs representent des operations logiques sur les bits des representations internes des valeurs desoperandes : AND, OR, XOR. Format :

exp1

&|^

exp2

exp1 et exp2 doivent etre d’un type entier. Cette expression represente un objet de type entier dont le codageinterne est construit bit par bit a partir des bits correspondants des valeurs de exp1 et exp2, selon la tablesuivante, dans laquelle (expk)i signifie � le ieme bit de expk � :

(exp1)i (exp2)i (exp1 & exp2)i (exp1 ^ exp2)i (exp1 | exp2)i

0 0 0 0 00 1 0 1 11 0 0 1 11 1 1 0 1

Avant l’evaluation du resultat, les operandes subissent les conversions usuelles (cf. section 2.3.1). Ces ex-pressions ne sont pas des lvalue.

Exemple. Un cas frequent d’utilisation de ces operateurs est la realisation d’ensembles, ou plutot de sous-ensembles d’un intervalle d’entiers [ 0 ... N − 1 ], N valant 8, 16 ou 32.

Par exemple, dans une bibliotheque graphique on peut souhaiter une fonction effectuant l’affichage d’untexte muni d’un ensemble d’attributs (gras, italique, souligne, capitalise, etc.) telle que :

void afficher(char *texte, unsigned long attributs);

Pour faire en sorte que l’argument attributs puisse representer un ensemble d’attributs quelconque, onassocie les attributs a des entiers conventionnels :

#define GRAS 1 (en binaire : 00...000001)#define ITALIQUE 2 (en binaire : 00...000010)#define SOULIGNE 4 (en binaire : 00...000100)#define CAPITALISE 8 (en binaire : 00...001000)etc.

Les valeurs utilisees pour representer les attributs sont des puissances de 2 distinctes, c’est-a-dire des nombresqui, ecrits en binaire, comportent un seul 1 place differemment de l’un a l’autre.

L’utilisateur d’ne telle fonction emploie l’operateur | pour composer, lors de l’appel, l’ensemble d’attributsqui l’interesse :

afficher("Bonjour", GRAS | ITALIQUE); // affichage en gras et italique

De son cote, l’auteur de la fonction utilise l’operateur & pour savoir si un attribut appartient ou non al’ensemble donne :

...if ((attributs & GRAS) != 0)

prendre les dispositions necessaires pour afficher en graselse if ((attributs & ITALIQUE) != 0)

prendre les dispositions necessaires pour afficher en italique...

c© H. Garreta, 2003 27

Page 28: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

2.2.17 Connecteurs logiques && et ||

Operations : conjonction et disjonction. Format :

exp1

{&&||

}exp2

exp1 et exp2 sont deux expressions de n’importe quel type. Cette expression represente un element de type intparmi { 0, 1 } defini de la maniere suivante :

Pour evaluer exp1 && exp2 : exp1 est evaluee d’abord et– si la valeur de exp1 est nulle, exp2 n’est pas evaluee et exp1 && exp2 vaut 0 ;– sinon exp2 est evaluee et exp1 && exp2 vaut 0 ou 1 selon que la valeur de exp2 est nulle ou non.

Pour evaluer exp1 || exp2 : exp1 est evaluee d’abord et– si la valeur de exp1 est non nulle, exp2 n’est pas evaluee et exp1 || exp2 vaut 1 ;– sinon exp2 est evaluee et exp1 || exp2 vaut 0 ou 1 selon que la valeur de exp2 est nulle ou non.

Ces expressions ne sont pas des lvalue.

Applications. Ainsi, C garantit que le premier operande sera evalue d’abord et que, s’il suffit a determinerle resultat de la conjonction ou de la disjonction, alors le second operande ne sera meme pas evalue. Pour leprogrammeur, cela est une bonne nouvelle. En effet, il n’est pas rare qu’on ecrive des conjonctions dont lepremier operande � protege � (dans l’esprit du programmeur) le second ; si cette protection ne fait pas partiede la semantique de l’operateur pour le langage utilise, le programme resultant peut etre faux. Consideronsl’exemple suivant : un certain tableau table est forme de nombre chaınes de caracteres ; soit ch une variablechaıne. L’operation � recherche de ch dans table � peut s’ecrire :

i = 0;while (i < nombre && strcmp(table[i], ch) != 0)

i++;

ce programme est juste parce que la condition strcmp(table[i], ch) != 0 n’est evaluee qu’apres avoir verifieque la condition i < nombre est vraie (un appel de strcmp(table[i], ch) avec un premier argument table[i]invalide peut avoir des consequences tout a fait desastreuses).

Une autre consequence de cette maniere de concevoir les connecteurs logiques est stylistique. En C la conjonc-tion et la disjonction s’evaluent dans l’esprit des instructions plus que dans celui des operations. L’applicationpratique de cela est la possibilite d’ecrire sous une forme fonctionnelle des algorithmes qui dans d’autres lan-gages restent sequentiels. Voici un exemple : le predicat qui caracterise la presence d’un element dans une listechaınee. Version (recursive) habituelle :

int present(INFO x, LISTE L) { /* l’information x est-elle dans la liste L ? */if (L == NULL)

return 0;else if (L->info == x)

return 1;else

return present(x, L->suivant);}

Version dans un style fonctionnel, permise par la semantique des operateurs && et || :

int existe(INFO x, LISTE L) { /* l’information x est-elle dans la liste L ? */return L != NULL && (x == L->info || existe(x, L->suivant));

}

2.2.18 Expression conditionnelle ? :

Operation : sorte de if...then...else... se presentant sous forme d’expression. Format :exp0 ? exp1 : exp2

exp0 est d’un type quelconque. exp1 et exp2 doivent etre de types compatibles. Cette expression est evaluee dela maniere suivante :

La condition exp0 est evaluee d’abord– si sa valeur est non nulle, exp1 est evaluee et definit la valeur de l’expression conditionnelle. Dans ce cas,

exp2 n’est pas evaluee ;

28 c© H. Garreta, 2003

Page 29: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.2 Presentation detaillee des operateurs

– sinon, exp2 est evaluee et definit la valeur de l’expression conditionnelle. Dans ce cas, exp1 n’est pasevaluee.

L’expression exp0 ?exp1:exp2 n’est pas une lvalue.

Exemple. L’operateur conditionnel est souvent interchangeable avec l’instruction conditionnelle, mais per-met parfois de reels allegements du code. Imaginons un programme devant afficher un des textes non ou ouiselon que la valeur d’une variable reponse est nulle ou non. Solutions classiques de ce micro-probleme :

if (reponse)printf("la reponse est oui");

elseprintf("la reponse est non");

ou bien, avec une variable auxiliaire :char *texte;...if (reponse)

texte = "oui";else

texte = "non");printf("la reponse est %s", texte);

Avec l’operateur conditionnel :printf("la reponse est %s", reponse ? "oui" : "non");

2.2.19 Affectation =

Operation : affectation, consideree comme une expression. Format :exp1 = exp2

exp1 doit etre une lvalue. Soit type1 le type de exp1 ; l’affectation ci-dessus represente le meme objet que

( type1 ) exp2

(la valeur de exp2 convertie dans le type de exp1), avec pour effet de bord le rangement de cette valeur dansl’emplacement de la memoire determine par exp1.

L’expression exp1 = exp2 n’est pas une lvalue.

Contrairement a ce qui se passe dans d’autres langages, une affectation est donc consideree en C comme unesorte d’expression : elle � fait � quelque chose, mais aussi elle � vaut � une certaine valeur et, a ce titre, elle peutfigurer comme operande dans une sur-expression. On en deduit la possibilite des affectations multiples, commedans l’expression :

a = b = c = 0;

comprise comme a = (b = (c = 0)). Elle aura donc le meme effet que les trois affectations a = 0 ; b = 0 ;c = 0 ;. Autre exemple, lecture et traitement d’une suite de caracteres dont la fin est indiquee par un point :

while ((c = getchar()) != ’.’)exploitation de c

Des contraintes pesent sur les types des deux operandes d’une affectation exp1 = exp2. Lorsqu’elles sontsatisfaites on dit que exp1 et exp2 sont compatibles pour l’affectation. Essentiellement :

– deux types numeriques sont toujours compatibles pour l’affectation. La valeur de exp2 subit eventuellementune conversion avant d’etre rangee dans exp1. La maniere de faire cette conversion est la meme que dansle cas de l’operateur de conversion (cf. section 2.2.12) ;

– si exp1 et exp2 sont de types adresses distincts, certains compilateurs (dont ceux conformes a la normeANSI) les considereront comme incompatibles tandis que d’autres compilateurs se limiteront a donner unmessage d’avertissement lors de l’affectation de exp2 a exp1 ;

– dans les autres cas, exp1 et exp2 sont compatibles pour l’affectation si et seulement si elles sont de memetype.

D’autre part, de la signification du nom d’une variable de type tableau (cf. 5.1.1) et de celle d’une variablede type structure (cf. 5.2.1) on deduit que :

– on ne peut affecter un tableau a un autre, meme s’ils sont definis de maniere rigoureusement identique(un nom de tableau n’est pas une lvalue) ;

– on peut affecter le contenu d’une variable de type structure ou union a une autre, a la condition qu’ellesaient ete declarees comme ayant exactement le meme type.

c© H. Garreta, 2003 29

Page 30: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.2 Presentation detaillee des operateurs 2 OPERATEURS ET EXPRESSIONS

2.2.20 Autres operateurs d’affectation += *= etc.

Operation binaire vue comme une modification du premier operande. Format :

exp1

+=-=*=/=%=>>=<<=&=^=|=

exp2

exp1 doit etre une lvalue. Cela fonctionne de la maniere suivante : si • represente l’un des operateurs + - * / %>> << & ^ |, alors

exp1 • = exp2

peut etre vue comme ayant la meme valeur et le meme effet queexp1 = exp1 • exp2

mais avec une seule evaluation de exp1. L’expression resultante n’est pas une lvalue.

Exemple. Ecrivons la version iterative usuelle de la fonction qui calcule xn avec x flottant et n entier nonnegatif :

double puissance(double x, int n) {double p = 1;while (n != 0) {

if (n % 2 != 0) /* n est-il impair ? */p *= x;

x *= x;n /= 2;

}return p;

}

Remarque. En examinant ce programme, on peut faire les memes commentaires qu’a l’occasion de plusieursautres elements du langage C :

– l’emploi de ces operateurs constitue une certaine optimisation du programme. En langage machine, lasuite d’instructions qui traduit textuellement a += c est plus courte que celle qui correspond a a = b+ c. Or, un compilateur rustique traitera a = a + c comme un cas particulier de a = b + c, sans voirl’equivalence avec la premiere forme.

– helas, l’emploi de ces operateurs rend les programmes de plus en plus denses et de moins en moins facilesa lire, ce qui favorise l’apparition d’erreurs de programmation.

– de nos jours les compilateurs de C sont devenus assez perfectionnes pour deceler automatiquement la pos-sibilite de telles optimisations. Par consequent, l’argument de l’efficacite ne justifie plus qu’on obscurcisseun programme par l’emploi de tels operateurs.

Il faut savoir, d’autre part, qu’il existe des situations ou l’emploi de ces operateurs n’est pas qu’une questiond’efficacite. En effet, si exp1 est une expression sans effet de bord, alors les expressions exp1 • = exp2 et exp1

= exp1 • exp2 sont reellement equivalentes. Mais ce n’est plus le cas si exp1 a un effet de bord. Il est clair, parexemple, que les deux expressions suivantes ne sont pas equivalentes (la premiere est tout simplement erroneeerronee)17 :

nombre[rand() % 100] = nombre[rand() % 100] + 1; // ERREUR !

etnombre[rand() % 100] += 1;

2.2.21 L’operateur virgule ,

Operation : evaluation en sequence. Format :17La fonction rand() renvoie un entier aleatoire distinct chaque fois qu’elle est appelee.

30 c© H. Garreta, 2003

Page 31: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2 OPERATEURS ET EXPRESSIONS 2.3 Autres remarques

exp1 , exp2

exp1 et exp2 sont quelconques. L’evaluation de exp1 , exp2 consiste en l’evaluation de exp1 suivie de l’evaluationde exp2. L’expression exp1 , exp2 possede le type et la valeur de exp2 ; le resultat de l’evaluation de exp1 estoublie, mais non son eventuel effet de bord (cet operateur n’est utile que si exp1 a un effet de bord).

L’expression exp1 , exp2 n’est pas une lvalue.

Exemple. Un cas frequent d’utilisation de cet operateur concerne la boucle for (cf. section 3.2.6), dont lasyntaxe requiert exactement trois expressions a trois endroits bien precis. Parfois, ces expressions doivent etredoublees :

...for (pr = NULL, p = liste; p != NULL; pr = p, p = p->suivant)

if (p->valeur == x)break;

...

Remarque. Dans des contextes ou des virgules apparaissent normalement, par exemple lors d’un appelde fonction, des parentheses sont requises afin de forcer le compilateur a reconnaıtre l’operateur virgule. Parexemple, l’expression

une_fonction(exp1, (exp2, exp3, exp4), exp5);

represente un appel de une fonction avec trois arguments effectifs : les valeurs de exp1, exp4 et exp5. Avantl’evaluation de exp4, les expressions exp2 puis exp3 auront ete evaluees.

2.3 Autres remarques

2.3.1 Les conversions usuelles

Les regles suivantes s’appliquent aux expressions construites a l’aide d’un des operateurs *, /, %, +, -, <, <=,>, >=, ==, !=, &, ^, |, && et ||. Mutatis mutandis, elles s’appliquent aussi a celles construites avec les operateurs*=, /=, %=, +=, -=, <<=, >>=, &=, ^= et |=.

Dans ces expressions, les operandes subissent certaines conversions avant que l’expression ne soit evaluee.En C ANSI ces conversions, dans l’ ” ordre logique ” ou elles sont faites, sont les suivantes :

– Si un des operandes est de type long double, convertir l’autre dans le type long double ; le type del’expression sera long double.

– Sinon, si un des operandes est de type double, convertir l’autre dans le type double ; le type de l’expressionsera double.

– Sinon, si un des operandes est de type float, convertir l’autre dans le type float ; le type de l’expressionsera float18.

– Effectuer la promotion entiere : convertir les char, les short, les enumerations et les champs de bits endes int. Si l’une des valeurs ne peut pas etre representee dans le type int, les convertir toutes dans letype unsigned int.

– Ensuite, si un des operandes est unsigned long, convertir l’autre en unsigned long ; le type de l’expres-sion sera unsigned long.

– Sinon, si un des operandes est long et l’autre unsigned int19 :– si un long peut representer toutes les valeurs unsigned int, alors convertir l’operande de type unsignedint en long. Le type de l’expression sera long ;

– sinon, convertir les deux operandes en unsigned long. Le type de l’expression sera unsigned long.– Sinon, si un des operandes est de type long, convertir l’autre en long ; le type de l’expression sera long.– Sinon, si un des operandes est de type unsigned int, convertir l’autre en unsigned int ; le type de l’expression

sera unsigned int.– Sinon, et si l’expression est correcte, c’est que les deux operandes sont de type int ; le type de l’expression

sera int.

18Cette regle est appaue avec le C ANSI : le compilateur accepte de faire des calculs sur des flottants en simple precision. Dansle C original, elle doit etre remplacee par la regle suivante : � sinon, si l’un des operandes est de type float, convertir les deuxoperandes dans le type double ; le type de l’expression sera double �.

19Cette regle compliquee est apparue avec le C ANSI. En C original, le type unsigned � tire vers lui � les autres types.

c© H. Garreta, 2003 31

Page 32: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

2.3 Autres remarques 2 OPERATEURS ET EXPRESSIONS

2.3.2 L’ordre d’evaluation des expressions

Les seules expressions pour lesquelles l’ordre (chronologique) d’evaluation des operandes est specifie sont lessuivantes :

– � exp1 && exp2 � et � exp1 || exp2 � : exp1 est evaluee d’abord ; exp2 n’est evaluee que si la valeur de exp1

ne permet pas de conclure ;– � exp0 ? exp1 : exp2 � exp0 est evaluee d’abord. Une seule des expressions exp1 ou exp2 est evaluee

ensuite ;– � exp0 , exp0 � : exp1 est evaluee d’abord, exp2 est evaluee ensuite.Dans tous les autres cas, C ne garantit pas l’ordre dans lequel les operandes intervenant dans une expression

ou les arguments effectifs d’un appel de fonction sont evalues ; il ne faut donc pas faire d’hypothese a ce sujet.La question ne se pose que lorsque ces operandes et arguments sont a leur tour des expressions complexes ; elleest importante dans la mesure ou C favorise la programmation avec des effets de bord. Par exemple, si i vaut1, l’expression a[i] + b[i++] peut aussi bien additionner a[1] et b[1] que a[2] et b[1].

L’ordre d’evaluation des operandes d’une affectation n’est pas fixe non plus. Pour evaluer exp1 = exp2, onpeut evaluer d’abord l’adresse de exp1 et ensuite la valeur de exp2, ou bien faire l’inverse. Ainsi, le resultat del’affectation a[i] = b[i++] est imprevisible.

2.3.3 Les operations non abstraites

Beaucoup d’operateurs etudies dans cette section (operateurs arithmetiques, comparaisons, logiques, etc.)representent des operations abstraites, c’est-a-dire possedant une definition formelle qui ne fait pas intervenirles particularites de l’implantation du langage. Bien sur, les operandes sont representes dans la machine par desconfigurations de bits, mais seule leur interpretation comme des entites de niveau superieur (nombres entiers,flottants...) est utile pour definir l’effet de l’operation en question ou les contraintes qu’elle subit.

A l’oppose, un petit nombre d’operateurs, le complement a un (~), les decalages (<< et >>) et les operationsbit-a-bit (&, ^ et |), n’ont pas de signification abstraite. Les transformations qu’ils effectuent sur les suites de bitsqui sont les codages internes de leurs operandes n’ont pas d’interpretation formelle a un niveau superieur. Detelles operations sont reservees aux programmes qui remplissent des fonctions de tres bas niveau, c’est-a-dire quisont aux points de contact entre la composante logicielle et la composante materielle d’un systeme informatique.

L’aspect de cette question qui nous interesse le plus ici est celui-ci : la portabilite des programmes contenantdes operations non abstraites n’est pas assuree. Ce defaut, qui n’est pas redhibitoire dans l’ecriture de fonctionsde bas niveau (ces fonctions ne sont pas destinees a etre portees), doit rendre le programmeur tres precautionneuxdes qu’il s’agit d’utiliser ces operateurs dans des programmes de niveau superieur, et le pousser a :

– isoler les operations non abstraites dans des fonctions de petite taille bien reperees ;– documenter soigneusement ces fonctions ;– constituer des jeux de tests validant chacune de ces fonctions.

32 c© H. Garreta, 2003

Page 33: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3 INSTRUCTIONS

3 Instructions

3.1 Syntaxe

Dans les descriptions syntaxiques suivantes le suffixe � opt � indique que la formule qu’il qualifie est option-nelle. Une formule avec des points de suspension, comme � element ... element �, indique un element pouvantapparaıtre un nombre quelconque, eventuellement nul, de fois.

instruction→ instruction-bloc→ instruction-expression→ instruction-goto→ instruction-if→ instruction-while→ instruction-do→ instruction-for→ instruction-break→ instruction-continue→ instruction-switch→ instruction-return→ instruction-vide→ identificateur : instruction

instruction-bloc→ { declaration ... declaration

instruction ... instruction }instruction-expression

→ expression ;

instruction-goto→ goto identif ;

instruction-if→ if ( expression ) instruction else instruction→ if ( expression ) instruction

instruction-while→ while ( expression ) instruction

instruction-do→ do instuction while ( expression ) ;

instruction-for→ for ( expressionopt ; expressionopt ; expressionopt ) instruction

instruction-break→ break ;

instruction-continue→ continue ;

instruction-switch→ switch ( expression ) { instruction-ou-case ... instruction-ou-case }

instruction-ou-case→ case expression-constante : instructionopt

→ default : instruction→ instruction

instruction-return→ return expressionopt ;

instruction-vide→ ;

C et le point-virgule. Comme l’indique la syntaxe de l’instruction-bloc, en C le point-virgule n’est pas

c© H. Garreta, 2003 33

Page 34: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3.2 Presentation detaillee des instructions 3 INSTRUCTIONS

un separateur d’instructions mais un terminateur de certaines instructions. Autrement dit, il appartient a lasyntaxe de chaque instruction de preciser si elle doit ou non etre terminee par un point-virgule, independammentde ce par quoi l’instruction est suivie dans le programme.

L’oubli du point-virgule a la fin d’une instruction qui en requiert un est toujours une erreur, quelle que soitla situation de cette instruction. Un surnombre de points-virgules cree des instructions vides.

3.2 Presentation detaillee des instructions

3.2.1 Blocs

Un bloc est une suite de declarations et d’instructions encadree par les deux accolades { et }. Du point devue de la syntaxe il se comporte comme une instruction unique et peut figurer en tout endroit ou une instructionsimple est permise.

Le bloc le plus exterieur d’une fonction et les autres blocs plus profonds ont le meme statut. En particulier,quelle que soit sa position dans le programme, un bloc peut comporter ses propres declarations de variables. Saufsi elle est declaree extern, une variable definie dans un bloc est locale a ce bloc et donc inconnue a l’exterieur.Dans le bloc ou elle est definie, une telle variable masque, sans le detruire, tout autre objet de meme nom connua l’exterieur du bloc.

Sauf si elles sont qualifiees static (ou extern), de telles variables sont creees et eventuellement initialiseeslors de l’activation du bloc ; elles sont detruites des que le controle quitte le bloc. Il ne faut donc pas espererqu’une telle variable conserve sa valeur entre deux passages dans le bloc.

Les variables locales aux blocs permettent d’optimiser la gestion de l’espace local. Par exemple, dans unprogramme tel que

if (...) {type1 n;...

}else {

type2 x;...

}

les variables n et x n’existeront jamais simultanement ; le compilateur peut donc leur allouer le meme emplace-ment de la memoire.

3.2.2 Instruction-expression

Format :expression ;

Mais oui, il suffit d’ecrire un point-virgule derriere n’importe quelle expression pour en faire une instruction.Exemples :

123; ai++; bx = 2 * x + 3; cprintf("%d\n", n); d

Intuitivement, une instruction-expression represente l’ordre � evaluez cette expression, ensuite oubliez leresultat �. Il est clair que si l’expression n’a pas d’effet de bord, cela n’aura servi a rien (exemple a). L’aspectutile de cette notion est : toute expression avec effet de bord pourra etre evaluee uniquement pour son effet debord (exemple b). Avec deux cas particuliers tres interessants :

– puisque l’affectation est une expression, on retrouve bien l’instruction d’affectation, fondamentale danstous les langages de programmation (exemple c) ;

– toute fonction peut etre appelee � comme une procedure � (exemple20 d), c’est-a-dire en ignorant la valeurqu’elle rend.

20On verra le moment venu (cf. section 7.2.5 d) que printf rend un resultat parfois utile.

34 c© H. Garreta, 2003

Page 35: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3 INSTRUCTIONS 3.2 Presentation detaillee des instructions

Remarque. Une consequence regrettable de tout cela est un piege assez vicieux tendu aux pascaliens.Imaginons que lirecaractere soit le nom d’une fonction sans argument. L’appel correct de cette fonctions’ecrit :

lirecaractere();

Cependant, puisque le nom d’une fonction est une expression (une constante valant l’adresse de la fonctio) etque toute expression suivie d’un point-virgule est une instruction correcte, l’enonce

lirecaractere;

sera trouve legal par le compilateur. Or cette expression (tout a fait analogue a l’exemple a ci-dessus) ne produitpas l’appel de la fonction et ne traduit donc probablement pas la pensee du programmeur.

3.2.3 Etiquettes et instruction goto

Format :etiquette : instruction

Une etiquette est un identificateur ; elle doit etre placee devant une fonction, separee de celle-ci par uncaractere deux points. Elle n’a pas a faire l’objet d’une declaration explicite ; il suffit de l’ecrire devant uneinstruction pour qu’elle soit automatiquement connue comme un nom a portee locale. Elle est alors utilisablepartout dans la fonction ou elle apparaıt (avant et apres l’instruction qu’elle prefixe) et elle reste inconnue endehors de la fonction.

L’instructiongoto etiquette ;

transfere le controle a l’instruction prefixee par l’etiquette en question.

Theoriquement, tout algorithme peut etre programme sans utiliser l’instruction goto. Dans certains langagescomme Pascal, elle est utilisee pour obtenir l’abandon d’une structure de controle (exemple : une boucle) depuisl’interieur de la structure. Un tel emploi de goto est avantageusement remplace en C par l’utilisation desinstructions return, break et continue.

Il est donc rare que l’on ait besoin de l’instruction goto en C. Elle ne se revele utile que lorsqu’il fautabandonner plusieurs structures de controle (if, while, for...) imbriquees les unes dans les autres. Exemple :

for (i = 0; i < N1; i++) {for (j = 0; j <= N2; j++)

for (k = 0; k <= N2; k++) {...if (...)

goto grande_boucle;...

}...

grande_boucle: // ici on a quitte les deux boucles internes (sur j et k)... // mais on est toujours dans la boucle la plus externe (sur i)

}

3.2.4 Instruction if...else...

Formats :

if (expression)instruction1

elseinstruction2

et

if (expression)instruction1

Dans la premiere forme, expression est evaluee : si elle est vraie (i.e. non nulle) instruction1 est executee ;si elle est fausse (nulle) instruction2 est executee. Dans la deuxieme forme, expression est evaluee : si elle estvraie instruction1 est executee ; sinon, rien n’est execute.

c© H. Garreta, 2003 35

Page 36: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3.2 Presentation detaillee des instructions 3 INSTRUCTIONS

On notera que l’expression conditionnelle doit figurer entre parentheses. Celles-ci font partie de la syntaxedu if, non de celle de l’expression.

Lorsque plusieurs instructions if sont imbriquees, il est convenu que chaque else se rapporte au dernierif pour lequel le compilateur a rencontre une condition suivie d’exactement une instruction. Le listing duprogramme peut (et doit !) traduire cela par une indentation (marge blanche) expressive, mais il ne faut pasoublier que le compilateur ne tient pas compte des marges. Par exemple, le programme suivant est probablementincorrect ; en tout cas, son indentation ne traduit pas convenablement les rapports entre les if et les else :

if (nombrePersonnes != 0)if (nombrePersonnes != nombreAdultes)

printf("Il y a des enfants!");else

printf("Il n’y a personne!");

Ce probleme se pose dans tous les langages qui offrent deux varietes d’instruction conditionnelle. On le resoutsoit par l’utilisation d’instructions vides :

if (nombrePersonnes != 0)if (nombrePersonnes != nombreAdultes)

printf("Il y a des enfants!");else

;else

printf("Il n’y a personne!");

soit, plus simplement, en utilisant des blocs :

if (nombrePersonnes != 0) {if (nombrePersonnes != nombreAdultes)

printf("Il y a des enfants!");}else

printf("Il n’y a personne!");

Remarque. La syntaxe prevoit exactement une instruction entre la condition et le else. Par consequent, unexces de points-virgules a la suite de instruction1 constitue une erreur. Voici une faute qu’on peut faire quandon debute :

if (nombrePersonnes != 0) {if (nombrePersonnes != nombreAdultes)

printf("Il y a des enfants!");};else

printf("Il n’y a personne!");

Il y a maintenant deux instructions entre la ligne du if et celle du else : une instruction-bloc { ... } et uneinstruction vide � ; �. Le compilateur signalera donc une erreur sur le else.

3.2.5 Instructions while et do...while

Ces instructions correspondent respectivement aux instructions while...do... et repeat...until... dulangage Pascal. Notez que la syntaxe exige que la condition figure entre parentheses. Formats :

while (expression)instruction

et

doinstruction

while (expression);

Le fonctionnement de ces instructions est decrit par les organigrammes de la figure 4. Fondamentalement,il s’agit de reiterer l’execution d’une certaine instruction tant qu’une certaine instruction, vue comme unecondition, reste vraie. Dans la structure while on verifie la condition avant d’executer l’instruction, tandis quedans la structure do...while on la verifie apres.

36 c© H. Garreta, 2003

Page 37: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3 INSTRUCTIONS 3.2 Presentation detaillee des instructions

instruction

expression != 0

nonoui

suite du programme

expression != 0

instruction

oui

non

suite du programme

Fig. 4 – Instruction while (a gauche) et do...while (a droite)

L’instruction do...while... execute donc au moins une fois l’instruction qui constitue son corps avantd’evaluer la condition de continuation. C’est en cela qu’elle est l’analogue de l’instruction repeat...until duPascal. Mais on notera que la condition figurant dans une instruction do...while (� faire tant que... �) et cellequi figurerait dans une instruction repeat...until equivalente (� repeter jusqu’a ce que... �) sont inverses l’unede l’autre.

3.2.6 Instruction for

Format :

for ( expr1 ; expr2 ; expr3 )instruction

Par definition, cette construction equivaut strictement a celle-ci :

expr1 ;while (expr2) {

instructionexpr3;

}

Ainsi, dans la construction for(expr1 ; expr2 ; expr3) :– expr1 est l’expression qui effectue les initialisations necessaires avant l’entree dans la boucle ;– expr2 est le test de continuation de la boucle ; il est evalue avant l’execution du corps de la boucle ;– expr3 est une expression (par exemple une incrementation) evaluee a la fin du corps de la boucle.

Par exemple, l’instruction Pascal :

for i:=0 to 9 dot[i]:=0

se traduit tout naturellement en C

for (i = 0; i < 10; i++)t[i] = 0;

Les expressions expr1 et expr3 peuvent etre absentes (les points-virgules doivent cependant apparaıtre). Parexemple

for ( ; expr2 ; )instruction

equivaut a

while (expr2)instruction

La condition de continuation expr2 peut elle aussi etre absente. On considere alors qu’elle est toujours vraie.Ainsi, la boucle � indefinie � peut se programmer :

for ( ; ; )instruction

Bien entendu, il faut dans ce cas que l’abandon de la boucle soit programme a l’interieur de son corps (sinoncette boucle indefinie devient infinie !). Exemple :

c© H. Garreta, 2003 37

Page 38: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3.2 Presentation detaillee des instructions 3 INSTRUCTIONS

for (;;) {printf("donne un nombre (0 pour sortir): ");scanf("%d", &n);if (n == 0)

break;...exploitation de la donnee n...

}

Lorsque l’abandon de la boucle correspond aussi a l’abandon de la procedure courante, break est avanta-geusement remplacee par return comme dans :

char *adresse_de_fin(char *ch) {/* renvoie adresse du caractere qui suit la chaıne ch */

for (;;)if (*ch++ == 0)

return ch;}

3.2.7 Instruction switch

Format :

switch ( expression )corps

Le corps de l’instruction switch prend la forme d’un bloc {...} renfermant une suite d’instructions entrelesquelles se trouvent des constructions de la forme

case expression-constante :

ou bien

default :

Le fonctionnement de cette instruction est le suivant : expression est evaluee ;– s’il existe un enonce case avec une constante qui egale la valeur de expression, le controle est transfere a

l’instruction qui suit cet enonce ;– si un tel case n’existe pas, et si l’enonce default existe, alors le controle est transfere a l’instruction qui

suit l’enonce default ;– si la valeur de expression ne correspond a aucun case et s’il n’y a pas d’enonce default, alors aucune

instruction n’est executee.

Attention. Lorsqu’il y a branchement reussi a un enonce case, toutes les instructions qui le suivent sontexecutees, jusqu’a la fin du bloc ou jusqu’a une instruction de rupture (break). Autrement dit, l’instructionswitch s’apparente beaucoup plus a une sorte de goto � parametre � (par la valeur de l’expression) qu’al’instruction case...of... de Pascal.

Exemple (idiot) :

j = 0;switch (i) {

case 3:j++;

case 2:j++;

case 1:j++;

}

Si on suppose que i ne peut prendre que les valeurs 0 ... 3, alors l’instruction ci-dessus a le meme effet quel’affectation j = i.

Pour obtenir un comportement similaire a celui du case...of... de Pascal, on doit utiliser l’instructionbreak, comme dans l’exemple suivant, dans lequel on compte la frequence de chaque chiffre et des caracteresblancs dans un texte :

38 c© H. Garreta, 2003

Page 39: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3 INSTRUCTIONS 3.2 Presentation detaillee des instructions

nb_blancs = nb_autres = 0;for (i = 0; i < 10; )

nb_chiffre[i++] = 0;while ((c = getchar()) != EOF)

switch (c) {case ’0’:case ’1’:case ’2’:case ’3’:case ’4’:case ’5’:case ’6’:case ’7’:case ’8’:case ’9’:

nb_chiffre[c - ’0’]++;break;

case ’ ’:case ’\n’:case ’\t’:

nb_blancs++;break;

default:nb_autres++;

}

3.2.8 Instructions break et continue

Formats :

break;continue;

Dans la portee d’une structure de controle (instructions for, while, do et switch), l’instruction breakprovoque l’abandon de la structure et le passage a l’instruction ecrite immediatement derriere. L’utilisation debreak ailleurs qu’a l’interieur d’une de ces quatre instructions constitue une erreur (que le compilateur signale).

Par exemple la construction

for (expr1 ; expr2 ; expr3) {...break;...

}

equivaut a

{for (expr1 ; expr2 ; expr3) {

...goto sortie;...

}sortie: ;}

L’instruction continue est moins souvent utilisee. Dans la portee d’une structure de controle de type boucle(while, do ou for), elle produit l’abandon de l’iteration courante et, le cas echeant, le demarrage de l’iterationsuivante. Elle agit comme si les instructions qui se trouvent entre l’instruction continue et la fin du corps de laboucle avaient ete supprimees pour l’iteration en cours : l’execution continue par le test de la boucle (precede,dans le cas de for, par l’execution de l’expression d’incrementation).

Lorsque plusieurs boucles sont imbriquees, c’est la plus profonde qui est concernee par les instructions breaket continue. Dans ce cas, l’emploi de ces instructions ne nous semble pas œuvrer pour la clarte des programmes.

c© H. Garreta, 2003 39

Page 40: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

3.2 Presentation detaillee des instructions 3 INSTRUCTIONS

3.2.9 Instruction return

Formats :

return expression ;

et

return ;

Dans un cas comme dans l’autre l’instruction return provoque l’abandon de la fonction en cours et le retoura la fonction appelante.

Dans la premiere forme expression est evaluee ; son resultat est la valeur que la fonction renvoie a la fonctionappelante ; c’est donc la valeur que l’appel de la fonction represente dans l’expression ou il figure. Si necessaire,la valeur de expression est convertie dans le type de la fonction (declare dans l’en-tete), les conversions autoriseesetant les memes que celles faites a l’occasion d’une affectation.

Dans la deuxieme forme, la valeur retournee par la fonction reste indeterminee. On suppose dans ce cas quela fonction appelante n’utilise pas cette valeur ; il est donc prudent de declarer void cette fonction.

Absence d’instruction return dans une fonction. Lorsque la derniere instruction (dans l’ordre del’execution) d’une fonction est terminee, le controle est rendu egalement a la procedure appelante. Tout se passecomme si l’accolade fermante qui termine le corps de la fonction etait en realite ecrite sous la forme

...return;

}

40 c© H. Garreta, 2003

Page 41: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4 FONCTIONS

4 Fonctions

Beaucoup de langages distinguent deux sortes de sous-programmes21 : les fonctions et les procedures. L’appeld’une fonction est une expression, tandis que l’appel d’une procedure est une instruction. Ou, si on prefere,l’appel d’une fonction renvoie un resultat, alors que l’appel d’une procedure ne renvoie rien.

En C on retrouve ces deux manieres d’appeler les sous-programmes, mais du point de la syntaxe le langagene connaıt que les fonctions. Autrement dit, un sous-programme est toujours suppose renvoyer une valeur, memelorsque celle-ci n’a pas de sens ou n’a pas ete specifiee. C’est pourquoi on ne parlera ici que de fonctions.

C’est dans la syntaxe et la semantique de la definition et de l’appel des fonctions que resident les principalesdifferences entre le C original et le C ANSI. Nous expliquons principalement le C ANSI, rassemblant dans unesection specifique (cf. section 4.2) la maniere de faire du C original.

4.1 Syntaxe ANSI ou “avec prototype”

4.1.1 Definition

Une fonction se definit par la construction :

typeopt ident ( declaration-un-ident , ... declaration-un-ident )instruction-bloc

Notez qu’il n’y a pas de point-virgule derriere le “)” de la premiere ligne. La presence d’un point-virgulea cet endroit provoquerait des erreurs bien bizarres, car la definition serait prise pour une declaration (cf.section 4.1.4).

La syntaxe indiquee ici est incomplete ; elle ne convient qu’aux fonctions dont le type est defini simplement,par un identificateur. On verra ulterieurement (cf. section 5.4.1) la maniere de declarer des fonctions rendantun resultat d’un type plus complexe.

La premiere ligne de la definition d’une fonction s’appelle l’en-tete, ou parfois le prototype, de la fonction.Chaque formule declaration-un-ident possede la meme syntaxe qu’une declaration de variable22.

Exemple.

int extract(char *dest, char *srce, int combien) {/* copie dans dest les combien premiers caracteres de srce *//* renvoie le nombre de caracteres effectivement copies */

int compteur;for (compteur = 0; compteur < combien && *srce != ’\0’; compteur++)

*dest++ = *srce++;*dest = ’\0’;return compteur;

}

Contrairement a d’autres langages, en C on ne peut pas definir une fonction a l’interieur d’une autre : toutesles fonctions sont au meme niveau, c’est-a-dire globales. C’est le cas notamment de main, qui est une fonctioncomme les autres ayant pour seule particularite un nom convenu.

4.1.2 Type de la fonction et des arguments

L’en-tete de la fonction definit le type des objets qu’elle renvoie. Ce peut etre :– tout type numerique ;– tout type pointeur ;– tout type struct ou union.

Si le type de la fonction n’est pas indique, le compilateur suppose qu’il s’agit d’une fonction a resultatentier. Ainsi, l’exemple precedent aurait aussi pu etre ecrit de maniere equivalente (mais cette pratique n’estpas conseillee) :

21La notion de sous-programme (comme les procedures de Pascal, les subroutines de Fortran, etc.) est supposee ici connue dulecteur.

22Restriction : on n’a pas le droit de � mettre en facteur � un type commun a plusieurs arguments. Ainsi, l’en-tete de la fonctionextract donnee en exemple ne peut pas s’ecrire sous la forme char *extract(char *dest, *srce, int n).

c© H. Garreta, 2003 41

Page 42: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4.1 Syntaxe ANSI ou “avec prototype” 4 FONCTIONS

extract(char *dest, char *srce, int combien)etc.

Fonctions sans resultat. Lorsqu’une fonction ne renvoie pas une valeur, c’est-a-dire lorsqu’elle corres-pond plus a une procedure qu’a une vraie fonction, il est prudent de la declarer comme rendant un objet de typevoidvoid. Ce type est garanti incompatible avec tous les autres types : une tentative d’utilisation du resultatde la fonction provoquera donc une erreur a la compilation. Le programmeur se trouve ainsi a l’abri d’uneutilisation intempestive du resultat de la fonction. Exemple :

void extract(char *dest, char *srce, int combien) {/* copie dans dest les combien premiers caracteres de srce *//* maintenant cette fonction ne renvoie rien */

int compteur;for (compteur = 0; compteur < combien && *srce != ’\0’; compteur++)

*dest++ = *srce++;*dest = ’\0’;

}

Fonctions sans arguments. Lorsqu’une fonction n’a pas d’arguments, sa definition prend la forme

typeopt ident ( void )instruction-bloc

On notera que, sauf cas exceptionnel, on ne doit pas ecrire une paire de parentheses vide dans la declaration oula definition d’une fonction. En effet, un en-tete de la forme

type ident()

ne signifie pas que la fonction n’a pas d’arguments, mais (cf. section 4.2.2) que le programmeur ne souhaite pasque ses appels soient controles par le compilateur.

4.1.3 Appel des fonctions

L’appel d’une fonction se fait en ecrivant son nom, suivi d’une paire de parentheses contenant eventuellementune liste d’arguments effectifs.

Notez bien que les parentheses doivent toujours apparaıtre, meme si la liste d’arguments est vide : si unnom de fonction apparaıt dans une expression sans les parentheses, alors il a la valeur d’une constante adresse(l’adresse de la fonction) et aucun appel de la fonction n’est effectue.

Passage des arguments. En C, le passage des arguments se fait par valeur. Cela signifie que les argumentsformels23 de la fonction representent d’authentiques variables locales initialisees, lors de l’appel de la fonction,par les valeurs des arguments effectifs24 correspondants.

Supposons qu’une fonction ait ete declaree ainsi

type fonction ( type1 arg formel1 , ... typek arg formelk )etc.

alors, lors d’un appel tel que

fonction ( arg effectif 1 , ... arg effectif k )

la transmission des valeurs des arguments se fait comme si on executait les affectations :

arg formel1 = arg effectif 1

...arg formelk = arg effectif k

Cela a des consequences tres importantes :– les erreurs dans le nombre des arguments effectifs sont detectees et signalees,– si le type d’un argument effectif n’est pas compatible avec celui de l’argument formel correspondant, une

erreur est signalee,– les valeurs des arguments effectifs subissent les conversions necessaires avant d’etre rangees dans les argu-

ments formels correspondants (exactement les memes conversions qui sont faites lors des affectations).

23Les arguments formels d’une fonction sont les identificateurs qui apparaissent dans la definition de la fonction, declares al’interieur de la paire de parentheses.

24Les arguments effectifs d’un appel de fonction sont les expressions qui apparaissent dans l’expression d’appel, ecrites a l’interieurde la paire de parentheses caracteristiques de l’appel.

42 c© H. Garreta, 2003

Page 43: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4 FONCTIONS 4.2 Syntaxe originale ou “sans prototype”

Remarque. Le langage ne specifie pas l’ordre chronologique des evaluations des arguments effectifs d’unappel de fonction. Ces derniers doivent donc etre sans effets de bord les uns sur les autres. Par exemple, il estimpossible de prevoir quelles sont les valeurs effectivement passees a une fonction lors de l’appel :

x = une_fonction(t[i++], t[i++]); // ERREUR !!!

Si i0 est la valeur de i juste avant l’execution de l’instruction ci-dessus, alors cet appel peut aussi biense traduire par une fonction(t[i0], t[i0 + 1]) que par une fonction(t[i0 + 1], t[i0]) ou meme parune fonction(t[i0], t[i0]). Ce n’est surement pas indifferent !

Appel d’une fonction inconnue. En C une fonction peut etre appelee alors qu’elle n’a pas ete definie(sous-entendu : entre le debut du fichier et l’endroit ou l’appel figure). Le compilateur suppose alors

– que la fonction renvoie un int,– que le nombre et les types des arguments formels de la fonction sont ceux qui correspondent aux arguments

effectifs de l’appel (ce qui, en particulier, empeche les controles et conversions mentionnes plus haut).

De plus, ces hypotheses sur la fonction constituent pour le compilateur une premiere declaration de lafonction. Toute definition ou declaration ulterieure tant soit peu differente sera qualifiee de � redeclarationillegale �25.

Lorsque les hypotheses ci-dessus ne sont pas justes, en particulier lorsque la fonction ne renvoie pas un int,il faut :

– soit ecrire la definition de la fonction appelee avant celle de la fonction appelante,– soit ecrire, avant l’appel de la fonction, une declaration externe de cette derniere, comme explique a la

section 4.1.4.

4.1.4 Declaration “externe” d’une fonction

Une declaration externe d’une fonction est une declaration qui n’est pas en meme temps une definition. On� annonce � l’existence de la fonction (definie plus loin, ou dans un autre fichier) tout en precisant le type de sonresultat et le nombre et les types de ses arguments, mais on ne donne pas son corps, c’est-a-dire les instructionsqui la composent.

Cela se fait en ecrivant– soit un en-tete identique a celui qui figure dans la definition de la fonction (avec les noms des arguments

formels), suivi d’un point-virgule ;– soit la formule obtenue a partir de l’en-tete precedent, en y supprimant les noms des arguments formels.

Par exemple, si une fonction a ete definie ainsi

void truc(char dest[80], char *srce, unsigned long n, float x) {corps de la fonction

}

alors des declarations externes correctes sont :

extern void truc(char dest[80], char *srce, unsigned long n, float x);

ou

ou extern void machin(char [80], char *, unsigned long, float);

Ces expressions sont appelees des prototypes de la fonction. Le mot extern est facultatif, mais le point-virgule est essentiel. Dans la premiere forme, les noms des arguments formels sont des identificateurs sansaucune portee.

4.2 Syntaxe originale ou “sans prototype”

4.2.1 Declaration et definition

Definition. En syntaxe originale la definition d’une fonction prend la forme

typeopt ident ( ident , ... ident )declaration ... declarationinstruction-bloc

25C’est une erreur surprenante, car le programmeur, oubliant que l’appel de la fonction a entraıne une declaration implicite,concoit sa definition ou declaration ulterieure comme etant la premiere declaration de la fonction.

c© H. Garreta, 2003 43

Page 44: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4.2 Syntaxe originale ou “sans prototype” 4 FONCTIONS

Les parentheses de l’en-tete ne contiennent ici que la liste des noms des arguments formels. Les declarationsde ces arguments se trouvent immediatement apres l’en-tete, avant l’accolade qui commence le corps de lafonction.

Exemple :

int extract(dest, srce, combien)/* copie dans dest les combien premiers caracteres de srce *//* renvoie le nombre de caracteres effectivement copies */

char *dest, char *srce;int combien;

{int compteur;for (compteur = 0; compteur < combien && *srce != ’\0’; compteur++)

*dest++ = *srce++;*dest = ’\0’;return compteur;

}

Declaration externe. En syntaxe originale, la declaration externe de la fonction precedente prend unedes formes suivantes :

extern int extract();

ou

int extract();

Comme on le voit, ni les types, ni meme les noms, des arguments formels n’apparaissent dans une declarationexterne26.

4.2.2 Appel

En syntaxe originale, lors d’un appel de la fonction le compilateur ne tient aucun compte ni du nombre nides types des arguments formels27, indiques lors de la definition de la fonction. Chaque argument est evalueseparement, puis

– les valeurs des arguments effectifs de type char ou short sont converties dans le type int ;– les valeurs des arguments effectifs de type float sont converties dans le type double ;– les valeurs des arguments effectifs d’autres types sont laissees telles quelles.

Juste avant le transfert du controle a la fonction, ces valeurs sont copiees dans les arguments formels cor-respondants. Ou plus exactement, dans ce que la fonction appelante croit etre les emplacements des argumentsformels de la fonction appelee : il n’y a aucune verification de la concordance, ni en nombre ni en type, desarguments formels et des arguments effectifs. Cela est une source d’erreurs tres importante. Par exemple, lafonction sqr ayant ete ainsi definie

double sqr(x)double x;

{return x * x;

}

l’appel

x = sqr(2);

est errone, car la fonction appelante depose la valeur entiere 2 a l’adresse du premier argument formel (qu’elle� croit � entier). Or la fonction appelee croit recevoir le codage d’un nombre flottant double. Le resultat n’aaucun sens. Des appels corrects auraient ete

x = sqr((double) 2);

ou

x = sqr(2.0);

26Par consequent, les declarations externes montrees ici sont sans aucune utilite, puisque int est le type suppose des fonctionsnon declarees.

27Mais oui, vous avez bien lu ! C’est la la principale caracteristique de la semantique d’appel des fonctions du C original.

44 c© H. Garreta, 2003

Page 45: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4 FONCTIONS 4.3 Arguments des fonctions

4.2.3 Coexistence des deux syntaxes

A cote de ce que nous avons appele la syntaxe ANSI, la syntaxe originale pour la definition et la declarationexterne des fonctions fait partie elle aussi du C ANSI ; de nos jours les programmeurs ont donc le choix entrel’une ou l’autre forme. Lorsque la fonction a ete definie ou declaree sous la syntaxe originale, les appels defonction sont faits sans verification ni conversion des arguments effectifs. Au contraire, lorsque la fonction a etespecifiee sous la syntaxe ANSI, ces controles et conversions sont effectues.

A cause de cette coexistence, si une fonction n’a pas d’argument, la definition de son en-tete en C ANSI doits’ecrire sous la forme bien peu heureuse :

typeopt ident ( void )

car une paire de parentheses sans rien dedans signifierait non pas que la fonction n’a pas d’arguments, maisqu’elle est declaree sans prototype, c’est-a-dire que ses appels doivent etre traites sans controle des arguments.Exemples :

int getchar(void); // une fonction sans arguments dont les appels seront controles

double moyenne(); // une fonction avec ou sans arguments, aux appels incontroles

4.3 Arguments des fonctions

4.3.1 Passage des arguments

L’idee maıtresse est qu’en C le passage des arguments des fonctions se fait toujours par valeur. Apres avoir faitles conversions opportunes la valeur de chaque argument effectif est affectee a l’argument formel correspondant.

Si l’argument effectif est d’un type simple (nombre, pointeur) ou d’un type struct ou union, sa valeur estrecopiee dans l’argument formel correspondant, quelle que soit sa taille, c’est-a-dire quel que soit le nombred’octets qu’il faut recopier.

4.3.2 Arguments de type tableau

Apparaissant dans la partie executable d’un programme, le nom d’un tableau, et plus generalement touteexpression declaree � tableau de T �, est consideree comme ayant pour type � adresse d’un T � et pour valeurl’adresse du premier element du tableau. Cela ne fait pas intervenir la taille effective du tableau28. Ainsi lorsqu’unargument effectif est un tableau, l’objet effectivement passe a la fonction est uniquement l’adresse du premierelement et il suffit que l’espace reserve dans la fonction pour un tel argument ait la taille, fixe, d’un pointeur.D’autre part, en C il n’est jamais verifie que les indices des tableaux appartiennent bien a l’intervalle 0...N − 1determine par le nombre d’elements indique dans la declaration. Il en decoule la propriete suivante :

Dans la declaration d’un argument formel t de type � tableau de T � :– l’indication du nombre d’elements de t est sans utilite29 ;– les formules � type t[ ] � et � type *t � sont tout a fait equivalentes ;– la fonction pourra etre appelee indifferemment avec un tableau ou un pointeur pour argument effectif.

Exemple. L’en-tete � vague � de la fonction suivante

int strlen(chaıne de caracteres s) {int i = 0;while (s[i] != 0)

i++;return i;

}

peut indifferemment etre concretisee de l’une des trois manieres suivantes :

int strlen(char s[80])

int strlen(char s[])

int strlen(char *s)

28Il n’en est pas de meme lors de la definition, ou la taille du tableau ne peut pas etre ignoree (elle est indispensable pourl’allocation de l’espace memoire).

29Attention, cette question se complique notablement dans le cas des tableaux multidimensionnels. Cf. 6.2.3

c© H. Garreta, 2003 45

Page 46: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4.3 Arguments des fonctions 4 FONCTIONS

Dans les trois cas, si t est un tableau de char et p l’adresse d’un char, les trois appels suivants sont corrects :

l1 = strlen(t);

l2 = strlen(p);

l3 = strlen("Bonjour");

A retenir : parce que les arguments effectifs sont passes par valeur, les tableaux sont passes par adresse.

Passage par valeur des tableaux. Cependant, puisque les structures sont passees par valeur, ellesfournissent un moyen pour obtenir le passage par valeur d’un tableau, bien que les occasions ou cela est necessairesemblent assez rares. Il suffit de declarer le tableau comme le champ unique d’une structure � enveloppe � neservant qu’a cela. Exemple :

struct enveloppe {int t[100];

};void possible_modification(struct enveloppe x) {

x.t[50] = 2;}main() {

struct enveloppe x;x.t[50] = 1;possible_modification(x);printf("%d\n", x.t[50]);

}

La valeur affichee est 1, ce qui prouve que la fonction appelee n’a modifie qu’une copie locale du tableauenveloppe, c’est-a-dire que celui-ci a bien ete passe par valeur (les structures seront vues a la section 5.2.1).

4.3.3 Arguments par adresse

Les arguments qui sont d’un type simple (char, int, pointeur...) ou bien des struc ou des union sonttoujours passes par valeur. Lorsque l’argument effectif est une variable, ce mecanisme empeche qu’elle puisseetre modifiee par la fonction appelee. Or une telle modification est parfois souhaitable ; elle requiert que lafonction puisse acceder non seulement a la valeur de la variable, mais aussi a son adresse.

C’est ce qui s’appelle le passage par adresse des arguments. Alors que certains langages le prennent encharge, C ne fournit aucun mecanisme specifique : le passage de l’adresse de l’argument doit etre programmeexplicitement en utilisant comme argument un pointeur vers la variable.

Par exemple, supposons avoir a ecrire une fonction

int quotient(a, b)

censee renvoyer le quotient de la division euclidienne de l’entier a par l’entier b et devant en plus remplacer apar le reste de cette division. On devra la definir :

int quotient(int *a, int b) {int r = *a / b;*a = *a % b;return r;

}

Ci-dessus, b represente un entier ; a l’adresse d’un entier. La notation *a fait reference a une variable (ou,plus generalement, une lvalue) appartenant a la procedure appelante.

L’argument effectif correspondant a un tel argument formel doit etre une adresse. Souvent, cela est obtenupar l’utilisation de l’operateur &. Par exemple, si x, y et z sont des variables entieres, un appel correct de lafonction precedente sera

z = quotient(&x, y);

Bien entendu, l’operateur & ne doit pas etre utilise si l’argument effectif est deja une adresse. Le schemasuivant resume les principales situations possibles. Soient les fonctions

46 c© H. Garreta, 2003

Page 47: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4 FONCTIONS 4.3 Arguments des fonctions

f(int a) {...

}g(int *b) {

...}h(int c, int *d) {

...}

A l’interieur de la fonction h, des appels corrects de f et g sont :f(c);f(*d);g(&c);g(d);

cette derniere expression correspondant, bien sur, a g(&*d).

4.3.4 Arguments en nombre variable

Le langage C permet de definir des fonctions dont le nombre d’arguments n’est pas fixe et peut varier d’unappel a un autre. Nous exposons ici la maniere dont cette facilite est realisee dans le C ANSI. Dans le Coriginal elle existe aussi ; elle consiste a utiliser, sans garde-fou, une faiblesse du langage (la non-verification dunombre d’arguments effectifs) et une caracteristique du systeme sous-jacent (le sens d’empilement des argumentseffectifs). Mais tout cela est suffisamment casse-cou pour que nous preferions n’expliquer que la maniere ANSIde faire, un peu plus fiable.

Une fonction avec un nombre variable d’arguments est declaree en explicitant quelques arguments fixes, aumoins un, suivis d’une virgule et de trois points : � , ... � . Exemple :

int max(short n, int x0, ...)

Une telle fonction peut etre appelee avec un nombre quelconque d’arguments effectifs, mais il faut qu’il y enait au moins autant qu’il y a d’arguments formels nommes, ici deux. Exemple d’appel :

m = max(3, p, q, r);

Pour les arguments nommes, l’affectation des valeurs des arguments effectifs aux arguments formels est faitecomme d’ordinaire. Pour les arguments anonymes elle s’effectue comme pour une fonction sans prototype : leschar et les short sont convertis en int, les float en double et il n’y a aucune autre conversion.

A l’interieur de la fonction on accede aux arguments anonymes au moyen des macros va start et va argdefinies dans le fichier stdarg.h, comme le montre l’exemple suivant :

#include <stdarg.h>

int max(short n, int x1, ...) {va_list magik;int x, i, m;

m = x1;va_start(magik, x1);for (i = 2; i <= n; i++)

if ((x = va_arg(magik, int)) > m)m = x;

return m;}

Des appels corrects de cette fonction seraient :a = max(3, p, q, r);b = max(8, x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]);c = max(2, u, 0);

Les elements definis dans le fichier stdarg.h sont :va list pointeur Declaration de la variable pointeur, qui sera automatiquement geree par le dispositif d’acces

aux arguments. Le programmeur choisit le nom de cette variable, mais il n’en fait rien d’autre que lamettre comme argument dans les appels des macros va start et va arg.

c© H. Garreta, 2003 47

Page 48: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

4.3 Arguments des fonctions 4 FONCTIONS

va start(pointeur , dernier argument) Initialisation de la variable pointeur ; dernier argument doit etre le der-nier des arguments explicitement nommes dans l’en-tete de la fonction.

va arg(pointeur, type) Parcours des arguments anonymes : le premier appel de cette macro donne le premierargument anonyme ; chaque appel suivant donne l’argument suivant le dernier deja obtenu. Chaque fois,type doit decrire le type de l’argument qui est en train d’etre reference.

Comme l’exemple le montre, le principe des fonctions avec un nombre variable d’arguments est de declarereffectivement au moins un argument, dont la fonction peut deduire l’adresse des autres. Il faut aussi choisir lemoyen d’indiquer a la fonction le nombre de valeurs reellement fournies. Dans l’exemple ci-dessus ce nombre estpasse comme premier argument ; une autre technique consiste a utiliser une valeur � intruse � (ex : le pointeurNULL parmi des pointeurs valides). La fonction printf est un autre exemple de fonction avec un nombre variabled’arguments. Elle est declaree (dans le fichier stdio.h) :

int printf(char *, ...);

Ici encore, le premier argument (le format) renseigne sur le nombre et la nature des autres arguments.

La question des fonctions formelles (fonctions arguments d’autres fonctions) et d’autres remarques sur lesfonctions et leurs adresses sont traitees a la section 6.3.1.

48 c© H. Garreta, 2003

Page 49: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES

5 Objets structures

5.1 Tableaux

5.1.1 Cas general

Dans le cas le plus simple la declaration d’un tableau se fait par une formule comme :

type-de-base nom [ expressionopt ] ;

Exemple :

unsigned long table[10];

L’expression optionnelle qui figure entre les crochets specifie le nombre d’elements du tableau. Le premierelement possede toujours l’indice 0. Par consequent, un tableau t declare de taille N possede les elementst0, t1, ... tN−1. Ainsi la definition precedente alloue un tableau de 10 elements, nommes respectivement table[0],table[1], ... table[9].

L’expression optionnelle qui figure entre les crochets doit etre de type entier et constante au sens de lasection 1.3.4, c’est-a-dire une expression dont tous les elements sont connus au moment de la compilation. Decette maniere le compilateur peut l’evaluer et connaıtre la quantite d’espace necessaire pour loger le tableau. Elleest obligatoire lorsqu’il s’agit d’une definition, car il y a alors allocation effective du tableau. Elle est facultativedans le cas des declarations qui ne sont pas des definitions, c’est-a-dire :

– lors de la declaration d’un tableau qui est un argument formel d’une fonction ;– lors de la declaration d’un tableau externe (defini dans un autre fichier).

Semantique du nom d’un tableau. En general lorsque le nom d’un tableau apparaıt dans une expressionil y joue le meme role qu’une constante de type adresse ayant pour valeur l’adresse du premier element dutableau. Autrement dit, si t est de type tableau, les deux expressions

t &t[0]

sont equivalentes.

Les exceptions a cette regle, c’est-a-dire les expressions de type tableau qui ne sont pas equivalentes al’adresse du premier element du tableau, sont

– l’occurrence du nom du tableau dans sa propre declaration ;– l’apparition d’un nom du tableau comme argument de sizeof.

Ce sont les seules occasions ou le compilateur veut bien se souvenir qu’un nom de tableau represente aussi unespace memoire possedant une certaine taille.

De tout cela, il faut retenir en tout cas que le nom d’un tableau n’est pas une lvalue et donc qu’il n’estpas possible d’affecter un tableau a un autre. Si a et b sont deux tableaux, meme ayant des types identiques,l’affectation b = a sera

– comprise comme l’affectation d’une adresse a une autre, et– rejetee, car b n’est pas modifiable.

Tableaux multidimensionnels. C ne prevoit que les tableaux a un seul indice, mais les elements destableaux peuvent a leur tour etre des tableaux. La declaration d’un tableau avec un nombre quelconque d’indicessuit la syntaxe :

type nom [ expressionopt ] [ expressionopt ] ... [ expressionopt ]

qui est un cas particulier de formules plus generales expliquees a la section 5.4.1. Par exemple, la declaration

double matrice[10][20];

introduit matrice comme etant le nom d’un tableau de 10 elements30 qui, chacun a son tour, est un tableau de20 elements de type double.

5.1.2 Initialisation des tableaux

Une variable de type tableau peut etre initialisee lors de sa declaration. Cela se fait par une expression ayantla syntaxe :

{ expression , expression , ... expression }30Dans le cas d’une matrice, la coutume est d’appeler ces elements les lignes de la matrice. Cela permet de dire alors : � en C les

matrices sont rangees par lignes �.

c© H. Garreta, 2003 49

Page 50: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5.1 Tableaux 5 OBJETS STRUCTURES

Exemple :

int t[5] = { 10, 20, 30, 40, 50 };

Les expressions indiquees doivent etre des expressions constantes au sens de la section 1.3.4. S’il y a moinsd’expressions que le tableau a d’elements, les elements restants sont remplis de zeros (cas des variables globales)ou bien restent indetermines (cas des variables locales). S’il y a plus d’expressions d’initialisation que d’elementsdans le tableau, une erreur est signalee.

D’autre part, la presence d’un initialisateur dispense d’indiquer la taille du tableau. Il est convenu que cedernier doit avoir alors pour taille le nombre effectif de valeurs initiales. Par exemple, la definition

int t[] = { 10, 20, 30, 40, 50 };

alloue un tableau a 5 composantes garni avec les valeurs indiquees.

Lorsque le tableau comporte des sous-tableaux, c’est-a-dire lorsque c’est un tableau a plusieurs indices, laliste des expressions d’initialisation peut comporter des sous-listes indiquees a leur tour avec des accolades, maisce n’est pas une obligation. Le cas echeant, la regle de completion par des zeros s’applique aussi aux sous-listes.Par exemple, les declarations

int t1[4][4] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };int t2[4][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

allouent et garnissent les deux tableaux de la figure 5.

t1

1 2 3 0

4 5 6 0

7 8 9 0

0 0 0 0

t2

1 2 3 4

5 6 7 8

9 0 0 0

0 0 0 0

Fig. 5 – Tableaux initialises

Remarque. Rappelons que ces representations rectangulaires sont tres conventionnelles. Dans la memoirede l’ordinateur ces deux tableaux sont plutot arranges comme sur la figure .

t1

1 2 3 0 4 5 6 0 7 8 9 0 0 0 0 0

t2

1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 0

Fig. 6 – Tableaux � a plat �

Si la taille d’une composante est un diviseur de la taille d’un entier, les tableaux sont � tasses � : chaquecomposante occupe le moins de place possible. En particulier, dans un tableau de char (resp. de short), chaquecomposante occupe un (resp. deux) octet(s).

5.1.3 Chaınes de caracteres

Les chaınes de caracteres sont representees comme des tableaux de caracteres. Un caractere nul suit le derniercaractere utile de la chaıne et en indique la fin. Cette convention est suivie par le compilateur (qui range leschaınes constantes en leur ajoutant un caractere nul), ainsi que par les fonctions de la librairie standard quiconstruisent des chaınes. Elle est supposee verifiee par les fonctions de la librairie standard qui exploitent deschaınes. Par consequent, avant de passer une chaıne a une telle fonction, il faut s’assurer qu’elle comporte bienun caractere nul a la fin.

50 c© H. Garreta, 2003

Page 51: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES 5.2 Structures et unions

Donnons un exemple classique de traitement de chaınes : la fonction strlen (extraite de la bibliothequestandard) qui calcule le nombre de caracteres utiles d’une chaıne :

int strlen(char s[]) {register int i = 0;while (s[i++] != ’\0’)

;return i - 1;

}Une constante-chaıne de caracteres apparaissant dans une expression a le meme type qu’un tableau de

caracteres c’est-a-dire, pour l’essentiel, le type � adresse d’un caractere �. Par exemple, si p a ete declarechar *p;

l’affectation suivante est tout a fait legitime :p = "Bonjour. Comment allez-vous?";

Elle affecte a p l’adresse de la zone de la memoire ou le compilateur a range le texte en question (l’adresse du ’B’de "Bonjour..."). Dans ce cas, bien que l’expression *p soit une lvalue (ce qui est une propriete syntaxique),elle ne doit pas etre consideree comme telle car *p represente un objet memorise dans la zone des constantes,et toute occurrence de *p comme operande gauche d’une affectation :

*p = expression;constituerait une tentative de modification d’un objet constant. Cette erreur est impossible a deceler a lacompilation. Selon le systeme sous-jacent, soit une erreur sera signalee a l’execution, provoquant l’abandonimmediat du programme, soit aucune erreur ne sera signalee mais la validite de la suite du traitement seracompromise.

Initialisation. Comme tous les tableaux, les variables-chaınes de caracteres peuvent etre initialisees lorsde leur declaration. De ce qui a ete dit pour les tableaux en general il decoule que les expressions suivantes sontcorrectes :

char message1[80] = { ’S’, ’a’, ’l’, ’u’, ’t’, ’\0’ };char message2[] = { ’S’, ’a’, ’l’, ’u’, ’t’, ’\0’ };

(la deuxieme formule definit un tableau de taille 6). Heureusement, C offre un raccourci : les deux expressionsprecedentes peuvent s’ecrire de maniere tout a fait equivalente :

char message1[80] = "Salut";char message2[] = "Salut";

Il faut bien noter cependant que l’expression "Salut" qui apparaıt dans ces deux exemples n’est pas du touttraite par le compilateur comme les autres chaınes de caracteres constantes rencontrees dans les programmes.Ici, il ne s’agit que d’un moyen commode pour indiquer une collection de valeurs initiales a ranger dans untableau. A ce propos, voir aussi la remarque 2 de la section 6.2.1.

5.2 Structures et unions

5.2.1 Structures

Les structures sont des variables composees de champs de types differents. Elles se declarent sous la syntaxesuivante :

struct nomopt

{’{’ declaration ... declaration ’}’

rien

}{nom ’,’ ... nom

rien

}’ ;’

A la suite du mot-cle struct on indique :– facultativement, le nom que l’on souhaite donner a la structure. Par la suite, l’expression � struct nom �

pourra etre employee comme une sorte de nom de type ;– facultativement, entre accolades, la liste des declarations des champs de la structure. Chaque champ peut

avoir un type quelconque (y compris struct, bien sur) ;– facultativement, la liste des variables que l’on definit ou declare comme possedant la structure ici definie.

Exemple.struct fiche {

int numero;char nom[32], prenom[32];

} a, b, c;

c© H. Garreta, 2003 51

Page 52: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5.2 Structures et unions 5 OBJETS STRUCTURES

Cette declaration introduit trois variables a, b et c, chacune constituee des trois champs numero, nom etprenom ; en meme temps elle donne a cette structure le nom fiche. Plus loin, on pourra declarer de nouvellesvariables analogues a a, b et c en ecrivant simplement

struct fiche x, y;

(remarquez qu’il faut ecrire � struct fiche � et non pas � fiche �). Nous aurions pu aussi bien faire lesdeclarations :

struct fiche {int numero;char nom[32], prenom[32];

};struct fiche a, b, c, x, y;

ou encore

struct {int numero;char nom[32], prenom[32];

} a, b, c, x, y;

mais cette derniere forme nous aurait empeche par la suite de declarer aussi facilement d’autres variables dememe type.

Comme dans beaucoup de langages, on accede aux champs d’une variable de type structure au moyen del’operateur � . �. Exemple :

a.numero = 1234;

Les structures supportent les manipulations � globales �31 :– on peut affecter une expression d’un type structure a une variable de type structure (pourvu que ces deux

types soient identiques) ;– les structures sont passees par valeur lors de l’appel des fonctions ;– le resultat d’une fonction peut etre une structure.

La possibilite pour une fonction de rendre une structure comme resultat est officielle dans le C ANSI ; surce point les compilateurs plus anciens presentent des differences de comportement. D’autre part il est facile devoir que -sauf dans le cas de structures vraiment tres simples- les transmettre comme resultats des fonctions esten general peu efficace.

Voici quelques informations sur la disposition des champs des structures. Elles sont sans importance lorsqueles structures sont exploitees par le programme qui les cree, mais deviennent indispensables lorsque les structuressont partagees entre sous-programmes ecrits dans divers langages ou lorsqu’elles servent a decrire les articlesd’un fichier existant en dehors du programme.

Position. C garantit que les champs d’une structure sont alloues dans l’ordre ou ils apparaissent dans ladeclaration. Ainsi, avec la definition

struct modele {type1 a, b;type2 c, d, e;type3 f;

}

on trouve, dans le sens des adresses croissantes : x.a, x.b, x.c, x.d, x.e et enfin x.f32.

Contiguıte. Les champs des structures subissent en general des contraintes d’alignement dependant dusysteme sous-jacent, qui engendrent des trous anonymes entre les champs. Il s’agit souvent des memes reglesque celles qui pesent sur les variables simples. Deux cas relativement frequents sont les suivants :

– les champs de type char sont a n’importe quelle adresse, tous les autres champs devant commencer a uneadresse paire ;

– les champs d’un type simple doivent commencer a une adresse multiple de leur taille (les short a uneadresse paire, les long a une adresse multiple de quatre, etc.).

Initialisation. Une variable de type structure peut etre initialisee au moment de sa declaration, du moinsdans le cas d’une variable globale. La syntaxe est la meme que pour un tableau. Exemple :

31L’expression correcte de cette propriete consisterait a dire qu’une structure, comme une expression d’un type primitif, beneficiede la semantique des valeurs, par opposition a un tableaux qui, lui, est assujetti a la semantique des valeurs.

32Alors que, avec la declaration correspondante en Pascal (facile a imaginer), beaucoup de compilateurs alloueraient les champsdans l’ordre : x.b, x.a, x.e, x.d, x.c et enfin x.f.

52 c© H. Garreta, 2003

Page 53: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES 5.2 Structures et unions

struct fiche {int numero;char nom[32], prenom[32];

} u = { 1234, "DURAND", "Pierre" };

Comme pour les tableaux, si on indique moins de valeurs que la structure ne comporte de champs, alors leschamps restants sont initialises par des zeros. Donner plus de valeurs qu’il n’y a de champs constitue une erreur.

5.2.2 Unions

Tandis que les champs des structures se suivent sans se chevaucher, les champs d’une union commencenttous au meme endroit et, donc, se superposent. Ainsi, une variable de type union peut contenir, a des momentsdifferents, des objets de types et de tailles differents. La taille d’une union est celle du plus volumineux de seschamps.

La syntaxe de la declaration et de l’utilisation d’une union est exactement la meme que pour les structures,avec le mot union a la place du mot struct. Exemple (la struct adresse virt est definie a la section 5.2.3) :

union mot_machine {unsigned long mot;char *ptr;struct adresse_virt adrv;

} mix;

La variable mix pourra etre vue tantot comme un entier long, tantot comme l’adresse d’un caractere, tantotcomme la structure bizarre definie ci-apres. Par exemple, le programme suivant teste si les deux bits hauts d’unecertaine variable m, de type unsigned long, sont nuls :

mix.mot = m;if (mix.adrv.tp == 0) etc.

Remarque 1. Dans ce genre de problemes, les operateurs binaires de bits (&, |, ^) s’averent egalement tresutiles. L’instruction precedente pourrait aussi s’ecrire (sur une machine 32 bits, ou int est synonyme de long) :

if (m & 0xC0000000 == 0) etc.(l’ecriture binaire du chiffre hexa C est 1100). Bien sur, les programmes de cette sorte n’etant pas portables, cesdeux manieres de faire peuvent etre equivalentes sur une machine et ne pas l’etre sur une autre.

Remarque 2. Les unions permettent de considerer un meme objet comme possedant plusieurs types ; ellessemblent donc faire double emploi avec l’operateur de conversion de type. Ce n’est pas le cas. D’une part, cetoperateur ne supporte pas les structures, tandis qu’un champ d’une union peut en etre une.

D’autre part, lorsque l’on change le type d’un objet au moyen de l’operateur de changement de type, Cconvertit la donnee, afin que l’expression ait, sous le nouveau type, une valeur vraisemblable et utile. Parexemple, si x est une variable reelle (float), l’expression � (int) x � convertit x en l’entier dont la valeur estla plus voisine de la valeur qu’avait le reel x. Cela implique une transformation, eventuellement assez couteuse,de la valeur de x. A l’oppose, lorsqu’on fait reference a un champ d’une union, la donnee ne subit aucunetransformation, meme si elle a ete affectee en faisant reference a un autre champ. Par exemple, si l’on declare

union {float reel;int entier;

} x;et qu’ensuite on execute les affectations :

x.reel = 12.5;i = x.entier;

l’entier i obtenu sera celui dont la representation binaire coıncide avec la representation binaire du nombreflottant 12.5 ; cet entier n’a aucune raison d’etre voisin de 12.

5.2.3 Champs de bits

Le langage C permet aussi d’utiliser des structures et des unions dont les champs, tous ou seulement certains,sont faits d’un nombre quelconque de bits. De tels champs doivent etre ainsi declares :

{unsignedopt intopt identificateur

rien

}’:’ constante-entiere

c© H. Garreta, 2003 53

Page 54: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5.3 Enumerations 5 OBJETS STRUCTURES

Chaque constante precise le nombre de bits qu’occupe le champ. Le type doit obligatoirement etre entier.Lorsque le type et le nom du champ sont absents, le nombre indique de bits est quand meme reserve : on supposequ’il s’agit de bits de � rembourrage � entre deux champs nommes.

Chacun de ces champs est loge immediatement apres le champ precedent, sauf si cela le met a cheval surdeux mots (de type int) ; dans ce cas, le champ est loge au debut du mot suivant.

Exemple : la structure

struct adresse_virt {unsigned depl : 9;unsigned numpagv : 16;

: 5;unsigned tp : 2;

};

decoupe un mot de 32 bits en en quatre champs, comme indique33 sur la figure 7.

082429

tp deplnumpag

Fig. 7 – Champs de bits

Attention. Dans tous les cas, les champs de bits sont tres dependants de l’implantation. Par exemple,certaines machines rangent les champs de gauche a droite, d’autres de droite a gauche. Ainsi, la portabilitedes structures contenant des champs de bits n’est pas assuree ; cela limite leur usage a une certaine categoriede programmes, comme ceux qui communiquent avec leur machine-hote a un niveau tres bas (noyau d’unsysteme d’exploitation, gestion des peripheriques, etc.). Ces programmes sont rarement destines a etre portesd’un systeme a un autre.

5.3 Enumerations

Les enumerations ne constituent pas un type structure. Si elles figurent ici c’est uniquement parce que lasyntaxe de leur declaration possede des points communs avec celle des structures. Dans les cas simples, cettesyntaxe est la suivante :

enum nomopt

{’{’ id-valeur ’,’ ... id-valeur ’}’

rien

}{nom ’,’ ... nom

rien

}’ ;’

ou chaque id-valeur est a son tour de la forme

identificateur{

’=’ expression-constanterien

}

Exemple :

enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi } x, y;

A la suite d’une telle declaration, le type enum jourouvrable est connu ; les variables x et y le possedent.Ce type est forme d’une famille finie de symboles qui representent des constantes entieres : lundi (egale a 0),mardi (egale a 1), etc. Dans ce programme on pourra ecrire :

enum jourouvrable a, b, c;

On aurait aussi bien pu introduire toutes ces variables par les declarations

enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi };...enum jourouvrable x, y, a, b, c;

ou encore

enum { lundi, mardi, mercredi, jeudi, vendredi } x, y, a, b, c;

33Sur un systeme ou un mot est fait de 32 bits (sinon un champ serait a cheval sur deux mots, ce qui est interdit).

54 c© H. Garreta, 2003

Page 55: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES 5.4 Declarateurs complexes

mais cette derniere forme nous aurait empeche par la suite de declarer aussi facilement d’autres variables dumeme type. Bien sur, les enumerations peuvent se combiner avec la declaration typedef (cf. section 5.4.3) :apres la declaration

typedef enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi } JOUROUVRABLE;

les expressions � enum jourouvrable � et � JOUROUVRABLE � sont equivalentes.

Les nombres entiers et les valeurs de tous les types enumeres sont totalement compatibles entre eux. Parconsequent, l’affectation d’un entier a une variable d’un type enumere ou l’affectation reciproque ne provoquenten principe aucun avertissement de la part du compilateur . Ainsi, en C, les types enumeres ne sont qu’unedeuxieme maniere de donner des noms a des nombres entiers (la premiere maniere etant l’emploi de la directive#define, voir section 8.1.2).

Par defaut, la valeur de chacune de ces constantes est egale au rang de celle-ci dans l’enumeration (lundivaut 0, mardi vaut 1, etc.). On peut alterer cette sequence en associant explicitement des valeurs a certainselements :

enum mois_en_r { janvier = 1, fevrier, mars, avril,septembre = 9, octobre, novembre, decembre };

(fevrier vaut 2, mars vaut 3, octobre vaut 10, etc.)

5.4 Declarateurs complexes

Jusqu’ici nous avons utilise des formes simplifiees des declarations. Tous les objets etaient soit simples, soitdes tableaux, des fonctions ou des adresses d’objets simples. Il nous reste a voir comment declarer des � tableauxde pointeurs � , des � tableaux d’adresses de fonctions �, des � fonctions rendant des adresses de tableaux �, etc.Autrement dit, nous devons expliquer la syntaxe des formules qui permettent de decrire des types de complexitequelconque.

La question concerne au premier chef les descripteurs de types, qui apparaissent dans trois sortes d’enonces :– les definitions de variables (globales et locales), les declarations des parametres formels des fonctions, les

declarations des variables externes et les declarations des champs des structures et des unions ;– les definitions des fonctions et les declarations des fonctions externes ;– les declarations de types (typedef)

La meme question reapparaıt dans des constructions qui decrivent des types sans les appliquer a des identi-ficateurs. Appelons cela des types desincarnes. Ils sont utilises

– par l’operateur de changement de type lorsque le type de destination n’est pas defini par un identificateur :� (char *) expression � ;

– comme argument de sizeof (rare) : � sizeof(int [100]) � ;– dans les prototypes des fonctions : � strcpy(char *, char *) ; �

5.4.1 Cas des declarations

Un declarateur complexe se compose de trois sortes d’ingredients :– un type � terminal � qui est un type de base (donc numerique), enum, struct ou union ou bien un type

auquel on a donne un nom par une declaration typedef ;– un certain nombre d’occurrences des operateurs (), [] et * qui representent des procedes recursifs de

construction de types complexes ;– l’identificateur qu’il s’agit de declarer.

Ces formules ont mauvaise reputation. Si elles sont faciles a lire et a traiter par le compilateur, elles s’averentdifficiles a composer et a lire par le programmeur, car les symboles qui representent les procedes recursifs deconstruction sont peu expressifs et se retrouvent places a l’envers de ce qui aurait semble naturel.

Pour cette raison nous allons donner un procede mecanique qui, a partir d’une sorte de description � al’endroit �, facile a concevoir, fabrique la declaration C souhaitee. Appelons description naturelle d’un type uneexpression de l’un des types suivants :

– la description correcte en C d’un type de base, d’un type enum, d’une struct ou union ou le nom d’untype baptise par typedef ;

– la formule � tableau de T �, ou T est la description naturelle d’un type ;– la formule � adresse d’un T � (ou � pointeur sur un T �), ou T est la description naturelle d’un type ;– la formule � fonction rendant un T �, ou T est la description naturelle d’un type.

c© H. Garreta, 2003 55

Page 56: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5.4 Declarateurs complexes 5 OBJETS STRUCTURES

Dans ces conditions, nous pouvons enoncer la � recette de cuisine � du tableau 3.

Pour obtenir la declaration d’une variable ou d’une fonction :

1. Ecrivez :– a gauche, la description naturelle du type,– a droite, l’identificateur a declarer (un seul).

2. Tant que l’expression a gauche n’est pas un type de base, enum, struct ou union,transformez les deux expressions de la maniere suivante :

(a) fonction rendant un expg expd → expg (expd)()(b) tableau de expg expd → expg (expd)[](c) pointeur sur expg expd → expg *expd

De plus, dans les regles (a) et (b), si expd n’est pas de la forme *exp, alors les parenthesesautour de expd peuvent etre omises.

Tab. 3 – Construction d’un declarateur complexe

Exemple 1. Soit a declarer tabPtr comme un tableau (de dimension indeterminee) de pointeurs d’entiers.Appliquant les regles du tableau 3, nous ecrirons successivement :

tableau de pointeur sur int tabPtrpointeur sur int (tabPtr)[]pointeur sur int tabPtr[]int *tabPtr[]

La partie gauche de la ligne ci-dessus se reduisant a un type simple, cette ligne constitue la declaration Ccherchee, comme il faudra l’ecrire dans un programme : � int *tabPtr[] ; �.

Exemple 2. Pour la comparer a la precedente, cherchons a ecrire maintenant la declaration de ptrTab, unpointeur sur un tableau34 d’entiers :

pointeur sur un tableau de int ptrTabtableau de int *ptrTabint (*ptrTab)[]

Les parentheses ci-dessus ne sont pas facultatives, car elles encadrent une expression qui commence par *(dit autrement, elles sont necessaires, car [] a une priorite superieure a *).

Les declarations pour lesquelles le membre gauche est le meme (a la fin des transformations) peuvent etreregroupees. Ainsi, les declarations de tabPtr et ptrTab peuvent etre ecrites ensemble :

int *tabPtr[], (*ptrTab)[];

En toute rigueur les regles a et b de ce tableau devraient etre plus complexes, car la description naturelledu type d’une fonction inclut en general la specification des types des arguments (le prototype de la fonction),et la description naturelle d’un type tableau comporte l’indication du nombre de composantes de celui-ci.

En realite ces indications s’ajoutent aux regles donnees ici, presque sans modifier le processus que ces reglesetablissent : tout simplement, des informations annexes sont ecrites a l’interieur des crochets des tableaux etdes parentheses des fonctions.

Exemple 3. Soit a declarer une matrice de 10 lignes et 20 colonnes. Nos pouvons ecrire successivement :

tableau de 10 tableau de 20 double matricetableau de 20 double (matrice)[10]tableau de 20 double matrice[10]double (matrice[10])[20]double matrice[10][20]

Exemple 4. Quand on ecrit des programmes relevant du calcul numerique on a souvent besoin de variablesde type � adresse d’une fonction R→ R �. Voici comment ecrire un tel type

34La notion de pointeur sur un tableau est assez academique, puisqu’une variable de type tableau represente deja l’adresse decelui-ci (dans la notion d’� adresse d’un tableau � il y a une double indirection). En pratique on n’en a presque jamais besoin.

56 c© H. Garreta, 2003

Page 57: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES 5.4 Declarateurs complexes

adresse de fonction (avec argument double) rendant double adrFonfonction (avec argument double) rendant double *adrFondouble (*adrFon)(double)

La declaration cherchee est donc � double (*adrFon)(double) �.

Exemple 5. Cet exemple abominable est extrait de la bibliotheque UNIX. La fonction signal renvoiel’adresse d’une � procedure �, c’est-a-dire d’une fonction rendant void. Voici comment il faudrait la declarer ensyntaxe originale, c’est-a-dire sans specifier les arguments de la fonction signal (cette declaration est fourniedans le fichier <signal.h>) :

fonction rendant un pointeur sur fonction rendant void signalpointeur sur fonction rendant un void (signal)()pointeur sur fonction rendant un void signal()fonction rendant un void *signal()void (*signal())()

En syntaxe ANSI il faut en plus donner les types des arguments des fonctions. On nous dit que les argumentsde la fonction signal sont un int et un pointeur sur une fonction prenant un int et rendant un void. En outre,signal rend un pointeur vers une telle fonction. Nous savons deja ecrire le type � pointeur sur une fonctionprenant un int et rendant un void � (ce n’est pas tres different de l’exemple precedent) :

void (*fon)(int);

Attaquons-nous a signal :

fonction (avec arguments int sig et int (*fon)(int))rendant un pointeur sur fonction(avec argument int) rendant void signal

pointeur sur fonction (avec argument int) rendant void(signal)(int sig, int (*fon)(int))

pointeur sur fonction (avec argument int) rendant voidsignal(int sig, int (*fon)(int))

fonction (avec argument int s) rendant void *signal(int sig, int (*fon)(int))void (*signal(int sig, int (*fon)(int)))(int)

Finalement, la declaration cherchee est

void (*signal(int sig, int (*fon)(int)))(int) ;

Comme annonce, les expressions finales obtenues sont assez lourdes a digerer pour un humain. A leur dechargeon peut tout de meme dire qu’une fois ecrites elles se revelent bien utiles puisqu’elles font apparaıtre les typesde leurs elements terminaux comme ils seront employes dans la partie executable des programmes. Ainsi, a lavue de l’expression

float matrice[10][20];

on comprend sans effort supplementaire qu’un element de la matrice s’ecrira matrice[i][j] et que cette formulerepresentera un objet de type float. De meme, l’enonce

void (*signal(sig, func))();

nous fera reconnaıtre immediatement l’expression (*signal(2,f))(n) comme un appel correct de la � procedure �rendue par l’appel de signal.

5.4.2 Pointeurs et tableaux constants et volatils

Les qualifieurs const et volatile peuvent aussi apparaıtre dans les declarateurs complexes. Plutot que derendre encore plus lourdes des explications qui le sont deja beaucoup, contentons-nous de montrer des exemplessignificatifs. Les cas les plus utiles sont les suivants :

1. La formule :

const type *nom

declare nom comme un pointeur vers un objet constant ayant le type indique (la variable pointee, *nom, ne doitpas etre modifiee).

2. La formule

type *const nom

c© H. Garreta, 2003 57

Page 58: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5.4 Declarateurs complexes 5 OBJETS STRUCTURES

declare nom comme un pointeur constant vers un objet ayant le type indique (c’est la variable nom qui ne doitpas etre modifiee).

3. Plus simplement, la formuleconst type nom[expropt]

declare nom comme un tableau d’objets constants35 (nom[i] ne doit pas etre modifie).

Ainsi par exemple les declarationsint e, *pe, *const pce = &e, te[100];const int ec = 0, *pec, *const pcec = &ec, tec[100];

declarent successivement :– e : un entier– pe : un pointeur vers un entier– pce : un pointeur constant vers un entier (qui doit etre obligatoirement initialise, puisqu’il est constant)– te : un tableau de 100 entiers– ec : un entier constant– pec : un pointeur vers un entier constant– pcec : un pointeur constant vers un entier constant– tec : un tableau de 100 entiers constants

Toute affectation avec pour membre gauche pce, ec, *pec, pcec, *pcec ou tec[i] est illegale.

En resume, la seule vraie nouveaute introduite ici est la notion de pointeur constant et la syntaxe desa declaration � type-pointe *const pointeur �. Signalons enfin que toute cette explication reste valable enremplacant le mot const et la notion d’objet constant par le mot volatile et le concept d’ objet volatil (cf.section 1.5.7). On a donc la possibilite de declarer des tableaux d’objets volatils, des pointeurs volatils vers desobjets, volatils ou non, etc.

5.4.3 La declaration typedef

Cette construction est l’homologue de la declaration type du langage Pascal. Elle permet de donner un noma un type dans le but d’alleger par la suite les expressions ou il figure. Syntaxe :

typedef declaration

Pour declarer un nom de type, on fait suivre le mot reserve typedef d’une expression identique a unedeclaration de variable, dans laquelle le role du nom de la variable est joue par le nom du type qu’on veutdefinir. Exemple : les declarations

typedef float MATRICE[10][20];...MATRICE mat;

sont equivalentes afloat mat[10][20];

Voici un autre exemple :typedef struct noeud {

int info;struct noeud *fils_gauche, *fils_droit;

} NOEUD;

Cette expression declare l’identificateur NOEUD comme le nom du type � la structure noeud � . Des variablesa, b, c de ce type pourront par la suite etre declarees aussi bien par l’expression

struct noeud a, b, c;

que parNOEUD a, b, c;

Remarquez l’utilisation differente d’un nom de structure (precede du mot struct) et d’un nom de type. Partradition, les noms des types definis a l’aide de la declaration typedef sont ecrits en majuscules.

L’ensemble des regles de syntaxe qui s’appliquent aux declarations de variables s’appliquent egalement ici.Par exemple, on peut introduire plusieurs noms de types dans un seul enonce typedef. Ainsi, a la suite de

35Notez qu’il n’y a pas besoin d’une notation speciale pour declarer un tableau constant d’objets (variables) puisque la significationordinaire des tableaux est exactement celle-la.

58 c© H. Garreta, 2003

Page 59: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

5 OBJETS STRUCTURES 5.4 Declarateurs complexes

typedef struct noeud {int info;struct noeud *fils_gauche, *fils_droit;

} NOEUD, *ARBRE;

les identificateurs NOEUD et ARBRE sont des synonymes pour � struct noeud � et � struct noeud * �.

Dans la construction des declarateurs complexes, le nom d’un type qui a fait l’objet d’une declarationtypedef peut etre utilise comme type de base. Par exemple :

typedef float LIGNE[20];typedef LIGNE MATRICE[10];

est une autre maniere d’introduire le type MATRICE deja vu. De meme

typedef void PROCEDURE(int);PROCEDURE *signal(int sig, PROCEDURE *func);

est une autre maniere (nettement plus facile a lire, n’est-ce pas ?) de declarer la fonction signal vue precedemment.

5.4.4 Cas des types desincarnes

Tout cela devient encore plus esoterique lorsqu’on doit construire un type sans l’appliquer a un identificateur.Le principe est alors le suivant : construisez la declaration correcte d’un nom quelconque X, puis effacez X.

De telles expressions sont utiles dans trois circonstances :

1. Pour construire un operateur de changement de type. Exemple : la fonction malloc alloue un bloc dememoire de taille donnee, dont elle renvoie l’adresse. Dans les bibliotheques du C original36, cette fonctionest declaree comme rendant l’adresse d’un char, et l’utilisateur doit convertir le resultat de malloc dans letype qui l’interesse. Supposons avoir besoin d’espace pour ranger un tableau de N reels en double precision.Declarations37 :

char *malloc(size_t);...double *p;

Utilisation :

p = (double *) malloc(N * sizeof(double));

Le type desincarne � double * � apparaissant ci-dessus a ete deduit d’une declaration de pointeur, � double*ptr � en effacant le nom declare, ptr.

2. Comme argument de l’operateur sizeof38. Essayons par exemple d’ecrire autrement l’affectation precedente.Declarons un tableau T de N doubles :

double T[N]

Supprimons T, il reste � double [N] �. Par consequent, l’appel de la fonction malloc montre ci-dessus peuts’ecrire aussi :

p = (double *) malloc(sizeof(double [N]));

3. Dans les prototypes des fonctions, en C ANSI. Par exemple, la bibliotheque standard C comporte lafonction qsort, qui implemente une methode de tri rapide d’un tableau (algorithme Quicksort) programme entoute generalite. Les arguments de cette fonction sont le tableau a trier, le nombre de ses elements, la taille dechacun et une fonction qui represente la relation d’ordre utilisee (le � critere de tri �) : elle recoit deux adressesd’elements du tableau et rend un nombre negatif, nul ou positif exprimant la comparaison.

La fonction qsort est donc ainsi declaree dans le fichier stdlib.h :

void qsort(const void*, size_t, size_t, int (*)(const void*, const void*));

Note. Ce n’est pas requis par la syntaxe mais, lorsqu’ils sont bien choisis, les noms des arguments sont uneaide pour le programmeur. Exemple :

void qsort(const void *tableau, size_t nombre, size_t taille,int (*compare)(const void *adr1, const void *adr2));

36Dans la bibliotheque ANSI (fichier stdlib.h) la fonction malloc est declaree comme rendant une valeur de type � void * �.Ce type etant compatible avec tous les autres types pointeur, le probleme expose ici ne se pose pas.

37Le type size t (defini dans <stddef.h>) represente un entier sans signe38Cette utilisation est plus rare. L’operateur sizeof est surtout utile dans le cadre des structures.

c© H. Garreta, 2003 59

Page 60: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS

6 Pointeurs

6.1 Generalites

Un pointeur est une variable dont la valeur est l’adresse d’une cellule de la memoire.

Les premiers langages evolues ne permettaient pas les manipulations de pointeurs, de telles operations etantobligatoirement confinees dans les programmes ecrits en assembleur. Le langage Pascal a introduit les pointeursparmi les types de donnees disponibles dans des langages generalistes. Mais ils y sont presentes comme desentites fort mysterieuses, dont l’utilisateur n’est cense connaıtre ni la structure ni le comportement. Cela n’enfacilite pas le maniement aux programmeurs debutants. Beaucoup des difficultes a comprendre les pointeursproviennent plus du secret qui les entoure que de leur reelle difficulte d’emploi.

Un pointeur n’est pas un concept unique, sans ressemblance avec les autres types de donnees, car une adressen’est en fin de compte rien d’autre qu’un nombre entier (l’indice d’un element du tableau qu’est la memoire del’ordinateur). Un pointeur devrait donc supporter toutes les operations qu’on peut faire subir a un nombre etqui gardent un sens lorsque ce nombre exprime un rang.

Ce qui est tout a fait specifique d’un pointeur p est la possibilite de mentionner la variable dont la valeurde p est l’adresse. Un pointeur correctement initialise est toujours le renvoi a une deuxieme variable, la variablepointee, accessible a partir du contenu de p.

Dans tous les langages ou ils existent, le principal interet des pointeurs reside dans la possibilite de realiserdes structures de donnees recursives (listes et arbres) : des structures dans lesquelles un champ est virtuellementaussi complexe que la structure elle-meme. En plus de cet aspect, en C les pointeurs sont le moyen d’ameliorerl’acces aux elements des tableaux ordinaires, en incorporant au langage des techniques d’adressage indirectcouramment utilisees en assembleur.

6.1.1 Declaration et initialisation des pointeurs

Nous avons etudie a la section precedente la syntaxe des declarations des pointeurs. Dans le cas le plussimple, cette syntaxe est

type *variable

Si par la suite on affecte a une telle variable l’adresse d’un certain objet, on fait reference a ce dernier parl’expression :

*variable

Par exemple, l’expression

char *p;

definit la variable p comme � pointeur vers un char �. Lorsqu’elle aura ete correctement initialisee, elle contiendral’adresse d’un caractere, auquel on pourra faire reference par l’expression

*p

Initialisation des pointeurs. Un pointeur contient en principe une adresse valide. Il existe trois manieressures de donner une telle valeur a un pointeur :

1. Prendre l’adresse d’une variable existant par ailleurs. Exemple, declaration :

type x, *p;

Initialisation :

p = &x;

p contient maintenant une adresse valide, l’adresse de la variable x. Par la suite, les expressions x et *pdesigneront le meme objet (le contenu de x).

2. Provoquer l’allocation d’un nouvel espace, c’est-a-dire allouer dynamiquement une variable anonyme.Exemple, declaration :

type *p;

Initialisation :

p = malloc(sizeof(type));

Apres cette instruction, une variable tout a fait analogue a la variable x de l’exemple precedent existe.Comme dans l’exemple precedent, *p en designe le contenu. La difference est qu’ici elle n’a pas de nom propre :

60 c© H. Garreta, 2003

Page 61: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.1 Generalites

si la valeur de p venait a etre alteree (sans avoir ete auparavant sauvegardee dans un autre pointeur), la variablepointee deviendrait inaccessible et serait definitivement perdue.

3. Obtenir une valeur par des calculs legitimes sur des pointeurs. Par exemple, comme on le verra bientot,avec les declarations

type t[10], *p, *q;

suivies de l’initialisation

p = &t[0]; (ou tout simplement p = t; )

l’expression

q = p + 3;

affecte a q l’adresse du quatrieme element du tableau t, c’est-a-dire &t[3].

Il existe aussi une maniere risquee, en tout cas non portable, de donner une valeur a un pointeur : lui affecterune constante ou une expression numerique, dans le style de :

p = (struct machin *) 0x2A3E;

De telles expressions, indispensables dans certaines fonctions de bas niveau (controle d’organes materiels, etc.),rendent non portables les programmes ou elles figurent. Elles doivent donc etre evitees chaque fois que cela estpossible.

A propos de la taille des pointeurs et celle des entiers. Une expression comme l’affectationci-dessus souleve un autre probleme : existe-t-il une relation entre la taille d’un nombre entier (types int etunsigned int) et celle d’un pointeur ? Le langage ne prescrit rien a ce sujet ; il est donc parfois errone et toujoursdangereux de supposer qu’un entier puisse contenir un pointeur. Cette question peut sembler tres secondaire,l’utilisation de variables entieres pour conserver ou transmettre des pointeurs paraissant bien marginale. Orc’est ce qu’on fait, peut-etre sans s’en rendre compte, lorsqu’on appelle une fonction qui renvoie un pointeursans avoir prealablement declare la fonction. Par exemple, supposons que sans avoir declare la fonction malloc(ni inclus le fichier <stdlib.h>) on ecrive l’affectation

p = (struct machin *) malloc(sizeof(struct machin));

Le compilateur rencontrant malloc pour la premiere fois, il postule que cette fonction renvoie un int et inserea cet endroit le code qui convertit un entier en un pointeur. Cette conversion peut fournir un resultat tout a faiterrone. Notamment, sur un systeme ou les entiers occupent deux octets et les pointeurs quatre, les deux octetshauts du resultat de malloc seront purement et simplement remplaces par zero. La question est d’autant plusvicieuse que le programme ci-dessus se comportera correctement sur tous les systemes dans lesquels les entierset les pointeurs ont la meme taille.

6.1.2 Les pointeurs generiques et le pointeur NULL

Certaines fonctions de la bibliotheque, comme malloc, rendent comme resultat une � adresse quelconque �,a charge pour l’utilisateur de la convertir en l’adresse specifique qui lui convient. La question est : commentdeclarer de tels pointeurs peu types ou pas types du tout ?

Le C original n’ayant rien prevu a ce sujet, la tradition s’etait creee de prendre le type char * pour � pointeurvers n’importe quoi �. En effet, sur beaucoup de systemes les adresses des objets de taille non unitaire supportentdes contraintes d’alignement (les short aux adresse paires, les long aux adresses multiples de quatre, etc.), alorsque les adresses des caracteres sont les seules a ne subir aucune restriction.

Le C ANSI introduit un type specifique pour representer de telles adresses generiques : le type void *. Cetype est compatible avec tout autre type pointeur : n’importe quel pointeur peut etre affecte a une variable detype void * et reciproquement39. Par exemple, le fichier standard <stdlib.h> contient la declaration suivantede la fonction malloc :

void *malloc(size_t size);

Appel :

p = malloc(sizeof(struct machin));

Bien entendu, on peut continuer a utiliser une conversion de type (on peut meme trouver que cela augmentela lisibilite du texte source) :

p = (struct machin *) malloc(sizeof(struct machin));

39Avec une protection supplementaire : le type void etant incompatible avec tous les autres types, l’objet pointe par une variabledeclaree de type void * ne pourra pas etre utilise tant que la variable n’aura pas ete convertie en un type pointeur precis.

c© H. Garreta, 2003 61

Page 62: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.2 Les pointeurs et les tableaux 6 POINTEURS

Le pointeur NULL. Une des principales applications des pointeurs est la mise en œuvre de structuresrecursives de taille variable : listes chaınees de longueur inconnue, arbres de profondeur quelconque, etc. Pourrealiser ces structures il est necessaire de posseder une valeur de pointeur, distincte de toute adresse valide,representant la fin de liste, la structure vide, etc.

C garantit qu’aucun objet valide ne se trouve a l’adresse 0 et l’entier 0 est compatible avec tout type pointeur.Cette valeur peut donc etre utilisee comme la valeur conventionnelle du � pointeur qui ne pointe vers rien � ;c’est l’equivalent en C de la constante nil de Pascal. En pratique on utilise plutot la constante NULL qui estdefinie dans le fichier <stddef.h> :

#define NULL ((void *) 0)

6.2 Les pointeurs et les tableaux

6.2.1 Arithmetique des adresses, indirection et indexation

Arithmetique. Certaines des operations arithmetiques definies sur les nombres gardent un sens lorsquel’un des operandes ou les deux sont des adresses :

– l’addition et la soustraction d’un nombre entier a une adresse ;– la soustraction de deux adresses (de types rigoureusement identiques).

L’idee de base de ces extensions est la suivante : si exp exprime l’adresse d’un certain objet O1 alors exp+1exprime l’adresse de l’objet de meme type que O1 qui se trouverait dans la memoire immediatement apres O1

et exp−1 l’adresse de celui qui se trouverait immediatement avant.

De maniere analogue, si exp1 et exp2 sont les adresses respectives de deux objets O1 et O2 de meme type etcontigus (O2 apres O1), il semble naturel de donner a exp2−exp1 la valeur 1.

L’arithmetique des adresses decoule de ces remarques. D’un point de vue technique, ajouter 1 a un pointeurc’est le considerer momentanement comme un entier et lui ajouter une fois la taille de l’objet pointe. On peutaussi dire cela de la maniere suivante : exp1 et exp2 etant deux expressions de type � adresse d’un objet de typeT � et n une expression entiere, nous avons :

exp1 + n ≡ (T *) ((unsigned long) exp1 + n×sizeof(T))

etexp2 − exp1 ≡

(unsigned long)exp2 − (unsigned long)exp1

sizeof(T)

L’indexation. Il decoule des explications precedentes que l’arithmetique des adresses ne se justifie que sion peut considerer un pointeur p comme l’adresse d’un element t[i0] d’un tableau, eventuellement fictif. Onpeut alors interpreter p + i comme l’adresse de t[i0 + i] et q − p comme l’ecart entre les indices correspondanta ces deux elements. Les pointeurs avec leur arithmetique et l’acces aux elements des tableaux sont donc desconcepts tres voisins. En realite il y a bien plus qu’un simple lien de voisinage entre ces deux concepts, car l’unest la definition de l’autre :

Soit exp une expression quelconque de type adresse et ind une expression entiere. Par definition, l’expression :

*(exp + ind)

se note :exp[ind]

On en deduit que si on a l’une ou l’autre des declarations

type t[100];

ou

type *t;

alors dans un cas comme dans l’autre on peut ecrire indifferemment

t[0] ou *t

ou

t[i] ou *(t + i)

Bien entendu, un programme est plus facile a lire si on montre une certaine constance dans le choix desnotations.

Exemple. Le programme suivant parcourt un tableau en utilisant un pointeur :

62 c© H. Garreta, 2003

Page 63: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.2 Les pointeurs et les tableaux

char s[10] = "Hello";

main() {char *p = s;while (*p != ’\0’)

fputc(*p++, stdout);}

Remarque 1. La similitude entre un tableau et un pointeur est grande, notamment dans la partie executabledes fonctions. Mais il subsiste des differences, surtout dans les declarations. Il ne faut pas oublier que lesdeclarations

int *p, t[100];

introduisent p comme une variable pointeur et t comme une constante pointeur. Ainsi, les expressions p = t etp++ sont legitims, mais t = p et t++ sont illegales, car elles tentent de modifier une � non variable � (un nomde tableau n’est pas une lvalue).

Remarque 2. Dans ce meme esprit, rappelons la difference qu’il peut y avoir entre les deux declarationssuivantes :

char s[] = "Bonsoir";

et

char *t = "Bonsoir";

s est (l’adresse d’) un tableau de caracteres (figure 8)

B n s i ro 0\o

s

Fig. 8 – Chaıne de caracteres � variable �

tandis que t est l’adresse d’un (tableau de) caractere(s) (figure 9)

B n s i ro 0\o

t

n s i ro 0\o A r v ou e

Fig. 9 – Constante chaıne de caracteres

On pourra referencer ces caracteres indifferemment par s[i], *(s + i), t[i] ou *(t + i). Cependant,dans le premier cas le compilateur a alloue un vrai tableau de 8 caracteres, et y a copie la chaıne donnee ; dansle second cas, la chaıne a ete rangee avec les autres constantes litterales et le compilateur n’a alloue qu’unevariable de type pointeur dans laquelle il a range l’adresse de cette constante. Par suite, s n’est pas une variablemais *s, s[i] et *(s + i) en sont, tandis que t est une variable mais *t, t[i] et *(t + i) n’en sont pas40.A ce propos voir aussi la section 5.1.3.

L’arithmetique des adresses permet d’ameliorer l’efficacite de certains traitements de tableaux41, en mettanta profit le fait que l’indirection est plus rapide que l’indexation. Autrement dit, l’acces a une donnee a traversune expression de la forme

*p

est reputee (legerement) plus rapide que l’acces a la meme donnee a travers l’expression

*(q + i) (ou, ce qui est la meme chose, q[i])

Developpons un exemple classique : la fonction strcpy(dest, srce) copie la chaıne de caracteres srcedans l’espace pointe par dest. Premiere forme :

40C’est uniquement d’un point de vue technique que ces expressions ne sont pas des variables car du point de vue de la syntaxeelles sont des lvalue tout a fait legitimes et le compilateur ne nous interdira pas de les faire apparaıtre a gauche d’une affectation.

41Bien sur, il s’agit de petites economies dont le benefice ne se fait sentir que lorsqu’elles s’appliquent a des parties des programmesqui sont vraiment critiques, c’est-a-dire executees de maniere tres frequente.

c© H. Garreta, 2003 63

Page 64: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.2 Les pointeurs et les tableaux 6 POINTEURS

char *strcpy(char dest[], char srce[]) {register int i = 0;while ((dest[i] = srce[i]) != ’\0’)

i++;return dest;

}

Deuxieme forme :

char *strcpy(char *dest, char *srce) {register char *d = dest, *s = srce;while ((*d++ = *s++) != ’\0’)

;return dest;

}

La deuxieme forme est plus efficace, car on y a supprime l’indexation. Pour s’en convaincre, il suffit deconsiderer que la boucle qui figure dans la premiere forme equivaut a :

while ((*(dest + i) = *(srce + i)) != ’\0’)i++;

6.2.2 Tableaux dynamiques

L’equivalence entre les tableaux et les pointeurs permet de realiser des tableaux dynamiques, c’est-a-diredes tableaux dont la taille n’est pas connue au moment de la compilation mais uniquement lors de l’execution,lorsque le tableau commence a exister. Pour cela il suffit de

– remplacer la declaration du tableau par celle d’un pointeur ;– allouer l’espace a l’execution, avant toute utilisation du tableau, par un appel de malloc ;– dans le cas d’un tableau local, liberer l’espace a la fin de l’utilisation.

Pour tout le reste, l’utilisation de ces tableaux dynamiques est identique a celle des tableaux normaux (cequi, pour le programmeur, est une tres bonne nouvelle !). Exemple : la fonction ci-dessous calcule dans untableau la Neme ligne du triangle de Pascal42 et s’en sert dans des calculs auxquels nous ne nous interessonspas ici. On notera qu’une seule ligne differe entre la version a tableau statique et celle avec tableau dynamique.

Programmation avec un tableau ordinaire (statique) :

void Blaise(int N) {int n, p, i;int tab[N_MAX + 1]; /* N_MAX est une constante connue a la compilation */

for (n = 0; n <= N; n++) {tab[0] = tab[n] = 1;for (p = n - 1; p > 0; p--)

tab[p] += + tab[p - 1];/* exploitation de tab ; par exemple, affichage : */

for (i = 0; i <= n; i++)printf("%5d", tab[i]);

printf("\n");}

}

Programmation avec un tableau dynamique :

void Blaise(int N) {int n, p, i;int *tab; /* ici, pas besoin de constante connue a la compilation */

tab = malloc((N + 1) * sizeof(int));if (tab == NULL)

return;

42Rappelons que les coefficients de la neme ligne du triangle de Pascal sont definis recursivement a partir de ceux de la n− 1eme

ligne par : Cpn = Cp−1

n−1 + Cpn−1.

64 c© H. Garreta, 2003

Page 65: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.2 Les pointeurs et les tableaux

for (n = 0; n <= N; n++) {tab[0] = tab[n] = 1;for (p = n - 1; p > 0; p--)

tab[p] += + tab[p - 1];/* exploitation de tab ; par exemple, affichage : */

for (i = 0; i <= n; i++)printf("%5d", tab[i]);

printf("\n");}free(tab); /* a ne pas oublier ( tab est une variable locale) */

}

Remarque. L’emploi de tableaux dynamiques a plusieurs indices est beaucoup plus complique que celuides tableaux a un indice comme ci-dessus, car il faut gerer soi-meme les calculs d’indices, comme le montre lasection suivante.

6.2.3 Tableaux multidimensionnels

Reflexions sur la double indexation. Supposons que nous ayons a ecrire un programme qui opere surdes matrices a NL et NC colonnes. La declaration d’une telle matrice est facile a construire (cf. section 5.4.1) :

tableau de NL tableau de NC float m1tableau de NC float (m1)[NL]tableau de NC float m1[NL]float (m1[NL])[NC]float m1[NL][NC]

Un element particulier de cette matrice sera note m1[i][j]. D’apres ce que nous savons deja, on peutl’interpreter de la maniere suivante43 :

m1[i][j]= *(m1[i] + j)= *(float *)((unsigned) m1[i] + j × sizeof(float))

Le probleme est : avec la declaration que nous avons donnee, que signifie m1[i] ? Il est interessant de constaterque cette expression n’est pas une lvalue, puisqu’elle est de type � tableau de... �, mais que, a part cela, elle esttraitee comme un element d’un tableau ordinaire. Autrement dit, si on suppose avoir fait la declaration

typedef float LIGNE[NC]; /* LIGNE = tableau de NC float */

alors m1[i] est une expression de type LIGNE valant :m1[i]

= *(m1 + i)= *(LIGNE *)((unsigned) m1 + i × sizeof(LIGNE))= *(LIGNE *)((unsigned) m1 + i × NC × sizeof(float))

En oubliant pour un instant les types des pointeurs, il nous restem1[i][j] = *(m1 + (i × NC + j) × sizeof(float))

Nous retrouvons l’expression classique i× NC + j caracterisant l’acces a un element mi,j d’une matrice NL×NC.Cette expression traduit la disposition � par lignes � des tableaux rectangulaires, comme le montre la figure 10.

i x NC j

m1[i][j]

m1

Fig. 10 – Tableau bidimensionnel statique

Etudions une autre maniere d’acceder aux elements de cette meme matrice. Soient les declarations43Pour alleger les formules, nous supposons ici que la taille d’un int, et donc celle d’un unsigned, sont egales a celle d’un pointeur.

c© H. Garreta, 2003 65

Page 66: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.2 Les pointeurs et les tableaux 6 POINTEURS

float m1[NL][NC], *m2[NL];

La deuxieme se lit, compte tenu des priorites : float *(m2[NL]). La variable m2 est donc declaree commeun tableau de NL pointeurs vers des nombres flottants. Pour realiser la matrice dont nous avons besoin, il noussuffit d’initialiser correctement ces pointeurs : chacun sera l’adresse d’une ligne de la matrice precedente. Nousaurons la configuration de la figure 11.

j

m2[i][j]

m2

i

m2[i]

Fig. 11 – Tableau bidimensionnel realise avec un tableau de pointeurs

Il est remarquable qu’un element de la nouvelle � matrice � ainsi declaree se note encore

m2[i][j]

mais maintenant, cette expression se traduira (en continuant a negliger les conversions des types pointeurs) par :

m2[i][j]= *(m2[i] + j × sizeof(float))= *(*(m2 + i × sizeof(float *)) + j × sizeof(float))

Les multiplications par sizeof(float) et sizeof(float *) sont realisees de maniere tres rapide par lescalculateurs (ce sont des multiplications par des puissances de 2), nous pouvons les ignorer. Il reste que nousavons remplace � m1 + i × NC + j � par � *(m2 + i) + j �. Nous avons donc bien elimine une � vraie �multiplication : notre deuxieme maniere est plus efficace. En contrepartie nous occupons un peu plus d’espace(les NL pointeurs supplementaires).

De plus, il nous faut initialiser le tableau de pointeurs m2. Comme notre but est ici d’acceder aux elementsde la matrice statique m1 existant par ailleurs, cela se fait par une expression d’une etonnante simplicite :

for (i = 0; i < NL; i++)m2[i] = m1[i];

6.2.4 Tableaux multidimensionnels dynamiques

Les considerations precedentes montrent pourquoi dans la realisation d’une matrice dynamique on devrarenoncer au mecanisme d’indexation de base. En effet, NC doit etre connu a la compilation pour que m[i][j]ait un sens.

Or nous sommes maintenant parfaitement equipes pour expliquer la maniere de realiser une telle matrice.Supposons que la fonction void unCalcul(int nl, int nc) implemente un algorithme qui requiert une ma-trice locale a nl lignes et nc colonnes.

Version statique :

#define NL 20#define NC 30...

void unCalcul(int nl, int nc) {double mat[NL][NC]; /* en esperant que nl < NL et nc < NC */int i, j;

66 c© H. Garreta, 2003

Page 67: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.2 Les pointeurs et les tableaux

/* utilisation de la matrice mat. Par exemple : */for (i = 0; i < nl; i++)

for (j = 0; j < nc; j++)mat[i][j] = 0;

etc.}

Version dynamique (par souci de clarte nous y avons neglige la detection des echecs de malloc) :void unCalcul(int nl, int nc) {

double **mat;int i, j;

/* initialisation des pointeurs */mat = malloc(nl * sizeof(double *));for (i = 0; i < nl; i++)

mat[i] = malloc(nc * sizeof(double));

/* utilisation de la matrice mat. Par exemple : */for (i = 0; i < nl; i++)

for (j = 0; j < nc; j++)mat[i][j] = 0;

etc.

/* liberation (indispensable dans le cas d’une variablelocale) */for (i = 0; i < nl; i++)

free(mat[i]);free(mat);

}

Dans cette maniere de proceder, les lignes de la matrice sont allouees a l’occasion de nl appels distincts demalloc. La matrice est realisee par des morceaux de memoire eparpillee, comme le montre la figure 12.

m2

Fig. 12 – Matrice dynamique (lignes allouees separement)

Une legere variante consiste a allouer d’un seul coup toute la memoire necessaire aux lignes de la matrice.La structure de la matrice ressemble alors a celle de la figure 11 :

int unCalcul(int nl, int nc) {double **mat, *espace;int i, j;

/* initialisation des pointeurs */mat = malloc(nl * sizeof(double *));if (mat == NULL)

return 0; /* echec */espace = malloc(nl * nc * sizeof(double));if (espace == NULL) {

free(mat);return 0; /* echec */

}for (i = 0; i < nl; i++)

mat[i] = espace + i * nc;

c© H. Garreta, 2003 67

Page 68: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.2 Les pointeurs et les tableaux 6 POINTEURS

/* utilisation de la matrice mat. Par exemple : */for (i = 0; i < nl; i++)

for (j = 0; j < nc; j++)mat[i][j] = 0;

etc.

/* liberation (indispensable dans le cas d’une variablelocale) */free(espace);free(mat);return 1; /* succes */

}

B e!

y0\

B n s i ro 0\o0\C v ? \a 0aC n e se ' e é nt g p s t a n eu t x a li

0\

longueur maximum d'une chaîne

Fig. 13 – Matrice de caracteres

6.2.5 Tableaux de chaınes de caracteres

Le remplacement d’un tableau a deux indices par un tableau de pointeurs se revele encore plus utile dansle traitement des tableaux de chaınes de caracteres. Ici, cette technique entraıne une economie de place sub-stantielle, puisqu’elle permet de remplacer un tableau rectangulaire dont la largeur est determinee par la plusgrande longueur possible d’une chaıne (avec un espace inoccupe tres important) comme celui de la figure 13,par un espace lineaire dans lequel chaque chaıne n’occupe que la place qui lui est necessaire, comme le montrela figure 14.

encombrement réel des caractères

B n s i ro 0\o C v ? \a 0a0\ B ey 0\ C n e se ' ...

Fig. 14 – Tableau de chaınes de caracteres

Nous allons illustrer cette methodologie par un exemple tres simple mais tout a fait typique. Notre programmelit des lignes de caracteres au terminal et les range dans une table, jusqu’a la rencontre de la fin de fichier oula lecture d’une ligne vide. Ensuite, il ordonne les lignes lues ; finalement il affiche les lignes ordonnees.

Les lignes lues sont rangees les unes a la suite des autres dans un tableau unique, nomme espace, dont lepointeur libre indique la premiere place disponible. La table des mots, notee mots, est une table de renvoisdans espace. La figure 14 illustre ces structures de donnees. Notez que le tri se fait en ordonnant la table despointeurs ; les caracteres eux-memes ne sont nullement deplaces.

Les fonctions gets (lecture de chaıne), puts (ecriture de chaıne), strcmp (comparaison de chaınes) et strlen(longueur d’une chaıne) appartiennent a la bibliotheque standard et sont expliquees dans les sections suivantes.

#include <stdio.h>#include <string.h>

#define MAXMOTS 100#define MAXCARS 3000

68 c© H. Garreta, 2003

Page 69: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.2 Les pointeurs et les tableaux

char espace[MAXCARS];char *libre = espace;char *mots[MAXMOTS];int nbr_mots = 0;

void tri(char *t[], int n) {for (;;) {

int i, ok = 1;for (i = 1; i < n; i++)

if (strcmp(t[i - 1], t[i]) > 0) {char *w = t[i - 1];t[i - 1] = t[i];t[i] = w;ok = 0;

}if (ok)

return;n--;

}}

void main(void) {int i;while(gets(libre) != NULL && *libre != ’\0’) {

mots[nbr_mots++] = libre;libre += strlen(libre) + 1;

}tri(mots, nbr_mots);for (i = 0; i < nbr_mots; i++)

printf("%s\n", mots[i]);}

Remarque. On peut signaler qu’une structure tout a fait analogue a notre table mots est creee par lecompilateur lorsqu’on initialise un tableau de chaınes de caracteres, comme dans :

char *messages[] = {"Fichier inexistant","Volume ou repertoire inexistant","Nom de fichier incorrect","Erreur physique d’entree-sortie","Autre erreur" };

La seule difference entre notre tableau mots et le tableau messages que creerait le compilateur par suitede la declaration ci-dessus est la suivante : les elements de mots sont des pointeurs vers un espace modifiablefaisant partie de la memoire attribuee pour les variables de notre programme, tandis que messages est fait depointeurs vers des cellules de la � zone de constantes �, espace immodifiable alloue et garni une fois pour toutespar le compilateur.

6.2.6 Tableaux multidimensionnels formels

Nous avons vu que si un argument formel d’une fonction ou une variable externe est un tableau a un seulindice, alors l’indication du nombre de ses elements est sans utilite puisqu’il n’y a pas d’allocation d’espace.Mais que se passe-t-il pour les tableaux a plusieurs indices ? Si un tableau a ete declare

T table[N1][N2] ... [Nk−1][Nk] ;

(N1, N2, etc. sont des constantes) alors un acces comme

table[i1][i2] ... [ik−1][ik]

se traduit par

*( (T*)table + i1 ×N2 ×N3 × · · · ×Nk + · · ·+ ik−1 ×Nk + ik )

c© H. Garreta, 2003 69

Page 70: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.2 Les pointeurs et les tableaux 6 POINTEURS

Qu’il y ait ou non allocation d’espace, il faut que ce calcul puisse etre effectue. D’ou la regle generale :lorsqu’un argument formel d’une fonction ou une variable externe est un tableau a plusieurs indices, l’indicationde la premiere dimension est sans utilite mais les autres dimensions doivent etre specifiees.

Pour un tableau a deux indices, cela donne : il est inutile d’indiquer combien il y a de lignes, mais il fautdonner la taille d’une ligne. Par exemple, la fonction suivante effectue la multiplication par un scalaire k de lamatrice m declaree NL × NC (deux constantes connues dans ce fichier) :

void k_fois(double k, double m[][NC]) {int i, j;for (i = 0; i < NL; i++)

for (j = 0; j < NC; j++)m[i][j] *= k;

}

Remarque. Bien entendu, une autre solution aurait consiste a faire soi-meme les calculs d’indice :

void k_fois(double k, void *m) {int i, j;for (i = 0; i < NL; i++)

for (j = 0; j < NC; j++)*((double *) m + i * NC + j) *= k;

}

Tout ce qu’on y gagne est un programme bien plus difficile a lire !

6.2.7 Tableaux non necessairement indexes a partir de zero

En principe, les tableaux de C sont indexes a partir de zero : sans qu’il soit necessaire de le specifier, untableau T declare de taille N est fait des elements T0, T1 . . . TN−1. Nous avons vu que, dans la grande majoritedes applications, cette convention est commode et fiable.

Il existe cependant quelques situations ou on souhaite pouvoir noter les elements d’un tableau T1, T2 . . . TN

ou, plus generalement, Ta, Ta+1 . . . Tb, a et b etant deux entiers quelconques verifiant a ≤ b. Nous allons voirque l’allocation dynamique fournit un moyen simple et pratique44 pour utiliser de tels tableaux.

Cas des tableaux a un indice. La fonction dTable45 cree un tableau d’elements de type double dontles indices sont les entiers iMin, iMin+1, . . . iMax :

double *dTable(int iMin, int iMax) {double *res = malloc((iMax - iMin + 1) * sizeof(double));if (res == NULL) {

erreurTable = 1;return NULL;

}erreurTable = 0;return res - iMin;

}

(Si on avait besoin de tableaux de float, de int, etc. on ecrirait des fonctions analogues fTable, iTable,etc.). La variable globale erreurTable est rendue necessaire par le fait qu’un resultat NULL ne suffit pas ici acaracteriser l’echec de l’allocation : res - iMin peut etre nul alors que l’allocation a reussi et res 6= NULL. Lafigure 15 illustre le resultat rendu par la fonction precedente lors d’un appel comme dTable(5, 10).

0n accede aux elements d’un tel tableau a l’aide d’un indice compris entre les valeurs indiquees mais, a partcela, de maniere tout a fait habituelle ; en particulier, sans perte d’efficacite. Exemple d’utilisation :

44Nous expliquons ici une technique massivement employee dans la bibliotheque de calcul scientifique Numerical Recipes ( c©Numerical Recipes Software) basee sur le livre Numerical Recipes in C (W. Press, S. Teukolsky, W. Vetterling et B. Flannery,Cambridge University Press, 1992).

45D’obscures contraintes techniques, ayant perdu tout interet de nos jours, font que cette technique peut ne pas fonctionner dansles implantations de C sur d’anciennes plate-formes, comme les PC a base de processeurs Intel anterieurs au 80286 (un lointainancetre du Pentium), dans lesquelles l’arithmetique des adresses subit plusieurs limitations drastiques.

70 c© H. Garreta, 2003

Page 71: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.2 Les pointeurs et les tableaux

adresse rendue commerésultat par dTable

cellules effectivement allouées

Fig. 15 – Adresse rendue par la fonction dTable

...double *t = dTable(5, 10);...for (i = 5; i <= 10; i++)

t[i] = 1000 + i;...for (i = 5; i <= 10; i++)

printf("%.1f\n", t[i]);...

Attention. Un tableau cree par un appel de la fonction dTable est dynamiquement alloue, cependant iln’est pas destine a etre libere. En particulier, a la suite du programme ci-dessus, un appel comme

free(t);

generera certainement une erreur a l’execution. Il faudrait definir une structure plus complexe qu’un simpletableau, memorisant soit la valeur de iMin, soit celle de res (variables locales de la fonction dTable ci-dessus)pour pouvoir gerer la liberation ulterieure d’un tel tableau.

Cas des tableaux a plusieurs indices. Les remarques precedentes, avec celles faites a la section 6.2.4,suggerent une maniere d’utiliser des matrices dont les lignes et les colonnes ne sont pas indexees a partir dezero.

La fonction dMatrice cree une matrice dont les lignes sont indexees par les entiers lMin, lMin+1 . . . lMaxet les colonnes sont indexees par les entiers cMin, cMin+1 . . . cMax :

double **dMatrice(int lMin, int lMax, int cMin, int cMax) {int nbLin, nbCol, i;double *espace, **lignes, *p;

nbLin = lMax - lMin + 1;nbCol = cMax - cMin + 1;espace = malloc(nbLin * nbCol * sizeof(double));if (espace == NULL) {

erreurTable = 1;return NULL;

}lignes = malloc(nbLin * sizeof(double *));if (lignes == NULL) {

erreurTable = 1;free(espace);return NULL;

}p = espace - cMin;for (i = 0; i < nbLin; i++) {

lignes[i] = p;p += nbCol;

}erreurTable = 0;return lignes - lMin;

}

Comme precedemment, l’emploi de ces matrices ne s’accompagne d’aucune perte d’efficacite. Voici unexemple d’utilisation :

c© H. Garreta, 2003 71

Page 72: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.3 Les adresses des fonctions 6 POINTEURS

...double **m = dMatrice(5, 10, -3, 3);...for (i = 5; i <= 10; i++)

for (j = -3; j <= 3; j++)m[i][j] = 1000 * i + j;

...for (i = 5; i <= 10; i++) {

for (j = -3; j <= 3; j++)printf("%10.1f ", m[i][j]);

printf("\n");}...

6.3 Les adresses des fonctions

6.3.1 Les fonctions et leurs adresses

A l’occasion de l’etude des declarateurs complexes, nous avons appris que la syntaxe de C permet de declarerdes objets de type � fonction rendant un (objet de type) T �. De plus, cette expression est un declarateur quipeut se composer recursivement avec les autres, � adresse de... � et � tableau de... �. Tout cela suggere quenous allons pouvoir travailler avec des variables de type fonction, des tableaux de fonctions, peut-etre memedes fonctions renvoyant des fonctions, etc.

Lorsqu’un programme est actif, les fonctions qui le composent sont logees dans la memoire de l’ordinateur,tout comme les donnees que ces fonctions manipulent. Le parallele s’arrete la, car il y a entre les fonctions etles donnees une difference redhibitoire : alors que du type d’une donnee on peut deduire sa taille, le type d’unefonction ne specifie que le type du resultat46 et ne donne aucune indication sur l’encombrement de la fonction (lataille de la fonction une fois compilee). Autrement dit, le type � fonction rendant un objet de type T � n’a pasde taille definie. De cette remarque decoule l’impossibilite technique de declarer des variables de type fonction.L’objet variable le plus simple permettant de designer une fonction devra avoir au moins le type � adresse d’unefonction rendant un objet de type T �.

Comment le programmeur obtient-il des adresses de fonctions ? De la meme maniere que le nom d’un tableaurepresente son adresse de base, le nom d’une fonction represente l’adresse de celle-ci. Ainsi, si on a la definitionde fonction

type nom(...) {etc.

}

alors l’expression reduite a nom :– possede le type � adresse d’une fonction rendant un type � ;– a pour valeur l’adresse de base de la fonction ;– n’est pas une lvalue.Nous allons examiner deux applications de cela parmi les plus importantes.

6.3.2 Fonctions formelles

Le probleme des fonctions formelles, c’est-a-dire des fonctions arguments d’autres fonctions, est resolu en Cpar l’utilisation d’arguments de type � pointeur vers une fonction �.

Exemple 1 (inevitable). La fonction dicho resout l’equation f(x) = 0. Ses arguments sont : les bornes a etb de l’intervalle de recherche, la precision ε voulue et surtout la fonction f dont on cherche un zero. Voici cettefonction :

46Dans le C ANSI (� avec prototypes �), la type d’une fonction specifie aussi le nombre et les types des arguments, bien sur.

72 c© H. Garreta, 2003

Page 73: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.3 Les adresses des fonctions

double dicho(double a, double b, double eps, double (*f)(double)) {double x; /* hypothese : f(a) * f(b) <= 0 */if ((*f)(a) > 0) {

x = a; a = b; b = x;}while (fabs(b - a) > eps) {

x = (a + b) / 2;if ((*f)(x) < 0)

a = x;else

b = x;}return x;

}L’argument f est declare comme un pointeur vers une fonction ; par consequent *f est une fonction et

(*f)(x) un appel de cette fonction. Si une fonction est le nom d’une fonction particuliere, changeant de signesur l’intervalle [0, 1], un appel correct de dicho serait

x = dicho(0.0, 1.0, 1E-8, une_fonction);

Remarque. En syntaxe originale la fonction dicho s’ecrirait

double dicho(a, b, eps, f)double a, b, eps, (*f)();

{etc. (la suite est la meme)

}

Exemple 2. Reprenons notre programme de la section 6.2.5 en remplacant la fonction de tri naıf par lafonction qsort de la bibliotheque standard. Cette fonction est expliquee a la section 8.4.2, elle trie un tableaupar rapport a un critere quelconque. Si elle nous interesse ici c’est parce qu’elle prend ce critere, represente parune fonction, pour argument :

#include <stdio.h>#include <string.h>

#define MAXMOTS 100#define MAXCARS 3000

char espace[MAXCARS], *libre = espace;char *mots[MAXMOTS];int nbr_mots = 0;

/* La declaration suivante se trouve aussi dans <stdlib.h> : */void qsort(void *base, size_t nmemb, size_t size,

int (*comp)(const void *, const void *));

int comparer(const void *a, const void *b) {/* Les arguments de cette fonction doivent etre declares "void *" (voyez la *//* declaration de qsort). En realite, dans les appels de cette fonction que *//* nous faisons ici, a et b sont des adresses de "chaınes", c’est-a-dire des *//* adresses d’adresses de char */

return strcmp(*(char **)a, *(char **)b);}

main() {int i;while(gets(libre) != NULL && *libre != ’\0’) {

mots[nbr_mots++] = libre;libre += strlen(libre) + 1;

}qsort(mots, nbr_mots, sizeof(char *), comparer);for (i = 0; i < nbr_mots; i++)

printf("%s\n", mots[i]);}

c© H. Garreta, 2003 73

Page 74: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.3 Les adresses des fonctions 6 POINTEURS

Remarque. La definition de comparer peut paraıtre bien compliquee. Pour la comprendre, il faut considererles diverses contraintes auxquelles elle doit satisfaire :

– comparer figurant comme argument dans l’appel de qsort, son prototype doit correspondre exactement47

a celui indique dans le prototype de qsort ;– a et b, les arguments formels de comparer, etant de type void *, on ne peut acceder aux objets qu’ils

pointent sans leur donner auparavant (a l’aide de l’operateur de conversion de type) un type pointeur� utilisable � ;

– d’apres la documentation de qsort, nous savons que comparer sera appelee avec deux adresses de chaınespour arguments effectifs ; une chaıne etant de type char *, le type � adresse de chaıne � est char **, cequi explique la facon dont a et b apparaissent dans l’appel de strcmp.

6.3.3 Tableaux de fonctions

Le programme suivant montre l’utilisation d’un tableau d’adresses de fonctions : chaque element du tableautable est forme de deux champs :

– le nom d’une fonction standard, sous forme de chaıne de caracteres ;– l’adresse de la fonction correspondante.

Notre programme lit des lignes constituees d’un nom de fonction, suivi d’un ou plusieurs blancs, suivis d’unnombre reel ; il evalue et affiche alors la valeur de la fonction mentionnee appliquee au nombre donne. La frapped’une ligne reduite au texte fin arrete le programme.

#include <stdio.h>

double sin(double), cos(double), exp(double), log(double);

struct {char *nom;double (*fon)(double);

} table[] = {"sin", sin,"cos", cos,"exp", exp,"log", log };

#define NBF (sizeof table / sizeof table[0])

main() {char nom[80];double x;int i;for(;;) {

scanf("%s", nom);if (strcmp(nom, "fin") == 0)

break;scanf("%lf", &x);for(i = 0; i < NBF && strcmp(table[i].nom, nom) != 0; i++)

;if (i < NBF)

printf("%f\n", (*table[i].fon)(x));else

printf("%s ???\n", nom);}

}

Voici une execution de ce programme :

47En syntaxe � sans prototype �, aucun controle n’est fait sur le type des arguments de comparer, et toute cette question est bienplus simple. Mais attention : quelle que soit la syntaxe employee, on ne peut pas ignorer le fait que comparer sera appelee (parqsort) avec pour arguments effectifs les adresses des chaınes a comparer.

74 c© H. Garreta, 2003

Page 75: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.4 Structures recursives

sin 10.841471cos 10.540302atn 1atn ???fin

Remarque. La declaration du tableau table presente une caracteristique peu frequente : des fonctionssont referencees mais elles ne sont pas en meme temps appelees. En effet, les noms des fonctions sin, cos,exp, etc. apparaissent dans cette declaration sans etre accompagnes d’une paire de parentheses ; il n’y a aucunsigne indiquant au compilateur qu’il s’agit de fonctions. C’est pourquoi ce programme sera trouve errone par lecompilateur a moins qu’on lui donne les declarations externes des fonctions, soit explicitement :

double sin(double), cos(double), exp(double), log(double);

soit en incluant un fichier dans lequel sont ecrites ces declarations :

#include <math.h>

6.3.4 Flou artistique

A ce point de notre expose vous etes en droit de trouver que les roles respectifs des objets de type � fonc-tion... � et des objets de type � adresse d’une fonction... � sont confus. Examinons une fonction bien ordinaire,sinus. La declaration de cette fonction, extraite du fichier <math.h>, est :

double sin(double);

D’autre part, la definition d’une variable de type � adresse d’une fonction double a un argument double � est :

double (*f)(double);

Voici l’initialisation de f par l’adresse de la fonction sin et deux appels equivalents de cette fonction (x, yet z sont des variables double) :

f = sin;y = (*f)(x);z = sin(x);

Quelle que soit la valeur de x, y et z recoivent la meme valeur. Pourtant, les trois lignes ci-dessus ne sontpas coherentes. La premiere montre que f et sin sont de meme type (a savoir � adresse d’une fonction... �) ; orf doit etre dereference pour appeler la fonction, tandis que sin n’a pas besoin de l’etre.

On peut comprendre la raison de cette incoherence. Apres avoir constate l’impossibilite de declarer desvariables de type � fonction... � , car une fonction n’a pas de taille definie, et donc l’obligation de passer par desvariables � adresse de fonction... �, on a voulu faire cohabiter la rigueur (un pointeur doit etre dereference pouracceder a l’objet qu’il pointe) et la tradition (le sinus de x s’est toujours ecrit sin(x)). Cela a conduit a definirl’operateur d’appel de fonction aussi bien pour une expression de type � fonction... � que pour une expressionde type � adresse d’une fonction... �. Ainsi, les deux expressions (*f)(x) et sin(x) sont correctes ; l’experiencemontre que cette tolerance n’a pas de consequences nefastes. Il n’en decoule pas moins que les deux expressions

u = f(x);v = (*sin)(x);

sont acceptees elles aussi (elles ont la meme valeur que les precedentes).

6.4 Structures recursives

6.4.1 Declaration

Les structures recursives font reference a elles-memes. Elles sont principalement utilisees pour implanter desstructures de donnees dont la definition formelle est un enonce recursif. Parmi les plus frequemment rencontrees :les listes et les arbres, comme les arbres binaires.

Ainsi, par exemple, on peut donner la definition recursive suivante : un arbre binaire est– soit un symbole conventionnel signifiant � la structure vide � ;– soit un triplet ( valeur , filsg , filsd ) constitue d’une information dont la nature depend du probleme

etudie et de deux descendants qui sont des arbres binaires.

c© H. Garreta, 2003 75

Page 76: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.4 Structures recursives 6 POINTEURS

Bien entendu, du point de vue technique il n’est pas possible de declarer un champ d’une structure commeayant le meme type que la structure elle-meme (une telle structure serait ipso facto de taille infinie !). Oncontourne cette difficulte en utilisant les pointeurs. Un arbre binaire est represente par une adresse, qui est :

– soit l’adresse nulle ;– soit l’adresse d’une structure constituee de trois champs : une information et deux descendants qui sont

des adresses d’arbres binaires.

Typiquement, la declaration d’une telle structure sera donc calquee sur la suivante :

struct noeud {type valeur;struct noeud *fils_gauche, *fils_droit;

};

Notez que le nom de la structure (noeud) est ici indispensable, pour indiquer le type des objets pointes parles champs fils gauche et fils droit.

6.4.2 Exemple

Le programme suivant effectue le meme travail que celui du 6.2.5, mais a la place d’une table on utilise unarbre binaire de recherche (voir, par exemple, R. Sedgewick, Algorithmes en langage C, chapitre 14) pour rangerles mots. La structure de donnees mise en œuvre est representee sur la figure 16.

j a n a ne é p a l \0u ...0\ d r 0\

libre

...... ......

racine

motfils gauchefils droit

Fig. 16 – Arbre binaire de mots

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

#define MAXCARS 3000 char

espace[MAXCARS], *libre = espace;

typedef struct noeud {char *mot;struct noeud *fils_gauche, *fils_droit;

} NOEUD, *POINTEUR;

POINTEUR alloc(char *m, POINTEUR g, POINTEUR d) {POINTEUR p = malloc(sizeof(NOEUD));p->mot = m;p->fils_gauche = g;p->fils_droit = d;return p;

}

76 c© H. Garreta, 2003

Page 77: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6 POINTEURS 6.4 Structures recursives

POINTEUR insertion(POINTEUR r, char *mot) {if (r == NULL)

r = alloc(mot, NULL, NULL);else

if (strcmp(mot, r->mot) <= 0)r->fils_gauche = insertion(r->fils_gauche, mot);

elser->fils_droit = insertion(r->fils_droit, mot);

return r;}

void parcours(POINTEUR r) {if (r != NULL) {

parcours(r->fils_gauche);printf("%s\n", r->mot);parcours(r->fils_droit);

}}

main() {POINTEUR racine = NULL;char *p;

p = gets(libre);while(*p != ’\0’) {

libre += strlen(p) + 1;racine = insertion(racine, p);p = gets(libre);

}

parcours(racine);}

6.4.3 Structures mutuellement recursives

Donnons-nous le probleme suivant : representer une � liste de familles � ; chaque famille est une � listed’individus �, avec la particularite que chaque individu fait reference a sa famille. Les structures famille etindividu se pointent mutuellement, chacune intervenant dans la definition de l’autre. Comment les declarer ?

...

...

...

...

...Liste

defamilles

Liste d'individus

Fig. 17 – Structures mutuellement recursives

Pour resoudre ce probleme le compilateur C tolere qu’on declare un pointeur vers une structure non encoredefinie. Plus precisement, si la structure ayant le nom indique n’a pas encore ete declaree, l’expression � structnom � est legitime. Elle identifie un type incomplet et on peut l’utiliser a tout endroit ou sa taille n’est pasrequise, notamment dans la declaration d’un pointeur vers une telle structure. Cela nous permet de declarer nosstructures mutuellement recursives sous la forme :

c© H. Garreta, 2003 77

Page 78: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

6.4 Structures recursives 6 POINTEURS

typedef struct famille {char *nom;struct individu *membres;struct famille *famille_suivante;

} FAMILLE;

typedef struct individu {char *prenom;struct individu *membre_suivant;struct famille *famille;

} INDIVIDU;

Une autre solution aurait ete :

typedef struct famille FAMILLE;typedef struct individu INDIVIDU;

struct famille {char *nom;INDIVIDU *membres;FAMILLE *famille_suivante;

};

struct individu {char *prenom;INDIVIDU *membre_suivant;FAMILLE *famille;

};

78 c© H. Garreta, 2003

Page 79: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES

7 Entrees-sorties

A strictement parler, les operations d’entree-sortie ne font pas partie du langage C ; elles sont effectuees pardes fonctions de la bibliotheque que chaque systeme offre avec le compilateur. Or, C est ne et a vecu longtempsen milieu UNIX avant d’etre transporte ailleurs ; la bibliotheque UNIX a donc souvent ete prise pour reference,semant quelque confusion car la realisation de certaines fonctions d’UNIX est inutile, voire impossible surcertains systemes. La norme ANSI y a mis bon ordre ; en principe la liste des fonctions standard est maintenantclairement etablie et un programme qui n’utilise que ces fonctions doit pouvoir etre transporte d’un systemea un autre sans modification. Rien n’empeche que telle ou telle realisation particuliere du langage propose desfonctions additionnelles ; il faudra alors vous referer a la documentation specifique.

7.1 Flots

Quelles que soient les particularites du systeme d’exploitation sous-jacent, les fonctions de la bibliothequestandard des entrees-sorties font en sorte que les fichiers soient vus par le programmeur comme des flots, c’est-a-dire des suites d’octets qui representent des donnees selon une des deux modalites suivantes :

– soit le fichier contient les caracteres qui constituent l’expression ecrite des donnees en question d’apres unecertaine syntaxe. Ces caracteres sont eux-memes representes selon une convention largement repandue,presque toujours le code ASCII. De tels fichiers sont appeles fichiers de texte. Leur principale qualite estd’etre exploitables par un logiciel, un systeme ou un equipement differents de celui qui les a produits ; enparticulier, ils peuvent etre edites, imprimes, etc. ;

– soit le fichier contient des donnees enregistrees sous une forme qui est la copie exacte de leur codage dansla memoire de l’ordinateur. On l’appelle alors fichier binaire. Les operations de lecture ou d’ecriture surde tels fichiers peuvent etre tres rapides, car elles ne requierent pas un travail d’analyse ou de synthese del’expression ecrite des donnees. En revanche, les fichiers binaires ne sont pas editables ou imprimables ; ilsne sont pas censes etre transportables d’un logiciel a un autre, encore moins d’un systeme a un autre.

Du point de vue du langage C (il n’en est peut etre pas de meme pour le systeme sous-jacent) etre de texteou binaire n’est pas un attribut d’un fichier, mais de la maniere dont il est traite par un programme. Ainsi ladistinction entre ces deux types de fichiers ne se fait pas lors de la declaration ou de l’ouverture48 du fichier,mais par le choix des fonctions de lecture ou d’ecriture qui sont utilisees : certaines fonctions (fgets et fputs,lecture et ecriture d’une ligne, et fscanf et fprintf, lecture et ecriture de donnees formatees) n’ont de sens quesur un flot de texte. D’autres fonctions (fread, fwrite) effectuent la lecture et l’ecriture de paquets d’octetssans interpretation aucune, et correspondent donc plutot aux flots binaires49.

Les flots binaires sont de simples suites d’octets. Les flots de texte ont une structure legerement plus riche,puisqu’ils sont censes etre organises comme des suites de lignes. Chaque ligne est faite d’un nombre quelconquede caracteres distincts de ’\n’ et se termine par le caractere ’\n’. Meme si sur un systeme particulier les fichiersde texte ne sont pas ainsi organises, ou si le caractere qui indique la fin d’une ligne est different, la bibliothequestandard fera en sorte qu’ils puissent etre vus de cette maniere par le programmeur50.

Tampons. Un fichier est dit tamponne51 lorsque les transferts physiques d’octets entre le fichier et unprogramme qui le lit ou l’ecrit se font par paquets de taille fixe, la taille du tampon, meme si les echangeslogiques sont faits par paquets de taille variable ou meme octet par octet. Par defaut un flot est toujours associea un tampon de taille predeterminee, mais le programmeur peut, a l’aide de la fonction setvbuf (cf. section7.1.1), modifier la taille de ce dernier ou meme le supprimer completement.

Ce mode de gestion des fichiers reduit le nombre d’operations physiques d’entree - sortie (beaucoup pluslentes que les transferts d’information purement internes) mais introduit un decalage dans leur chronologie.Ce que le programmeur croit etre une operation d’ecriture dans un fichier n’est en realite qu’une operation derecopie dans un tampon situe en memoire, dont le contenu sera transfere ulterieurement vers le fichier. Parconsequent, il est difficile ou impossible de savoir dans quel etat se trouve le fichier a un moment donne ; deplus, si le programme vient a subir une terminaison anormale, une partie des informations logiquement ecrites

48Il y a quand-meme un point de detail pour lequel il faut indiquer, lors de l’ouverture, si le fichier est de texte ou non : y a-t-illieu de guetter les marques de fins-de-ligne pour les transformer dans le caractere ’\n’ et reciproquement ?

49Consequence : sauf si le systeme sous-jacent l’interdit, un fichier ecrit comme un flot de texte peut etre lu comme un flot binaire.Cela revient a ignorer le fait que les octets dont il se compose representent des donnees d’un ordre superieur. La demarche inverse,lire comme un flot de texte un fichier binaire, n’a pas de signification et peut provoquer des erreurs fatales (les fins de ligne serontabsentes ou incomprises, le tampon debordera, etc.).

50En particulier, le couple (CR , LF ) utilise par certains systemes, comme MS-DOS, sera traduit en lecture par l’unique caractere’\n’. Inversement, sur de tels systemes, l’ecriture � logique � du caractere ’\n’ se traduira par l’ecriture effective du couple (CR ,LF ).

51En bon franglais, on dit plutot � bufferise �.

c© H. Garreta, 2003 79

Page 80: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.1 Flots 7 ENTREES-SORTIES

dans le fichier seront perdues, parce qu’elles n’ont jamais ete physiquement transferees52.

7.1.1 Fonctions generales sur les flots

Les flots sont representes dans les programmes par des variables de type FILE *. La structure FILE estdefinie dans le fichier <stdio.h>. Pour utiliser un ou plusieurs flots il faut donc ecrire en tete de son programmela directive

#include <stdio.h>

puis une declaration de la forme

FILE *fichier;

Parmi les principales fonctions qui effectuent les operations generales (ouverture, fermeture, etc.) sur les flotsnous avons :

FILE *fopen(const char *nom, const char *mode)

Cette fonction ouvre le fichier dont le nom est indique par la chaıne nom et rend un pointeur sur le flotcorrespondant, ou NULL si l’operation a echoue (fichier absent, etc.). Le nom doit etre correct pour lesysteme d’exploitation sous-jacent ; cela ne regarde pas le langage C.Les valeurs permises pour mode sont :

"r" (read) ouverture d’un fichier. Le fichier doit exister ; son contenu n’est pas detruit. Le descripteur duflot cree est positionne en lecture et au debut du fichier. En principe, seules les operations de lecturesont permises sur ce flot.

"r+" comme "r", mais les operations d’ecriture sont permises aussi.

"w" (write) creation d’un fichier. Le fichier peut exister ou non ; s’il existe, son contenu est entierementefface. Le descripteur du flot cree est positionne en ecriture et au debut du fichier (qui est vide). Enprincipe, seules les operations d’ecriture sont permises sur ce flot.

"w+" comme ”w”, mais les operations de lecture sont permises aussi.

"a" (append) allongement d’un fichier. Le fichier existe ou non ; s’il existe, son contenu n’est pas efface. Ledescripteur du flot cree est positionne en ecriture et a la fin du fichier. Seules les operations d’ecrituresont permises.

"a+" comme ”a”, mais les operations de lecture sont permises aussi.

Si l’on envisage d’utiliser le fichier en mode binaire, il faut en principe ajouter la lettre b au mode ("r+b"ou "rb+", "w+b" ou "wb+", etc.). Il n’est pas exclu que sur un systeme d’exploitation particulier cela n’aitaucune utilite.

Remarque. Sur l’interet qu’il peut y avoir a effectuer des lectures et des ecritures sur le meme fichier,voir les remarques faites a la section 7.4.3 a propos des fichiers en acces relatif.

int fflush(FILE *flot)

Cette fonction provoque l’ecriture physique immediate du tampon en cours de composition pour le flotindique. Elle rend EOF en cas d’erreur, zero dans les autres cas.

Remarque. En regle generale, si le fichier physique qui correspond au flot indique est un organe interactif,par exemple si c’est le clavier ou l’ecran d’un poste de travail, alors la frappe par l’utilisateur du caracterede fin de ligne ou l’ecriture de ce caractere par un programme provoque egalement le transfert effectif ducontenu du tampon.

int fclose(FILE *flot)

Cette fonction ferme le flot indique (ecriture physique des tampons, restitution de l’espace alloue pour letampon et les descripteurs du flot, etc.). Elle rend zero en cas de succes, une autre valeur en cas d’erreur.Un appel comme fclose(flot) rend la valeur de la variable flot illegitime.

Oublier de fermer, par un appel de fclose, un fichier qui a ete ouvert en lecture n’a en general aucuneconsequence. Mais oublier de fermer un fichier qui a ete ouvert en ecriture (c’est-a-dire un fichier qui vientd’etre cree) peut etre fatal pour l’information qui y a ete ecrite, car

52S’il existe une probabilite non negligeable pour qu’un programme ait une fin anormale (par exemple a cause de conditionsd’exploitation difficiles) il est prudent de programmer des appels reguliers de la fonction fflush (cf. section 7.1.1).

80 c© H. Garreta, 2003

Page 81: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.1 Flots

– le contenu du tampon d’ecriture en cours de formation lors de la terminaison du programme ne seraprobablement pas sauvegarde dans le fichier ;

– dans certains systemes les fichiers nouvellement crees ne subissent les operations qui les rendent connusdu systeme d’exploitation (comme l’inclusion de leur nom dans un repertoire) qu’au moment de leurfermeture. Un fichier qui n’a jamais ete ferme a donc une existence tres precaire.

FILE *tmpfile(void)

Cette fonction cree un fichier temporaire en mode "w+b", sans nom externe, qui sera automatiquementdetruit a la terminaison du programme. Elle renvoie le flot associe ou NULL en cas d’erreur.

int setvbuf(FILE *flot, char *tampon, int mode, size t taille)

Cette fonction redefinit le tampon associe a un flot ouvert. Elle doit etre appelee apres l’ouverture duflot mais avant toute lecture ou ecriture. L’argument mode doit etre une des trois constantes suivantes,definies dans <stdio.h> :IOFBF Tampon de taille fixe. L’ecriture ou la lecture physique a lieu lorsque le tampon contient taille

caracteres.IOLBF Tampon egal a une ligne. L’ecriture ou la lecture physique a lieu lors de la rencontre du caractere

’\n’ qui determine la fin des lignes.IONBF Pas de tampon. Une ecriture ou une lecture physique a lieu a chaque lecture ou ecriture logique.L’argument taille indique la taille voulue pour le tampon. L’argument tampon, s’il n’est pas NULL, estcense pointer un espace (de taille au moins egale a taille) qui sera utilise comme tampon. Si tampon estNULL, alors la fonction alloue un espace propre.

int feof(FILE *flot)

Cette fonction renvoie une valeur non nulle si et seulement si l’indicateur de fin de fichier du flot indiqueest � vrai �.

int ferror(FILE *flot)

Cette fonction renvoie une valeur non nulle si et seulement si l’indicateur d’erreur du flot indique est� vrai �. Elle doit etre appelee immediatement apres une entree-sortie sur ce flot pour savoir si l’operationen question a echoue. La variable globale entiere errno (declaree dans <errno.h>) contient alors un coderenseignant sur la nature de l’erreur.

7.1.2 Les unites standard d’entree-sortie

Sans que le programmeur ait a prendre aucune disposition particuliere, l’execution d’un programme com-mence avec trois flots de texte automatiquement ouverts par le systeme. Ils sont connectes (on dit affectes) auxorganes d’entree-sortie les plus � naturels � par rapport a la maniere dont on utilise l’ordinateur. Ces fichierssont declares dans <stdio.h>, de la maniere suivante :

FILE *stdin, *stdout, *stderr;

stdin est l’unite standard d’entree. Elle est habituellement affectee au clavier du poste de travail.stdout est l’unite standard de sortie. Elle est habituellement affectee a l’ecran du poste de travail.stderr est l’unite standard d’affichage des erreurs. Elle est aussi affectee a l’ecran du poste de travail.

Les fonctions de lecture (resp. d’ecriture) qui ne specifient pas un autre flot utilisent stdin (resp. stdout).

Remarque. L’utilisation des unites standard apparaıt encore plus interessante lorsque l’on sait que dansUNIX et plusieurs autres systemes d’exploitation il est permis de reaffecter les unites standard lors d’uneexecution du programme, sans qu’il faille recompiler ce dernier. En UNIX et MS-DOS cela fonctionne de lamaniere suivante : supposons que prog1 soit un programme qui n’utilise que les unites standard stdin etstdout. Active par la commande prog1, il lira ses donnees au clavier et affichera ses resultats a l’ecran. Maiss’il est lance par la commande

prog1 < fichier1 (resp. : prog1 > fichier2)alors le fichier fichier1 (resp. fichier2) remplacera le clavier (resp. l’ecran). De maniere similaire, supposonsque prog2 soit un autre programme qui fait ses entrees-sorties de la meme maniere. Alors la commande

prog1 | prog2

active en parallele ces deux programmes avec la sortie standard de prog1 connectee a l’entree standard de prog2(les sorties de prog1 sont lues et exploitees par prog2 au fur et a mesure qu’elles sont produites par prog1).Bien entendu, tout cela se combine. Par exemple, la commande

c© H. Garreta, 2003 81

Page 82: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.2 Lecture et ecriture textuelles 7 ENTREES-SORTIES

prog1 < fichier1 | prog2 > fichier2

active prog1 et prog2. Les donnees de prog1 sont lues dans fichier1, ses resultats sont fournis a titre dedonnees a prog2, dont les resultats sont ecrits dans fichier2.

7.2 Lecture et ecriture textuelles

7.2.1 Lecture et ecriture de caracteres et de chaınes

Les fonctions de lecture-ecriture les plus simples effectuent la lecture ou l’ecriture d’un caractere isole oubien la lecture ou l’ecriture d’une ligne. Pour la lecture nous avons :

int fgetc(FILE *flot)

Renvoie le caractere suivant sur le flot indique, ou EOF si la fin du fichier est atteinte ou si une erreursurvient. C’est une vraie fonction.

int getc(FILE *flot)

Fait la meme chose que fgetc mais c’est peut etre macro.

L’interet d’utiliser une macro reside dans l’efficacite superieure des (petites) macros par rapport aux(petites) fonctions53. Il ne faut pas oublier cependant qu’il y a deux cas ou les macros ne peuvent pas etreutilisees :– lorsque l’argument a un effet de bord. Une expression comme

*s++ = fgetc(table_de_fichiers[i++])

est correcte (mais peu utilisee), tandis que*s++ = getc(table_de_fichiers[i++])

est certainement erronee, car ayant un effet indefini (on ne peut pas dire combien de fois i seraincremente) ;

– lorsqu’il faut obtenir l’adresse de l’operation en question, pour la passer comme argument d’une autrefonction. Par exemple, si getc est une macro, un appel comme appliquer(getc, fichier) sera cer-tainement trouve incorrect par le compilateur.

int getchar(void)

getchar() equivaut a getc(stdin) ;

char *fgets(char *s, int n, FILE *flot)

Lecture d’une ligne en veillant a ne pas deborder. Plus precisement, cette fonction lit des caracteres surle flot indique et les place dans l’espace pointe par s. Elle s’arrete lorsqu’elle a lu n−1 caracteres oulorsqu’elle a rencontre un caractere ’\n’ de fin de ligne (ce caractere est copie dans le tableau). Uncaractere ’\0’ est ajoute a la fin du tableau. La fonction rend s, ou NULL si une erreur ou la fin du fichiera ete rencontree.Lisez aussi la remarque faite ci-apres a propos de la fonction gets.

char *gets(char *s)

Lecture d’une ligne sur le flot stdin. Les caracteres lus sont ranges dans l’espace pointe par s. Elle renvoiele meme resultat que fgets mais, contrairement a cette derniere– il n’y a pas de test de debordement54 ;– le caractere de fin de ligne est remplace par ’\0’.Atention. L’argument de gets doit etre l’adresse d’un espace disponible (accessible en ecriture) et detaille suffisante, dans lequel gets puisse ranger les caracteres qui seront lus.

Exemples. Pour illustrer ces fonctions, voici deux exercices d’ecole. D’une part, l’ecriture d’une fonctionanalogue a gets en utilisant getchar :

char *myGets(char *s) {int c, i = 0;c = getchar();

53Il s’agit d’entrees-sorties tamponnees, c’est-a-dire de simples transferts entre memoires. S’il s’etait agi d’operations physiques, legain de temps obtenu en utilisant getc a la place de fgetc aurait ete negligeable devant le temps requis par l’entree-sortie elle-meme.

54Ainsi, gets lit des caracteres jusqu’a la rencontre de ’\n’, sans se preoccuper de savoir si l’espace dans lequel ces caracteressont ranges est de taille suffisante pour les recevoir ; autrement dit, tout appel de gets peut entraıner un debordement de memoire.C’est la raison pour laquelle on dit que tout programme comportant ne serait-ce qu’un appel de gets est bogue.

82 c© H. Garreta, 2003

Page 83: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.2 Lecture et ecriture textuelles

while (c != ’\n’) {if (c == EOF)

return NULL;s[i++] = c;c = getchar();

}s[i] = ’\0’;return s;

}

D’autre part, l’ecriture d’une fonction analogue a getchar (version fonction) en utilisant gets :int myGetchar(void) {

static char tampon[256] = "";static char *pos = tampon;if (*pos == 0) {

if (gets(tampon) == NULL)return EOF;

strcat(tampon, "\n");pos = tampon;

}return *pos++;

}

Voyons maintenant les fonctions d’ecriture :

int fputc(int caractere, FILE *flot)Ecrit le caractere indique sur le flot indique. Renvoie le caractere ecrit ou EOF si une erreur survient.C’est une vraie fonction.

int putc(int caractere, FILE *flot)Fait la meme chose que fputc mais peut etre une macro. Voir a ce propos les remarques faites a l’occasionde l’introduction de getc.

int putchar(int caractere)

putchar(c) equivaut a putc(c, stdout).

int fputs(const char *s, FILE *flot)

Ecrit la chaıne s sur le flot indique. La fonction renvoie EOF en cas d’erreur, une valeur ≥ 0 dans lesautres cas.

int puts(const char *s)

Cette fonction ecrit la chaıne s sur le flot stdout. Elle renvoie le meme resultat que fputs. Contrairementa fputs un caractere ’\n’ est ajoute a la suite de la chaıne.

int ungetc(int c, FILE *flot)� Delecture � du caractere c. Cette fonction remet le caractere c dans le flot indique, ou il sera trouvelors de la prochaine lecture. Sur chaque flot, seule la place pour un caractere � delu � est garantie. On nepeut pas � delire � le caractere EOF.

Cette operation incarne donc l’idee de restitution (a l’unite d’entree, qui en est le fournisseur) d’uncaractere lu a tort. C’est une maniere de resoudre un probleme que pose l’ecriture d’analyseurs lexicaux,ou la fin de certaines unites lexicales est indiquee par un caractere qui les suit sans en faire partie. Exemple :la lecture d’un nombre entier non negatif :

int lirentier(FILE *flot) { /* lecture d’un entier (simpliste) */int c, x = 0;c = getc(flot);while (’0’ <= c && c <= ’9’) {

x = 10 * x + c - ’0’;c = getc(flot);

}/* ici, c est le caractere suivant le nombre */

ungetc(c, flot);return x;

}

c© H. Garreta, 2003 83

Page 84: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.2 Lecture et ecriture textuelles 7 ENTREES-SORTIES

7.2.2 Ecriture avec format printf

La fonction d’ecriture sur l’unite de sortie standard (en principe l’ecran du poste de travail) avec conversionet mise en forme des donnees est :

printf( format , expr1 , expr2 , ... exprn )

format est une chaıne qui joue un role particulier. Elle est formee de caracteres qui seront ecrits normalement,melanges a des indications sur les types des expressions expr1, expr2, ... exprn et sur l’aspect sous lequel il fautecrire les valeurs de ces expressions.

Cela fonctionne de la maniere suivante : la fonction printf parcourt la chaıne format et reconnaıt certainsgroupes de caracteres comme etant des specifications de format. C’est le caractere % qui declenche cette re-connaissance. La fonction printf recopie sur le flot de sortie tout caractere qui ne fait pas partie d’une tellespecification. La ieme specification determine la maniere dont la valeur de expr i sera ecrite.

Les specifications de format reconnues par la fonction printf sont expliquees dans la table 4.

Notez que seuls les elements 1 (le caractere %) et 6 (la lettre qui indique le type de la conversion) sontobligatoires dans une specification de format.

Exemple 1. Supposons qu’on ait donne la declaration-initialisation

float x = 123.456;

Voici une liste d’appels de printf, avec l’affichage produit par chacun :

Appel Affichage obtenuprintf(">%f<", x); >123.456001<printf(">%12f<", x); > 123.456001<printf(">%12.2f<", x); > 123.46<printf(">%.2f<", x); >123.46<printf(">%-12.2f<", x); >123.46 <printf(">%+-12.2f<", x); >+123.46 <printf(">% -12.2f<", x); > 123.46 <printf(">%12.2f<", x); > 123.46<printf(">%012.2f<", x); >000000123.46<printf(">%.0f<", x); >123<printf(">%#.0f<", x); >123.<

Exemple 2. La liste suivante illustre l’utilisation de la largeur et de la precision pour les chaınes. Supposonsavoir donne les declarations :

char *s = "ABCDEFG";int l = -10, n = 5;

Voici une liste d’appels de printf, avec l’affichage produit par chacun :

Appel Affichage obtenuprintf("printf(">%5s<", s); >ABCDEFG<printf("printf(">%10s<", s); > ABCDEFG<printf("printf(">%-10s<", s); >ABCDEFG <printf("printf(">%10.5s<", s); > ABCDE<printf("printf(">%-10.5s<", s); >ABCDE <printf("printf(">%.5s<", s); >ABCDE<printf("printf(">%*.*s<", l, n, s); >ABCDE <

La largeur du champ (cf. tableau 4, n◦ 3) peut etre indiquee par le caractere * a la place d’un nombre. Elleest alors definie par la valeur de l’argument dont c’est le tour, qui doit etre de type entier. Il en est de memepour la specification de la precision (cf. tableau 4, n◦ 4). Par exemple, i et j etant des variables entieres et xune variable flottante, l’expression

printf("%*.*f", i, j, x);

ecrit la valeur de x sur un nombre de caracteres egal a la valeur de i, avec un nombre de chiffres apres lavirgule egal a la valeur de j.

84 c© H. Garreta, 2003

Page 85: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.2 Lecture et ecriture textuelles

Chaque specification de format se compose, dans l’ordre indique, des elements suivants :

1. Obligatoirement, le caractere %.

2. Facultativement, un ou plusieurs modifieurs, dans un ordre quelconque, parmi :

- : cadrer a gauche du champ (par defaut le cadrage se fait a droite) ;

+ : imprimer le signe du nombre meme lorsque celui-ci est positif ;

espace : si le premier caractere n’est pas un signe, placer un espace au debut d’un nombre ;

0 : completer les nombres par des zeros (a la place de blancs) ;

# : utiliser le format alternatif, lorsqu’il existe (voir ci-dessous).

3. Facultativement, un nombre qui indique la largeur du champ. Il s’agit d’une largeur minimum, automatique-ment augmentee si elle se revele insuffisante.

4. Facultativement, un point suivi d’un nombre qui indique la precision :– dans les conversions e, E ou f : le nombre de chiffres a ecrire apres la virgule decimale ;– dans les conversions g ou G : le nombre de chiffres significatifs ;– pour une chaıne : le nombre maximum de caracteres a afficher ;– pour un entier (conversions d et u) : le nombre minimum de chiffres. Le cas echeant, des zeros seront

ajoutes a gauche.

5. Facultativement, une lettre qui complete l’information sur la nature de l’argument et, le cas echeant, modifieen consequence la largeur du champ :

h (associee a d ou u) : l’argument est un short. Cette indication est sans effet si un int et un short sontla meme chose ;

l (associee a d, u, x ou X) : l’argument est un long ou un unsigned long. Cette indication est sans effetsi un int et un long sont la meme chose ;

L (associee a f, e, E, g ou G) : l’argument est un long double.

6. Obligatoirement, un caractere de conversion, parmi

d : argument : int ; impression : notation decimale signee.

o : argument : int ; impression : notation octale non signee.Format alternatif (voir # au point 2) : un 0 est imprime au debut du nombre.

x : argument : int ; impression : notation hexadecimale non signee avec les chiffres 0...9abcdef.Format alternatif : le nombre est precede de 0x.

X : comme x mais avec ABCDEF et 0X a la place de abcdef et 0x.

u : argument : int ; impression : notation decimale non signee.

c : argument : unsigned char ; impression : un seul caractere.

s : argument : char * ; impression : chaıne de caracteres.

f : argument : double ; impression : notation en � virgule fixe � [-]zzz.fff. Le nombre de f est donne parla precision. Precision par defaut : 6. Si la precision vaut zero, le point est supprime.Format alternatif : le point est toujours imprime.

e : argument : double. Impression : notation en � virgule flottante � [-]z.ffffff e±nn. Le nombre de f estdonne par la precision. Precision par defaut : 6. Si la precision vaut zero, le point est supprime.Format alternatif : le point est toujours imprime.

E : comme %e avec E a la place de e.

g : argument : double ; impression : comme %e si l’exposant est inferieur a -4 ou superieur a la precision,comme %f sinon.Format alternatif : le point est toujours imprime.

G : comme %g avec E a la place de e.

p : argument : void * ; impression : depend de l’implantation (par exemple %X).

n : argument : int *. Aucune impression. Effet : l’argument correspondant doit etre l’adresse d’unevariable entiere, qui recevra pour valeur le nombre de caracteres effectivement ecrits jusqu’a presentpar l’appel en cours de la fonction printf.

% : pas d’argument correspondant ; impression : le caractere %.

Tab. 4 – Specifications de format pour printf, fprintf et sprintf

c© H. Garreta, 2003 85

Page 86: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.2 Lecture et ecriture textuelles 7 ENTREES-SORTIES

7.2.3 Lecture avec format scanf

La fonction de lecture avec format a l’unite d’entree standard (en principe le clavier du poste de travail) estscanf( format , adresse1 , adresse2 , ... adressen )

L’argument format est une chaıne qui indique la maniere de convertir les caracteres qui seront lus ; adresse1,adresse2, ... adressen indiquent les variables devant recevoir les donnees lues. Ce sont des � sorties � de lafonction scanf, par consequent il est essentiel que ces arguments soient des adresses de variables a lire.

La fonction scanf constituant un vrai analyseur lexical, les donnees lues ont de nombreuses occasions d’etreincorrectes et on peut s’attendre a ce que scanf soit d’un maniement difficile. L’experience ne decoit pas cetteattente ! Voici un bon conseil : si un programme ne fonctionne pas comme il le devrait, commencez par verifierles valeurs lues par la fonction scanf (par exemple avec des appels de printf suivant immediatement les appelsde scanf).

Fonctionnement : scanf parcourt le format. Elle rend la main lorsque la fin du format est atteinte ou surune erreur. Jusque-la :

– Tout caractere ordinaire du format, c’est-a-dire qui n’est ni un caractere d’espacement (blanc, tabulation)ni un caractere faisant partie d’une specification de format (commencant par %), doit s’identifier au ca-ractere courant du flot d’entree. Si cette identification a lieu, le caractere courant est lu, sans etre rangedans aucune variable, et le parcours du format se poursuit. Si l’identification n’a pas lieu, l’activation descanf se termine.

– Les specifications de format commencent par %. Elles indiquent la maniere d’analyser les caracteres lussur le flot d’entree et de ranger les valeurs ainsi obtenues. Voir le tableau 5.

– Dans le format, les caracteres ordinaires et les specifications peuvent etre separes entre eux par descaracteres d’espacement. Le nombre de ces espacements est sans importance, mais leur presence indiqueque les donnees correspondantes peuvent etre separees dans le flot d’entree par un nombre quelconque decaracteres d’espacement ou de fin de ligne.

– S’il n’y a pas d’espacement, dans le format, entre les caracteres ordinaires ou les specifications, alors lesdonnees correspondantes dans le flot d’entree doivent etre adjacentes.

– Cependant, les espacements au debut des nombres sont toujours sautes.

On peut melanger des appels de scanf et d’autres fonctions de lecture. Chaque appel d’une telle fonctioncommence par lire le premier caractere que l’appel precedent n’a pas � consomme �.

Exemple 1. Lecture d’un entier :scanf("%d", &x);

Exemple 2. Lecture de deux entiers separes par une virgule et encadres par une paire de parentheses :scanf("(%d,%d)", &x, &y);

Donnees correctes :(22,33) ou ( 22, 33)

(des blancs, mais uniquement devant les nombres). Donnees incorrectes :( 22 , 33 )

(trop de blancs).

Exemple 3. Comme ci-dessus mais avec une syntaxe plus souple (les blancs sont toleres partout) :scanf(" (%d ,%d )", &x, &y);

Toutes les donnees de l’exemple precedent sont correctes. Exemple de donnee incorrecte :( 22 ; 33 )

Exemple 4. Lecture d’un caractere55 :char calu;...scanf("%c", &calu);

Remarque. Nous avons indique par ailleurs qu’en C on range souvent les caracteres dans des variables detype int afin de permettre la memorisation d’une valeur � intruse �, EOF. Cependant, notez bien qu’ici noussommes obliges de declarer la variable calu de type char ; le programme

int calu;...scanf("%c", &calu);

55Rappelons que fgetc, getc et getchar font ce travail bien plus simplement.

86 c© H. Garreta, 2003

Page 87: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.2 Lecture et ecriture textuelles

Chaque specification de format se compose, dans l’ordre indique, des elements suivants :

1. Obligatoirement, le caractere %.

2. Facultativement, le caractere * qui indique une suppression d’affectation : lire une donnee comme indique etl’� oublier � (c’est-a-dire ne pas le ranger dans une variable).

3. Facultativement, un nombre donnant la largeur maximum du champ (utile, par exemple, pour la lecture denombres colles les uns aux autres).

4. Facultativement, une lettre qui apporte des informations complementaires sur la nature de l’argument cor-respondant :

h devant d, i, o, u, x : l’argument est l’adresse d’un short (et non pas l’adresse d’un int) ;

l devant d, i, o, u, x : l’argument est l’adresse d’un long (et non pas l’adresse d’un int).Devant e, f, g : l’argument est l’adresse d’un double (et non pas d’un float) ;

L devant e, f, g : l’argument est l’adresse d’un long double (et non pas d’un float).

5. Obligatoirement, un caractere de conversion parmi

d – Argument : int *. Donnee : nombre entier en notation decimale.

i – Argument : int *. Donnee : entier en notation decimale, octale (precede de 0) ou hexadecimale(precede de 0x ou 0X).

o – Argument : int *. Donnee : entier en octal (precede ou non de 0).

u – Argument : unsigned int *. Donnee : entier en notation decimale.

x – Argument : int *. Donnee : entier en notation hexadecimale (precede ou non de 0x ou 0X).

c – Argument : char *. Donnee : autant de caracteres que la largeur du champ l’indique (par defaut :1). Ces caracteres sont copies dans l’espace dont l’adresse est donnee en argument. Les blancs ettabulations ne sont pas pris pour des separateurs (ils sont copies comme les autres caracteres) et iln’y a pas de ’\0’ automatiquement ajoute a la fin de la chaıne.

s – Argument : char *. Donnee : chaıne de caracteres terminee par un caractere d’espacement (blanc,tabulation, fin de ligne) qui n’en fait pas partie. Un ’\0’ sera automatiquement ajoute apres lescaracteres lus.

e – Argument : float *. Donnee : nombre flottant, c’est-a-dire dans l’ordre : signe facultatif, suite dechiffres, point decimal et suite de chiffres facultatifs, exposant (e ou E, signe facultatif et suite dechiffres) facultatif.

f – Comme e.

g – Comme e.

p – Argument : void ** (ou void *, pour ce que ca change...). Donnee : nombre exprimant un pointeurcomme il serait imprime par printf avec le format %p.

n – Argument : int *. Aucune lecture. Effet : l’argument correspondant doit etre l’adresse d’une variableentiere ; celle-ci recoit pour valeur le nombre de caracteres effectivement lus jusqu’a ce point par lepresent appel de la fonction scanf.

[caractere...caractere] – Argument : char *. Donnee : la plus longue suite faite de caracteres appartenanta l’ensemblea indique entre crochets. Un caractere au moins sera lu. Un ’\0’ est ajoute a la fin dela chaıne lue.

[^caractere...caractere] – Argument : char *. Donnee : comme ci-dessus mais les caracteres permis sontmaintenant ceux qui n’appartiennent pas a l’ensemble indique.

% Pas d’argument. Le caractere % doit se presenter sur le flot d’entree.aPour specifier le caractere ] l’ecrire immediatement apres le [ initial.

Tab. 5 – Specifications de format pour scanf, fscanf et sscanf

aurait ete incorrect (bien qu’il puisse fonctionner correctement sur certains systemes), car on y fournit a scanfl’adresse d’un entier (fait de plusieurs octets) pour y ranger un caractere, alors que scanf, d’apres l’analyse duformat, � croit � recevoir l’adresse d’un char (un octet). Or on ne doit pas faire d’hypothese sur la maniere dontsont organises les octets qui composent un entier, le langage C ne precisant rien a ce sujet. Une autre solutioncorrecte aurait ete la suivante :

c© H. Garreta, 2003 87

Page 88: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.2 Lecture et ecriture textuelles 7 ENTREES-SORTIES

int calu;char tmp;...scanf("%c", &tmp);calu = tmp;

Exemple 5. Lecture d’un nombre entier et du caractere le suivant immediatement, quel qu’il soit :

int nblu;char calu;...scanf("%d%c", &nblu, &calu);

Exemple 6. Lecture d’un nombre entier et du premier caractere non blanc le suivant :

int nblu;char calu;...scanf("%d %c", &nblu, &calu);

7.2.4 A propos de la fonction scanf et des lectures interactives

A propos de � sauter les blancs �. Nous avons dit que les specifications qui constituent le formatpeuvent etre separees entre elles par un ou plusieurs blancs, ce qui indique que les donnees a lire correspondantespeuvent alors etre separees par un nombre quelconque de blancs.

Pour la fonction scanf, les blancs dans le format se traduisent donc par l’ordre � a present, sautez tousles caracteres blancs qui se presentent �. Or la seule maniere pour l’ordinateur de s’assurer qu’il a bien sautetous les caracteres blancs consiste a lire (sans le consommer) le premier caractere non blanc qui les suit. Si lalecture se fait dans un fichier, cela ne pose pas de probleme, les caracteres a lire existant tous des le depart.Mais cela peut creer des situations confuses dans le cas d’une lecture interactive mettant en œuvre le clavier etun operateur vivant. Exemple :

int x, y = 33;...printf("donne x : ");scanf(" %d ", &x); /* aıe... */printf("donne y : ");scanf("%d", &y);printf("x = %d, y = %d\n", x, y);

Parce qu’on a ecrit, peut-etre sans faire attention, des blancs apres le format %d, le premier appel de scanfci-dessus signifie � lisez un entier et tous les blancs qui le suivent �. L’operateur constatera avec etonnement quepour que son premier nombre sont pris en compte il est oblige de composer le deuxieme ! Exemple de dialogue(le signe ¶ representant la frappe de la touche � Entree �) :

donne x : 11¶¶¶ la machine ne reagit pas... ! ?22¶donne y : x = 11, y = 22

Notez que faire suivre le premier nombre d’un caractere non blanc conventionnel n’est pas une bonne solutioncar, ce caractere n’etant pas consomme, il fait echouer la lecture du second nombre :

donne x : 11¶donne y : x = 11, y = 33

Lecon a retenir : dans le format de scanf, les blancs ne sont pas de la decoration, ils jouent un role bienprecis.

A propos de la lecture d’un caractere. Une autre situation generatrice de confusion est celle ou deslectures de nombres sont suivies par des lectures de caracteres ou de chaınes. Par exemple, le programme naıfsuivant lit des nombres les uns a la suite des autres et affiche le carre de chacun :

88 c© H. Garreta, 2003

Page 89: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.2 Lecture et ecriture textuelles

int x, c;...do {

printf("donne un nombre : ");scanf("%d", &x); ← lecture d’un nombreprintf("%d ^ 2 = %d\n", x, x * x);printf("un autre calcul (O/N) ? ");c = getchar(); ← lecture d’un caractere

}while (c == ’O’);printf("au revoir...");\end{verbatim}

Voici un dialogue (la replique de l’operateur est soulignee, le signe ¶ represente la frappe de la touche �Entree �) :donne un nombre : 14¶14 ^ 2 = 196un autre calcul (O/N) ? au revoir...

La machine n’a pas attendu la reponse (O ou N) de l’operateur... ! Que s’est-il passe ? L’operateur a composeun nombre suivi immediatement d’un caractere � retour-chariot � (produit par la touche � Entree �). Ce retour-chariot a indique la fin de la lecture du nombre, mais il n’a pas ete consomme et est reste disponible pourdes lectures ulterieures. L’appel de getchar (qui, dans l’esprit du programmeur, aurait du provoquer unenouvelle lecture physique) trouve ce retour-chariot et l’affecte a c a titre de reponse. On voit que ce programmene s’executera correctement que si l’operateur compose, a la suite de l’invite donne un nombre, un nombreimmediatement suivi d’un caractere O ou N en reponse a une question qui ne lui a pas encore ete posee !

La solution de ce probleme consiste a vider le tampon d’entree avant de proceder a une lecture de caractere.Dans beaucoup de situations l’etat du tampon est connu, et une maniere simple de le vider consiste a le lire pargets :

int x, c;char s[BUFSIZ];...do {

printf("donne un nombre : ");scanf("%d", &x);

ici, le tampon contient (au moins) un ’\n’gets(s);

maintenant le tampon est videprintf("%d ^ 2 = %d\n", x, x * x);printf("un autre calcul (O/N) ? ");c = getchar();

}while (c == ’O’);printf("au revoir...");

7.2.5 Les variantes de printf et scanf

Voici la famille au complet, comme elle est declaree (en syntaxe ANSI) dans le fichier <stdio.h> :

int printf(const char *format, ... )

Nous avons decrit cette fonction. Ajoutons seulement qu’elle renvoie le nombre de caracteres effectivementecrits (on utilise rarement cette information) ou un nombre negatif en cas d’erreur.

int fprintf(FILE *flot, const char *format, ... )

Comme printf mais sur le flot indique (a la place de stdout). Ainsiprintf( format , exp1 , ... , expk )

equivaut afprintf(stdout, format , exp1 , ... , expk )

int sprintf(char *destination, const char *format, ... )

Cette fonction effectue les memes mises en forme que ses deux sœurs, avec la difference que les caracteresproduits ne sont pas ajoutes a un flot, mais sont concatenes ensemble dans la chaıne destination. Elle

c© H. Garreta, 2003 89

Page 90: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.3 Operations en mode binaire 7 ENTREES-SORTIES

permet donc de dissocier les deux fonctionnalites de printf, transcodage et ecriture des donnees, afind’utiliser la premiere sans la deuxieme.

int scanf(const char *format, ... )

Nous avons decrit cette fonction. Ajoutons qu’elle renvoie EOF si une fin de fichier ou une erreur a empechetoute lecture ; autrement elle rend le nombre de variables lues avec succes. Cette indication fournit unemaniere bien pratique pour detecter, dans les programmes simples, la fin d’une serie de donnees. Exemple :

for(;;) {printf("donne un nombre : ");if (scanf("%d", &x) < 1)

break;exploitation du nombre lu

}printf("au revoir...");

La boucle precedente sera arretee par n’importe quel caractere non blanc ne pouvant pas faire partie d’unnombre.

int fscanf(FILE *flot, const char *format, ... )

Comme scanf mais sur le flot indique (a la place de stdin). Ainsiscanf( format , exp1 , ... , expk )

equivaut afscanf(stdin, format , exp1 , ... , expk )

int sscanf(const char *source, const char *format, ... )

Cette fonction effectue la meme analyse lexicale que ses sœurs, avec la particularite que le texte analysen’est pas pris dans un flot, mais dans la chaıne source.

7.3 Operations en mode binaire

7.3.1 Lecture-ecriture

size t fread(void *destination, size t taille, size t nombre, FILE *flot)

Cette fonction essaye de lire sur le flot indique nombre objets, chacun ayant la taille indiquee, et lescopie les uns a la suite des autres dans l’espace pointe par destination. Elle renvoie le nombre d’objetseffectivement lus, qui peut etre inferieur au nombre demande, a cause de la rencontre de la fin du fichier56,d’une erreur, etc.

size t fwrite(const void *source, size t taille, size t nombre, FILE *flot)

Cette fonction ecrit les nombre objets, chacun ayant la taille indiquee, qui se trouvent les uns a la suitedes autres a l’adresse indiquee par source. Elle renvoie le nombre d’objets ecrits, qui peut etre inferieurau nombre demande (en cas d’erreur).

7.3.2 Positionnement dans les fichiers

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

Cette fonction positionne le pointeur du fichier associe au flot indique. La premiere lecture ou ecritureulterieure se fera a partir de la nouvelle position. Celle-ci est obtenue en ajoutant la valeur de deplacementa une valeur de base qui peut etre la position courante, la fin ou le debut du fichier. C’est l’argumentorigine qui indique de quelle base il s’agit ; la valeur de cet argument doit etre une des constantes (definiesdans <stdio.h>) :

SEEK SET : base = le debut du fichierSEEK CUR : base = la position couranteSEEK END : base = la fin du fichier

La fonction renvoie zero en cas de succes, une valeur non nulle en cas d’erreur. Sur un fichier de texte, ilvaut mieux utiliser getpos et setpos.Un appel de cette fonction sur un fichier en ecriture produit la vidange du tampon (fseek impliquefflush). Lire ci-apres les explications sur l’acces relatif aux fichiers.

56Lorsqu’un fichier est lu par des appels de la fonction fread, comparer avec zero le nombre d’objets effectivement lus est lamaniere la plus pratique de detecter la fin du fichier.

90 c© H. Garreta, 2003

Page 91: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.3 Operations en mode binaire

void rewind(FILE *flot)

Positionnement au debut du fichier du pointeur de fichier associe au flot indique. Un appel de cette fonctionequivaut a fseek(flot, 0L, SEEK SET) suivi de la mise a zero de tous les indicateurs d’erreur.

void fgetpos(FILE *flot, fpos t *ptr)

Place dans ptr la position courante dans le flot indique en vue de son utilisation par setpos. Le typefpos t (defini dans <stdio.h>) a une taille suffisante pour contenir une telle information.

void fsetpos(FILE *flot, const fpos t *ptr)

La valeur de ptr provenant d’un appel de getpos, cette fonction positionne le flot indique a l’emplacementcorrespondant.

L’acces relatif aux elements des fichiers. La principale application des fonctions de positionnementest la realisation de ce qu’on appelle l’acces relatif 57 ou parfois acces direct aux composantes d’un fichier :l’acces a une composante (on dit un enregistrement) que ce soit pour le lire ou pour l’ecrire, a partir de ladonnee de son rang dans le fichier sans etre oblige d’acceder au prealable aux enregistrements qui le precedent.

Pour donner une signification utilisable a la notion de � neme article �, il faut que le fichier puisse etre vucomme une suite d’articles de meme taille (mais la bibliotheque C ignorera ce fait ; le fichier restera une suited’octets). Cela s’obtient generalement en declarant une structure qui represente un article, selon le modele :

typedef struct {...champs dont se composent tous les articles...

} ARTICLE;...

Un fichier destine a etre utilise en acces relatif sera declare normalement, et ouvert de facon a autoriser leslectures et les ecritures :

FILE *fichier;...fichier = fopen(nom , "rb+");

Si les articles sont numerotes a partir de zero, l’operation � lecture de l’enregistrement de rang n � seprogramme alors selon le schema suivant :

ARTICLE article;...fseek(fichier, n * sizeof(ARTICLE), SEEK_SET);fread( & article, sizeof(ARTICLE), 1, fichier);...exploitation des valeurs des champs de article...

L’operation � ecriture de la composante de rang n � obeit au schema

ARTICLE article;...affectation des champs de article...fseek(fichier, n * sizeof(ARTICLE), SEEK_SET);fwrite( & article, sizeof(ARTICLE), 1, fichier);...

Attention. La possibilite d’effectuer des lectures et des ecritures sur le meme fichier pose des problemesdans la gestion des tampons associes au fichier que les fonctions de la bibliotheque standard ne resolvent pas.Ainsi chaque fois qu’une serie de lectures sur un fichier va etre suivie d’une serie d’ecritures sur le meme fichier,ou reciproquement, il appartient au programmeur de prevoir l’ecriture effective du tampon, soit par un appelexplicite de la fonction fflush, soit par un appel d’une fonction de positionnement comme fseek. Notez quecette contrainte est satisfaite si on pratique des acces relatifs selon les schemas indiques ci-dessus, puisqu’il y atoujours un appel de fseek entre deux acces au fichier, quel que soit leur mode.

57L’acces relatif s’oppose a l’acces sequentiel, dans lequel une composante ne peut etre lue ou ecrite autrement qu’a la suite dela lecture ou de l’ecriture de la composante qui la precede.

c© H. Garreta, 2003 91

Page 92: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.4 Exemples 7 ENTREES-SORTIES

7.4 Exemples

Les programmes suivants sont assez naıfs ; ils se proposent d’illustrer les fonctions de traitement de fichiers,aussi bien pour les fichiers de texte que pour les fichiers binaires.

7.4.1 Fichiers “en vrac”

Le programme suivant fait une copie identique d’un fichier (ce programme n’est pas bien utile, generalementune fonction du systeme d’exploitation fait ce travail) :

#include <stdio.h>

#define PAS_D_ERREUR 0 /* codes conventionnels */#define ERREUR_OUVERTURE 1 /* a l’usage du systeme */#define ERREUR_CREATION 2 /* d’exploitation */

FILE *srce, *dest;

main() {char tampon[512];int nombre;

printf("source : ");gets(tampon);srce = fopen(tampon, "rb");if (srce == NULL)

return ERREUR_OUVERTURE;

printf("destination : ");gets(tampon);dest = fopen(tampon, "wb");if (dest == NULL)

return ERREUR_CREATION;

while ((nombre = fread(tampon, 1, 512, srce)) > 0)fwrite(tampon, 1, nombre, dest);

fclose(dest);fclose(srce);return PAS_D_ERREUR;

}

L’essentiel de ce programme est sa boucle while. Elle se compose d’un appel de fread demandant la lecturede 512 octets, suivi d’un appel de fwrite pour ecrire les nombre octets effectivement lus. Supposons que lefichier a copier comporte N octets ; ces deux operations seront executees N

512 fois avec nombre egal a 512 puis(sauf si N est multiple de 512) une derniere fois avec nombre egal au reste de la division de N par 512.

7.4.2 Fichiers binaires et fichiers de texte

L’objet du programme suivant est la constitution d’un fichier d’articles a partir d’un fichier de texte qui sepresente comme une repetition du groupe suivant :

– un nom et un prenom sur la meme ligne ;– une adresse sur la ligne suivante ;– un entier seul sur une ligne, exprimant un certain � nombre de passages � (a un peage autoroutier).

Exemple

TOMBAL PierreRue de la Gaiete de Vivre10

92 c© H. Garreta, 2003

Page 93: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.4 Exemples

MENSOIF GerardAllee ’Les verts’20

etc.

Le programme est independant de la nature du fichier de texte, qui peut etre aussi bien le clavier d’unterminal qu’un fichier existant par ailleurs.

/* constitution d’un fichier d’articles *//* a partir d’un fichier de texte */

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

#define PAS_D_ERREUR 0#define ERREUR_OUVERTURE 1#define ERREUR_CREATION 2#define ERREUR_ECRITURE 3#define ERREUR_FERMETURE 4

struct /* un article du fichier */{char nom[32], prenom[32];char adresse[80];int nombre_de_passages;} art;

FILE *srce; /* fichier de texte */FILE *dest; /* fichier binaire */

main() {char nom[256];

printf("source : ");gets(nom);if ((srce = fopen(nom, "r")) == NULL)

exit(ERREUR_OUVERTURE);printf("destination : ");gets(nom);if ((dest = fopen(nom, "wb")) == NULL)

exit(ERREUR_CREATION);

for (;;) {if (fscanf(srce, " %s %s ", art.nom, art.prenom) != 2)

break;fgets(art.adresse, 80, srce);

/* enlevons le ’\n’ : */art.adresse[strlen(art.adresse) - 1] = ’\0’;fscanf(srce, " %d ", &art.nombre_de_passages);

if (fwrite(&art, sizeof art, 1, dest) != 1)exit(ERREUR_ECRITURE);

}if (fclose(dest) != 0)

exit(ERREUR_ECRITURE);exit(PAS_D_ERREUR);

}

Notez les diverses fonctions qui ont ete utilisees pour lire du texte. Le nombre de passages est lu par scanf,car cette fonction est la seule qui convertit les nombres. Le nom et le prenom sont lus par scanf aussi, car celanous arrange bien que les blancs soient pris pour des separateurs et enleves. Mais l’adresse est lue par gets, carelle peut contenir des blancs qui ne doivent pas etre supprimes.

c© H. Garreta, 2003 93

Page 94: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7.5 Les fichiers de bas niveau d’UNIX 7 ENTREES-SORTIES

7.4.3 Fichiers en acces relatif

Dans le programme precedent nous avons vu un fichier binaire organise en articles et traite sequentiellement(chaque article etant ecrit a la suite du precedent). Voyons un exemple ou ce meme fichier est traite en accesrelatif.

Imaginons que le fichier cree a l’exemple precedent soit le fichier des abonnes a un certain peage, et quenous disposions par ailleurs d’un fichier binaire contenant les numeros des abonnes qui ont emprunte ce peagedurant une periode donnee. On nous demande d’ecrire un programme pour incrementer la valeur du champnbr passages de chaque abonne concerne.

#include <stdio.h>

#define PAS_D_ERREUR 0#define ERREUR_OUVERTURE_ABONNES 1#define ERREUR_OUVERTURE_PASSAGES 2

struct{char nom[32], prenom[32];char adresse[80];int nombre_de_passages;} art;

FILE *passages, *abonnes;

main() {char nom[256];long n;

printf("fichier des passages : ");if ((passages = fopen(gets(nom), "rb")) == NULL)

exit(ERREUR_OUVERTURE_PASSAGES);printf("fichier des abonnes : ");if ((abonnes = fopen(gets(nom), "rb+")) == NULL)

exit(ERREUR_OUVERTURE_ABONNES);

for (;;) {if (fread(&n, sizeof n, 1, passages) != 1)

break;fseek(abonnes, n * sizeof art, SEEK_SET);fread(&art, sizeof art, 1, abonnes);art.nombre_de_passages++;fseek(abonnes, n * sizeof art, SEEK_SET);fwrite(&art, sizeof art, 1, abonnes);}

fclose(abonnes);exit(PAS_D_ERREUR);

}

7.5 Les fichiers de bas niveau d’UNIX

Les fichiers de bas niveau du systeme UNIX ne sont pas fondamentalement distincts des flots que l’on vientde decrire. En fait, les flots et les fichiers de bas niveau sont deux manieres de voir depuis un programme lesmemes entites de base (les fichiers). Les fichiers de bas niveau sont la maniere habituelle de realiser les fichiersbinaires lorsqu’on ne possede pas un C ANSI.

Dans le systeme UNIX chaque processus dispose, pour gerer ses fichiers, d’une table de descripteurs defichiers. Lorsque les fichiers sont geres au plus bas niveau, ils sont representes tout simplement par un indice danscette table. Par exemple, les trois unites standard stdin, stdout et stderr sont respectivement representeesen tant que fichiers par les descripteurs d’indices 0, 1 et 2.

94 c© H. Garreta, 2003

Page 95: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

7 ENTREES-SORTIES 7.5 Les fichiers de bas niveau d’UNIX

Les fonctions disponibles sont :

int open(char *nom, int mode, int permissions)

Ouvre un fichier existant ayant le nom externe indique. L’argument mode doit avoir pour valeur une desconstantes symboliques (definies dans <ioctl.h> ou dans <sys/file.h>) :

O RDONLY : ouverture en lecture seulementO WRONLY : ouverture en ecriture seulementO RDWR : ouverture en lecture / ecriture

Cette fonction renvoie le numero du descripteur ouvert pour le fichier, ou un nombre negatif en cas d’erreur.Pour l’argument permissions voir ci-dessous.

int creat(char *nom, int permissions)

Cree un fichier nouveau, ayant le nom indique, et l’ouvre en ecriture. Comme pour la fonction open,l’argument permissions indique que le droit de lecture, le droit d’ecriture ou le droit d’execution estaccorde ou non au proprietaire du fichier, aux membres de son groupe de travail ou a tous les utilisateursdu systeme. Cela fait trois groupes de trois bits, soit un nombre qui s’ecrit particulierement bien en octal.Par exemple :

fic = creat("tmp/monfic", 0751);

cree un fichier nouveau et lui accorde les permissions :

lecture ecriture execution en octalproprietaire 1 1 1 7groupe 1 0 1 5autres 0 0 1 1

int read(int fichier, char *adresse, int nombre)

Lit nombre octets depuis le fichier indique et les range a partir de l’adresse indiquee. Renvoie le nombred’octets effectivement lus, qui peut etre inferieur a nombre (fin de fichier, erreur de lecture, etc.).

int write(int fichier, void *adresse, int nombre)

Ecrit les nombre octets qui se trouvent a l’adresse indiquee dans le fichier indique. Rend le nombred’octets effectivement ecrits, qui peut etre inferieur a nombre (erreur d’ecriture, etc.).

int close(int descfic)

Ferme le fichier indique.

long lseek(int fichier, long rang, int origine)

Positionne le fichier indique sur l’octet ayant le rang indique. Ce rang exprime une position......relative au debut du fichier (si origine = 0)...relative a la position courante (si origine = 1)...relative a la fin du fichier (si origine = 2)

Exemple. Voici la version UNIX (et, de plus, non ANSI) du programme, donne plus haut, qui effectue unecopie identique d’un fichier quelconque. Au sujet de l’utilisation qui est faite des arguments de main voir lasection 8.3.1.

main(int argc, char *argv[]) {int srce, dest, n;char tampon[512];if (argc < 3

|| (srce = open(argv[1], 0, 0777)) < 0|| (dest = creat(argv[2], 0777)) < 0)

return 1;while ((n = read(srce, tampon, 512)) > 0)

write(dest, tampon, n);close(dest);close(srce);return 0;

}

c© H. Garreta, 2003 95

Page 96: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL”

8 “Genie logiciel”

Sous un titre tres pompeux (et injustifie) cette section regroupe un ensemble de notions ayant en communque leur interet apparaıt surtout a l’occasion de l’ecriture de gros programmes. Ce sont des elements importantsdu langage mais, sauf quelques exceptions, on peut s’en passer aussi longtemps qu’on limite la pratique de C ades exercices de petite taille.

8.1 Le preprocesseur

Le preprocesseur transforme le texte source d’un programme avant que la compilation ne commence.Generalement associees au processus de la compilation, ses actions semblent en faire partie, mais il faut sa-voir que le preprocesseur est un programme separe, largement independant du compilateur. En particulier lepreprocesseur ne connaıt pas la syntaxe du langage C. Les transformations qu’il effectue sur un programmesont simplement des ajouts, des suppressions et des remplacements de morceaux de texte nullement astreints acorrespondre aux entites syntaxiques du langage.

Les directives destinees au preprocesseur comportent, dans l’ordre :– un signe # (qui, en syntaxe originale, doit occuper la premiere position de la ligne) ;– un mot-cle parmi include, define, if, ifdef, ifndef, else et endif qui identifie la directive. Nous

passerons sous silence quelques autres directives mineures comme pragma, line ou error ;– le corps de la directive, dont la structure depend de la nature de celle-ci.

8.1.1 Inclusion de fichiers

La possibilite d’inclure des fichiers sources dans d’autres fichiers sources s’avere tres utile, par exemplepour inserer dans chacun des fichiers separes qui constituent un programme l’ensemble de leurs declarationscommunes (types, structures, etc.). Ainsi ces definitions sont ecrites dans un seul fichier, ce qui en facilite lamaintenance.

Un cas particulier tres important de ce partage des declarations est celui des bibliotheques livrees avec lecompilateur. Elles sont essentiellement composees de fonctions utilitaires deja compilees dont seul le fichier objetest fourni. Pour que les appels de ces fonctions puissent etre correctement compiles58 dans des programmes quiles utilisent, un certain nombre de fichiers en-tete sont livres avec les bibliotheques, comprenant

– principalement, les prototypes des fonctions qui forment la bibliotheque ou qui incarnent une fonctionnaliteparticuliere (les entrees - sorties, la gestion des chaınes de caracteres, la bibliotheque mathematique, etc.) ;

– souvent, un ensemble de directives #define et de declarations struct, union et typedef qui definissentles constantes et les types necessaires pour utiliser la bibliotheque en question ;

– parfois, quelques directives #include concernant d’autres fichiers en-tete lorsque les elements definis dansle fichier en-tete en question ne peuvent eux-memes etre compris sans ces autres fichiers ;

– plus rarement, quelques declarations de variables externes.

Cette directive possede deux formes :

#include "nom-de-fichier"

C’est la forme generale. Le fichier specifie est insere a l’endroit ou la directive figure avant que la compilationne commence ; ce doit etre un fichier source ecrit en C. La chaıne de caracteres "nom-de-fichier" doit etre unnom complet et correct pour le systeme d’exploitation utilise.

La deuxieme forme est :

#include <nom-de-fichier>

Ici, le nom est incomplet ; il ne mentionne pas le chemin d’acces au fichier, car il est convenu qu’il s’agit d’unfichier appartenant au systeme, faisant partie de la bibliotheque standard : le preprocesseur � sait � dans quelsrepertoires ces fichiers sont ranges. Ce sont des fichiers dont le nom se termine par .h (pour header, en-tete) etqui contiennent les declarations necessaires pour qu’un programme puisse utiliser correctement les fonctions dela bibliotheque standard.

Exemples (avec des noms de fichier en syntaxe UNIX) :

58Notez bien qu’il ne s’agit pas de fournir les fonctions de la bibliotheque (cela est fait par l’apport du fichier objet au moment del’edition de liens) mais uniquement de donner l’information requise pour que les appels de ces fonctions puissent etre correctementtraduits par le compilateur. Pour cette raison on ne trouve jamais dans les fichiers en-tete ni des variables non externes, ni le corpsdes fonctions.

96 c© H. Garreta, 2003

Page 97: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.1 Le preprocesseur

#include <stdio.h>#include "/users/henri/projet_grandiose/gadgets.h"

Attention. Il ne faut pas confondre inclusion de fichiers et compilation separee. Les fichiers que l’on inclutau moyen de la directive #include contiennent du texte source C qui sera compile chaque fois que l’inclusionsera faite (c’est pourquoi ces fichiers ne contiennent jamais de fonctions). Le service rendu par la directive#include est de permettre d’avoir un texte en un seul exemplaire, non de permettre de le compiler une seulefois.

8.1.2 Definition et appel des “macros”

Les macros sans argument59 se definissent par la directive :

#define nom corps

Le nom de la macro est un identificateur. Le corps de la macro s’etend jusqu’a la fin de la ligne60. Partoutdans le texte ou le nom de la macro apparaıt en qualite d’identificateur (ce qui exclut les commentaires, leschaınes de caracteres et les occurrences ou le nom de la macro est colle a un autre identificateur), il sera remplacepar le corps de la macro, lequel est un texte quelconque n’ayant a obeir a aucune syntaxe. Par exemple, entrel’endroit ou figurent les deux directives suivantes :

#define NBLIGNES 15#define NBCOLONNES (2 * NBLIGNES)

et la fin du fichier ou elles apparaissent, chaque occurrence de l’identificateur NBLIGNES sera remplacee par letexte 15 et chaque occurrence de l’identificateur NBCOLONNES par le texte (2 * 15). Au moins d’un point devue logique, ces remplacements se feront avant que la compilation ne commence. Ainsi, a la place de

double matrice[NBLIGNES][NBCOLONNES];

le compilateur trouvera

double matrice[15][(2 * 15)];

(il remplacera immediatement 2 * 15 par 30).

Attention. Voici une erreur que l’on peut faire :

#define NBLIGNES 15;#define NBCOLONNES (2 * NBLIGNES);

C’est une erreur difficile a deceler, car le compilateur ne signalera ici rien de particulier. Mais plus loin il donnerapour erronee une declaration comme :

double matrice[NBLIGNES][NBCOLONNES];

En effet, cette expression qui paraıt sans defaut aura ete transformee par le preprocesseur en61

double matrice[15;][(2 * 15;);];

Macros avec arguments. Elles se definissent par des expressions de la forme :

#define nom(ident1, ... identk) corps

Il ne doit pas y avoir de blanc entre le nom de la macro et la parenthese ouvrante. Comme precedemment,le corps de la macro s’etend jusqu’a la fin de la ligne. Les identificateurs entre parentheses sont les argumentsde la macro ; ils apparaissent aussi dans le corps de celle-ci. Les utilisations (on dit les appels) de la macro sefont sous la forme

nom(texte1, ... textek)

Cette expression sera remplacee par le corps de la macro, dans lequel ident1 aura ete remplace par texte1,ident2 par texte2, etc. Exemple62 :

#define permuter(a, b, type) { type w_; w_ = a; a = b; b = w_; }

Exemple d’appel :

permuter(t[i], t[j], short *)

59Le mot macro est une abreviation de l’expression � macro-instruction � designant un mecanisme qui existe depuis longtempsdans beaucoup d’assembleurs. Les macros avec arguments sont appelees aussi pseudo-fonctions.

60Mais il decoule de la section 1.2.1 que le corps d’une macro peut occuper en fait plusieurs lignes : il suffit que chacune d’elles,sauf la derniere, se termine par le caractere \.

61Pour permettre de trouver ce genre d’erreurs, certains compilateurs C possedent une option (sous UNIX l’option -E) qui produitl’affichage du programme source uniquement transforme par le preprocesseur.

62Dans cet exemple, les accolades { et } donnent au corps de la macro une structure de bloc qui sera comprise et exploitee parle compilateur, mais cette structure est parfaitement indifferente au preprocesseur.

c© H. Garreta, 2003 97

Page 98: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.1 Le preprocesseur 8 “GENIE LOGICIEL”

Resultat de la substitution (on dit aussi developpement) de la macro :

{ short * w_; w_ = t[i]; t[i] = t[j]; t[j] = w_; }

Les appels de macros ressemblent beaucoup aux appels de fonctions. La meme facilite, ne pas recrire uncertain code source, peut etre obtenue avec une macro et avec une fonction. Mais il faut comprendre que ce n’estpas du tout la meme chose. Alors que le corps d’une fonction figure en un seul exemplaire dans le programmeexecutable dont elle fait partie, le corps d’une macro est recopie puis compile a chaque endroit ou figure l’appelde la macro.

Il faut donc que le corps d’une macro soit reduit, sans quoi son utilisation peut finir par etre tres onereuseen termes d’espace occupe. Un autre inconvenient des macros est que leur definition � hors syntaxe � les rendtres difficiles a maıtriser au-dela d’un certain degre de complexite (pas de variables locales, etc.).

En faveur des macros : leur efficacite. Le texte de la macro etant recopie a la place de l’appel, on gagne achaque fois le cout d’un appel de fonction. Il eut ete bien peu efficace d’appeler une fonction pour ne faire que lapermutation de deux variables ! Autre interet des macros, elles sont independantes des types de leurs argumentsou meme, comme ci-dessus, elles peuvent avoir un type pour argument.

En definitive, la principale utilite des macros est d’ameliorer l’expressivite et la portabilite des programmesen centralisant la definition d’operations compliquees, proches du materiel ou sujettes a modification. Voici unexemple elementaire : en C original (dans lequel on ne dispose pas du type void *) la fonction malloc doit etregeneralement utilisee en association avec un changement de type :

p = (machin *) malloc(sizeof(machin));

Des telles expressions peuvent figurer a de nombreux endroits d’un programme. Si nous definissons la macro

#define NOUVEAU(type) ((type *) malloc(sizeof(type)))

alors les allocations d’espace memoire s’ecriront de maniere bien plus simple et expressive

p = NOUVEAU(machin);

De plus, un eventuel remplacement ulterieur de malloc par un autre procede d’obtention de memoire serafacile et fiable, puisqu’il n’y aura qu’un point du programme a modifier.

Attention. 1. Il est quasiment obligatoire d’ecrire les arguments et le corps des macros entre parentheses,pour eviter des problemes lies aux priorites des operateurs. Par exemple, la macro

#define TVA(p) 0.196 * p

est tres imprudente, car une expression comme (int) TVA(x) sera developpee en (int) 0.196 * x, ce qui vauttoujours 0. Premiere correction :

#define TVA(p) (0.186 * p)

Le probleme precedent est corrige, mais il y en a un autre : l’expression TVA(p1 + p2) est developpee en(0.186 * p1 + p2), ce qui est probablement errone. D’ou la forme habituelle :

#define TVA(p) (0.186 * (p))

2. Il ne faut jamais appeler une macro inconnue avec des arguments qui ont un effet de bord, car ceux-cipeuvent etre evalues plus d’une fois. Exemple classique : on dispose d’une macro nommee toupper qui transformetoute lettre minuscule en la majuscule correspondante, et ne fait rien aux autres caracteres. Elle pourrait etreainsi definie (mais on n’est pas cense le savoir) :

#define toupper(c) (’a’ <= (c) && (c) <= ’z’ ? (c) + (’A’ - ’a’) : (c))

On voit que l’evaluation de toupper(c) comporte au moins deux evaluations de c. Voici un tres mauvais appelde cette macro :

calu = toupper(getchar())

En effet, cette expression se developpe en

calu = (’a’ <= (getchar()) && (getchar()) <= ’z’? (getchar()) + (’A’ - ’a’) : (getchar()));

A chaque appel, au moins deux caracteres sont lus, ce qui n’est surement pas l’effet recherche. On noteraque si toupper avait ete une fonction, l’expression toupper(getchar()) aurait ete tout a fait correcte. Maispuisque ce n’en est pas une, il faut l’appeler avec un argument sans effet de bord. Par exemple :

{ calu = getchar(); calu = toupper(calu); }

98 c© H. Garreta, 2003

Page 99: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.1 Le preprocesseur

8.1.3 Compilation conditionnelle

Les directives de conditionnelle (compilation) se presentent sous les formes suivantes :

#if expression#ifdef identificateur#ifndef identificateur

...texte compile si la condition est vraie, ignore si elle est fausse...

#else...texte compile si la condition est fausse, ignore si elle est vraie...

#endif

La partie else est facultative ; on a donc droit aussi aux formes :

#if expression#ifdef identificateur#ifndef identificateur

...texte compile si la condition est vraie, ignore si elle est fausse...

#endif

Lorsque le premier element de cette construction est #if expression, expression doit pouvoir etre evalueepar le preprocesseur. Ce doit donc etre une expression constante ne contenant pas l’operateur de changementde type ni les operateurs sizeof ou &. Elle sera evaluee et

– si elle est vraie, c’est-a-dire non nulle, alors le texte qui se trouve entre #if et #else sera traite par lecompilateur, tandis que celui qui figure entre #else et #endif sera purement et simplement tenu pourinexistant ;

– si elle est fausse, c’est-a-dire nulle, c’est le premier texte qui sera ignore tandis que le second sera lu parle compilateur. Dans la forme sans #else, si la condition est fausse aucun texte ne sera compile.

Les directives

#ifdef identificateur#ifndef identificateur

equivalent respectivement a

#if � identificateur est le nom d’une macro actuellement definie �#if � identificateur n’est pas le nom d’une macro actuellement definie �

La compilation conditionnelle se revele etre un outil precieux pour controler les parties des programmesqui dependent de la machine ou du systeme sous-jacent. On obtient de la sorte des textes sources qui restentportables malgre le fait qu’ils contiennent des elements non portables. Voici un exemple : les fonctions de lecture-ecriture sur des fichiers binaires ne sont pas les memes dans la bibliotheque ANSI et dans la bibliotheque UNIXclassique. Une maniere d’ecrire un programme independant de ce fait consiste a enfermer les appels de cesfonctions et les declarations qui s’y rapportent dans des sequences conditionnelles. Declaration :

...#ifdef BINAIRES_ANSI

FILE *fic;#else

int fic;#endif...

Ouverture :

c© H. Garreta, 2003 99

Page 100: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.2 La modularite de C 8 “GENIE LOGICIEL”

...#ifdef BINAIRES_ANSI

fic = fopen(nom, "r");ok = (fic != NULL);

#elsefic = open(nom, 0);ok = (fic >= 0);

#endif...

Lecture :

...#ifndef BINAIRES_ANSI

ok = (fread(&art, sizeof(art), 1, fic) == 1);#else

ok = (read(fic, &art, sizeof(art)) == sizeof(art));#endif...

L’emploi de la compilation conditionnelle associee a la definition de noms de macros est rendu encore pluscommode par le fait qu’on peut utiliser

– des noms de macros normalement definis (#define...) ;– des noms de macros definis dans la commande qui lance la compilation, ce qui permet de changer le texte

qui sera compile sans toucher au fichier source. De plus, puisque ces definitions se font au niveau de lacommande de compilation, elles pourront etre faites par des procedures de commandes (scripts) doncautomatisees ;

– un ensemble de noms de macros predefinis dans chaque systeme, pour le caracteriser. Par exemple dansle compilateur des systemes UNIX la macro UNIX est definie et vaut 1.

Remarque. Le preprocesseur du C ANSI offre egalement l’operateur defined qui permet d’ecrire les di-rectives #ifdef ident (resp. #ifndef ident) sous la forme #if defined ident (resp. #if !defined ident).D’autre part, la construction

#if expression1...#else#if expression2...#endif#endif

peut en C ANSI s’abreger en

#if expression1...#elif expression2...#endif

8.2 La modularite de C

A ce point de notre expose nous pouvons nous poser une question tout a fait dans l’air du temps : le langageC est-il modulaire ? La modularite est une qualite que les bonnes methodes de conception doivent posseder. Ellen’est pas facile a definir ; on peut toutefois la cerner a travers quelques criteres63. Une methode de conceptiondoit

– aider a diviser chaque nouveau probleme en sous-problemes qu’on peut resoudre separement (critere dedecomposabilite) ;

– favoriser la production de composants logiciels qui peuvent se combiner librement pour produire de nou-veaux systemes (critere de composabilite) ;

– permettre au concepteur d’ecrire des modules dont chacun peut etre compris isolement par un lecteurhumain (critere de comprehensibilite) ;

63Suivant l’analyse de Bertrand Meyer (Conception et programmation par objets, InterEditions, 1990) a laquelle il semble difficiled’ajouter ou de retrancher quelque chose.

100 c© H. Garreta, 2003

Page 101: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.2 La modularite de C

– permettre qu’une petite modification des specifications du probleme entraıne uniquement la modificationd’un petit nombre des modules de la solution (critere de continuite) ;

– assurer que l’effet d’une condition anormale se produisant dans un module restera localise a ce moduleou, au pire, n’atteindra qu’un petit nombre de modules � voisins � ( critere de protection).

Theoriquement, tout langage de programmation peut servir a mettre en œuvre une methode de conceptionmodulaire, mais dans certains cas il faut une telle dose de ruse et d’autodiscipline, pour un resultat si peu fiable,que le jeu n’en vaut pas la chandelle. En pratique un langage est dit modulaire lorsqu’on peut, sans douleuret sans artifice, en faire l’outil d’une methode de conception modulaire. L’experience montre que les criteresprecedents induisent sur le langage en question quelques contraintes assez precises, comme celles-ci :

– les modules doivent correspondre a des entites syntaxiques du langage ;– chaque module doit partager des informations avec aussi peu d’autres modules que possible, et quand un

tel partage existe il doit concerner aussi peu d’elements que possible ;– quand deux modules partagent des informations, cela doit etre clairement indique dans leurs deux textes.

A la lumiere de ces principes la modularite de C apparaıt fort rudimentaire, pour ne pas dire inexistante.Si l’on prend pour modules les fichiers sources qui composent un programme, on constate qu’aucune structuresyntaxique ne signale les modules ni n’en delimite la portee, d’autant plus que le caractere non syntaxique de ladirective #include brouille l’organisation du programme en fichiers distincts. Aucune declaration particulieren’est requise pour indiquer que des objets sont partages entre plusieurs modules. Chaque module communiqueavec tous les autres et, sauf specification contraire, tous les objets de chaque module sont partages.

Bien sur, la compilation separee est une des idees-cles du langage, et il est possible de rendre inaccessiblesles noms qui peuvent etre prives. Mais le langage offre peu d’outils pour rendre fiable le partage des noms quidoivent etre publics, et ces outils restent d’un emploi facultatif, subordonne a l’autodiscipline du programmeur.Par exemple, si dans un module B on doit referencer une variable ou une fonction definie dans un module A, ilsuffit d’ecrire dans B une declaration comme extern int x ;. Cet enonce postule l’existence d’un objet nommex, ce qui sera controle par l’editeur de liens. Mais il donne a x des attributs (la classe variable, le type int) quel’objet designe par x ne possede pas forcement ; aucune verification cependant ne sera faite. Ainsi la compilationde B se fait dans la plus totale insecurite.

8.2.1 Fichiers en-tete

Le seul moyen dont dispose l’auteur d’un module A pour s’assurer que les autres modules qui forment unprogramme utilisent correctement les variables et fonctions qu’il rend publiques consiste a ecrire un fichier en-tete (fichier A.h) contenant toutes les declarations publiques. Ce fichier doit etre inclus par la directive #includedans le module qui implante les objets publics (fichier A.c) et dans chacun des modules qui les utilisent. Decette maniere tous ces fichiers � voient � les memes definitions de types, les memes declarations de variableset les memes prototypes de fonctions ; ces declarations sont ecrites en un seul endroit, et toute modification del’une d’entre elles se repercute sur tous les fichiers qui en dependent.

La necessite de ces fichiers en-tete apparaıt encore plus grande quand on considere le cas des bibliotheques,c’est-a-dire des modules que leurs fonctionnalites placent en position de prestataires de services vis-a-vis desautres modules qui composent un programme ; on parle alors de module serveur et de modules clients. En fait,on peut presque toujours voir la modularite en termes de serveurs et clients, car il y a toujours une hierarchieparmi les modules. Le propre des bibliotheques est d’etre concues de maniere independante des clients, afin depouvoir etre utilisees dans un programme present et un nombre quelconque de programmes futurs. L’interet deleur associer le meilleur dispositif pour minimiser le risque de mauvaise utilisation est evident.

Typiquement, un fichier en-tete comportera les elements suivants :– Des directives #include concernant les autres fichiers en-tete necessaires pour la comprehension (par le

compilateur) des elements qui apparaissent dans le fichier en-tete en question.– Des definitions de constantes, soit sous forme de directives #define soit sous forme de type enumere, qui

sont des informations symboliques echangees entre le serveur et ses clients. Exemple : dans une bibliothequegraphique, les noms conventionnels des couleurs.

– Des definitions de structures (struct, union) et de types (typedef) qui definissent la nature des objetsmanipules par la bibliotheque. Typiquement, ces types permettent aux clients de declarer les objets quisont les arguments et les resultats des fonctions de la bibliotheque. Exemple : dans une bibliothequegraphique, la definition de types point, segment, etc.

– Les declarations extern des variables publiques du serveur. Les definitions correspondantes (sans le quali-fieur extern) figureront dans le module serveur.Remarque. L’emploi de variables publiques est deconseille ; un module ne devrait offrir que des fonc-

c© H. Garreta, 2003 101

Page 102: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.2 La modularite de C 8 “GENIE LOGICIEL”

tions64.– Les declarations des fonctions publiques du serveur. Les definitions correspondantes figureront dans le

module serveur. En syntaxe originale seules les declarations des fonctions qui ne rendent pas un entiersont necessaires, mais meme dans ce cas c’est une bonne habitude que d’y mettre les declarations de toutesles fonctions, cela constitue un germe de documentation.

Bien entendu, tous les noms de variables et fonctions du module serveur qui ne figurent pas dans le fichieren-tete doivent etre rendus prives (en les qualifiant static).

8.2.2 Exemple : stdio.h

A titre d’exemple visitons rapidement le plus utilise des fichiers en-tete de la bibliotheque standard : stdio.h.Notre but n’est pas de prolonger ici l’etude des entrees-sorties, mais d’illustrer les indications du paragrapheprecedent sur l’ecriture des fichiers en-tete. Le texte ci-apres est fait de morceaux de la version MPW (MacintoshProgrammer Workshop) du fichier stdio.h auquel nous avons enleve ce qui ne sert pas notre propos :

/* stdIO.h -- Standard C I/O Package* Modified for use with Macintosh C* Apple Computer, Inc. 1985-1988** Copyright American Telephone & Telegraph* Used with permission, Apple Computer Inc. (1985)* All rights reserved.*/

#ifndef __STDIO__ α#define __STDIO__

#include <stddef.h>#include <stdarg.h>

/** Miscellaneous constants*/

#define EOF (-1)#define BUFSIZ 1024#define SEEK_CUR 1#define SEEK_END 2#define SEEK_SET 0

/** The basic data structure for a stream is the FILE.*/

typedef struct { βint _cnt;unsigned char *_ptr;unsigned char *_base;unsigned char *_end;unsigned short _size;unsigned short _flag;unsigned short _file;

} FILE;

/** Things used internally:*/

extern FILE _iob[]; γint _filbuf (FILE *);int _flsbuf (int, FILE *);

64S’il est utile qu’un client puisse consulter ou modifier une variable du serveur, ecrivez une fonction qui ne fait que cela (maisen controlant la validite de la consultation ou de la modification). C’est bien plus sur que de donner libre acces a la variable.

102 c© H. Garreta, 2003

Page 103: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.2 La modularite de C

/** The standard predefined streams*/

#define stdin (&_iob[0])#define stdout (&_iob[1])#define stderr (&_iob[2])

/** File access functions*/

int fclose (FILE *); δint fflush (FILE *);FILE *fopen (const char *, const char *);int setvbuf (FILE *, char *, int, size_t);

/** Formatted input/output functions*/

int printf (const char *, ...); εint fprintf (FILE *, const char *, ...);int sprintf (char *, const char *, ...);int scanf (const char *, ...);int fscanf (FILE *, const char *, ...);int sscanf (const char *, const char *, ...);

/** Character input/output functions and macros*/

int fgetc (FILE *);int ungetc (int, FILE *);int fputc (int, FILE *);char *fgets (char *, int, FILE *);char *gets (char *);int puts (const char *);int fputs (const char *, FILE *);#define getc(p) (--(p)->_cnt >= 0 \

? (int) *(p)->_ptr++ : _filbuf(p))#define getchar() getc(stdin)#define putc(x, p) (--(p)->_cnt >= 0 \

? ((int) (*(p)->_ptr++ = (unsigned char) (x))) \: _flsbuf((unsigned char) (x), (p)))

#define putchar(x) putc((x), stdout)

/** Direct input/output functions*/

size_t fread (void *, size_t, size_t, FILE *);size_t fwrite (const void *, size_t, size_t, FILE *);#endif __STDIO__

Renvois :

α Lorsque les fichiers en-tete sont d’un interet general ils finissent par etre inclus dans de nombreux autresfichiers, eux-memes inclus les uns dans les autres. Le compilateur risque alors de lire plusieurs fois un memefichier, ce qui entraıne un travail inutile et parfois des erreurs liees a des redefinitions. Par exemple, ima-ginons que MonBazar.h soit un fichier comportant la directive #include <stdio.h>. Dans la compilationd’un fichier commencant par les deux directives

#include <stdio.h>#include "MonBazar.h"

le compilateur risquerait de compiler deux fois le fichier stdio.h. Les deux premieres directives de cefichier resolvent ce probleme : lorsque ce fichier est pris en compte une premiere fois, le nom __STDIO__(nom improbable arbitrairement associe a stdio.h) devient defini. Durant la presente compilation, la

c© H. Garreta, 2003 103

Page 104: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.3 Deux ou trois choses bien pratiques... 8 “GENIE LOGICIEL”

directive #ifndef __STDIO__ fera que l’interieur du fichier ne sera plus jamais lu par le compilateur. Tousles fichiers en-tete peuvent etre proteges de cette maniere contre les inclusions multiples.

β On peut noter ici que la vue du type FILE qu’ont les modules clients est tres differente de celle qu’ilsauraient en utilisant un langage tres modulaire comme Modula II ou Ada. Dans ces langages les nomset les types des champs d’une structure comme FILE resteraient prives, ainsi que la structure elle-meme ;seul le type adresse d’une telle structure serait rendu public. Cela s’appelle un type opaque et augmentela fiabilite des programmes.En C nous n’avons pas d’outils pour proceder de la sorte : ou bien un type est prive, connu uniquementdu module serveur, ou bien il faut � tout dire � a son sujet. L’utilisation d’un pointeur generique, dans lestyle de

typedef void *FILE_ADDRESS;

dissimulerait bien l’information sur la structure FILE, mais aurait la consequence de rendre impossibles adeceler les mauvaises utilisations du type en question par le client. Notez d’autre part que la connaissancedans les modules clients des noms des champs de la structure FILE est indispensable pour l’utilisation desmacros comme getc ou putc.

γ Tous les noms qui apparaissent dans le fichier en-tete deviennent de ce fait publics, meme ceux qu’onaurait aime garder prives alors qu’on ne le peut pas, par exemple parce qu’ils apparaissent dans le corpsd’une macro, comme ici le nom de la table des descripteurs de fichiers iob. Le langage C n’ayant rienprevu a ce effet, la � privacite � n’est assuree que par une convention entre le systeme et les utilisateurs :tout nom commencant par un blanc souligne � � appartient au systeme et l’utilisateur doit feindre d’enignorer l’existence.

δ Ceci n’est pas la meilleure maniere de donner les prototypes des fonctions dans un fichier en-tete. Dans unedeclaration qui n’est pas une definition, la syntaxe n’exige pas les noms des arguments mais uniquementleurs types. Cependant, s’ils sont bien choisis, ces noms apportent une information supplementaire quiaugmente la securite d’utilisation de la bibliotheque. Par exemple, les prototypes

FILE *fopen(const char *, const char *);size_t fread(void *, size_t, size_t, FILE *);

ne seraient d’aucune utilite a un programmeur qui hesiterait a propos du role ou de l’ordre des argumentsde ces fonctions, contrairement a leurs versions � equivalentes � :

FILE *fopen(const char *filename, const char *mode);size_t fread(void *buffer, size_t size, size_t count, FILE *stream);

ε Les appels de fonctions avec des arguments variables ne beneficient pas des verifications syntaxiques quele C ANSI effectue lorsque la fonction a fait l’objet d’une definition de prototype. Sauf pour les argumentsnommes (le premier ou les deux premiers), les arguments que l’on passe a l’une de ces six fonctionsechappent donc a tout controle du compilateur. L’information concernant la nature de ces arguments estportee par le format ; elle ne pourra etre exploitee qu’a l’execution.

Autres remarques. Le premier client de ce dispositif doit etre le fournisseur lui-meme. Pour que laprotection contre l’erreur recherchee soit effective, il faut que chacun des fichiers qui realisent l’implantation desvariables et fonctions � promises � dans stdio.h comporte la directive

#include <stdio.h>

De cette maniere on garantit que l’auteur et l’utilisateur de chaque variable ou fonction sont bien d’accordsur la definition de l’entite en question.

Une autre regle a respecter par l’auteur du ou des modules serveurs : qualifier static tous les noms qui nesont pas declares dans le fichier en-tete, pour eviter les collisions de noms. Le fichier en-tete joue ainsi, pour cequi concerne les variables et les fonctions, le role de liste officielle des seuls noms publics.

8.3 Deux ou trois choses bien pratiques...

8.3.1 Les arguments du programme principal

Cette section ne concerne que les environnements, comme UNIX ou MS-DOS, dans lesquels les programmessont actives en composant une commande de la forme nom-du-programme argument1 ... argumentk.

L’execution d’un programme commence par la fonction main. Tout se passe comme si le systeme d’exploita-tion avait appele cette fonction comme une fonction ordinaire. Il faut savoir que lors de cet appel, des argumentssont fournis au programme. Voici l’en-tete complet de main, en syntaxe ANSI :

104 c© H. Garreta, 2003

Page 105: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.3 Deux ou trois choses bien pratiques...

int main(int argc, char *argv[])

avec :argc : nombre d’arguments du programmeargv : tableau de chaınes de caracteres, qui sont les arguments du programme. Par convention, le premier

argument est le nom du programme lui-meme.

Imaginons avoir ecrit un programme qui, une fois compile, se nomme echo ; supposons que ce programmesoit lance par la commande :

echo Pierre Paul

alors, main recoit les arguments que montre la figure 18.

e h oc 0\ P e r ei r 0\ P a lu 0\

NULL

argc argv3

Fig. 18 – Arguments de main

Supposons que tout ce que l’on demande a echo soit de recopier la liste de ses arguments. Voici commenton pourrait ecrire ce programme :

main(int argc, char *argv[]) {int i;

for (i = 0; i < argc; i++)printf("%s\n", argv[i]);

return 0;}

La frappe de la commande � echo Pierre Paul � produit l’affichage de

echoPierrePaul

La principale application de ce mecanisme est la fourniture a un programme des parametres dont il peutdependre, comme des noms de fichiers, des tailles de tableaux, etc. Par exemple, voici une nouvelle version duprogramme de copie de fichiers donne a la section 7.4.1, qui prend les noms des fichiers source et destinationcomme arguments :

#include <stdio.h>

#define PAS_D_ERREUR 0#define ERREUR_OUVERTURE 1#define ERREUR_CREATION 2#define PAS_ASSEZ_D_ARGUMENTS 3

FILE *srce, *dest;

main(int argc, char *argv[]) {char tampon[512];int nombre;if (argc < 3)

return PAS_ASSEZ_D_ARGUMENTS;if ((srce = fopen(argv[1], "rb")) == NULL)

return ERREUR_OUVERTURE;if ((dest = fopen(argv[2], "wb")) == NULL)

return ERREUR_CREATION;

c© H. Garreta, 2003 105

Page 106: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.3 Deux ou trois choses bien pratiques... 8 “GENIE LOGICIEL”

while ((nombre = fread(tampon, 1, 512, srce)) > 0)fwrite(tampon, 1, nombre, dest);

fclose(dest);return PAS_D_ERREUR;

}

Si nous appelons copier le fichier executable produit en compilant le texte ci-dessus, alors nous pourronsl’executer en composant la commande

copier fichier-source fichier-destination

8.3.2 Branchements hors fonction : setjmp.h

Le mecanisme des longs branchements permet d’obtenir la terminaison immediate d’un nombre quelconquede fonctions actives. Il est realise a l’aide des deux fonctions :

int setjmp(jmp_buf contexte); voidlongjmp(jmp_buf contexte, int code);

Cela fonctionne de la maniere suivante : tout d’abord il faut declarer une variable, generalement globale, detype jmp buf (type defini dans le fichier en-tete setjmp.h) :

#include <setjmp.h>...jmp_buf contexte;

L’appel

setjmp(contexte);

enregistre dans contexte certaines informations traduisant l’etat du systeme, puis renvoie zero. Ensuite, l’appel

longjmp(contexte, valeur);

remet le systeme dans l’etat qui a ete enregistre dans la variable contexte. Plus precisement, le systeme setrouve comme si l’appel de setjmp(contexte) venait tout juste de se terminer, en rendant cette fois non paszero mais la valeur indiquee dans l’appel de longjmp.

Le principal service rendu par ce dispositif est de permettre de programmer simplement, et en maıtrisantle � point de chute �, l’abandon d’une famille de fonctions qui se sont mutuellement appelees. Examinons unexemple classique : supposons qu’une certaine fonction expression soit un analyseur syntaxique presentantdeux caracteristiques tres frequentes :

– la fonction expression est a l’origine d’une imbrication dynamique fort complexe (expression appelleune fonction terme, qui appelle une fonction facteur qui a son tour appelle une fonction primaire laquellerappelle expression, etc.) ;

– chacune des fonctions expression, terme, facteur, etc., peut a tout moment rencontrer une erreur dansses donnees, qui rend la poursuite du traitement inutile ou impossible.

main analyse setjmp

expression terme

longjmp(c,v)

setjmp(c)

analyse()

expression()

terme()

Fig. 19 – Branchement hors fonction

106 c© H. Garreta, 2003

Page 107: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.3 Deux ou trois choses bien pratiques...

Voici comment l’appel d’expression pourrait etre emballe dans une fonction � enveloppe � nommee analyse(voir la figure 19) :

#include <setjmp.h>...jmp_buf contexte;...int analyse(void) {

if (setjmp(contexte) == 0) {expression();return SUCCES;

}else

return ERREUR;}

Fonctionnement : lorsque la fonction analyse est activee, setjmp est appelee et rend 0 ; elle est doncimmediatement suivie par l’appel de expression. Si le travail de expression se termine normalement, alorsanalyse rendra SUCCES et elle aura ete transparente. D’autre part, dans la fonction expression et toutes cellesappelees � au-dessus � d’elle (terme, facteur, etc.), il est possible d’effectuer l’appel

longjmp(contexte, 1);

qui ramene le controle dans la fonction analyse, exactement sur la partie condition de l’instruction

if (setjmp(contexte) == 0)

mais cette fois la valeur rendue par setjmp sera 1 et analyse rendra donc la valeur ERREUR.

Remarques. 1. L’appel de longjmp se presente comme une remise du systeme dans l’un des etats par lesquelsil est passe. Cela est vrai, sauf pour ce qui concerne la valeur des variables, notamment les variables globales :elles ne reprennent pas la valeur qu’elles avaient lorsque longjmp a ete appelee.

2. On ne peut effectuer un appel de longjmp que pour ramener le systeme dans l’une des fonctions appeleeset non encore terminees, pour laquelle l’espace local est encore alloue dans la pile. Appeler longjmp avec uncontexte qui a ete memorise dans une fonction dont l’activation est terminee est une erreur aux consequencesindefinies, meme si cette fonction a ete rappelee depuis. Par exemple, un appel longjmp(c,v) qui serait placedans la fonction main apres l’appel analyse() (voir la figure 19) serait errone, car il utiliserait le contexte d’uneactivation de fonction (la fonction analyse) qui n’existe plus.

8.3.3 Interruptions : signal.h

L’objet de la fonction signal est la detection d’evenements asynchrones qui peuvent se produire pendantl’execution d’un programme. � Asynchrones � signifie qu’on ne peut pas prevoir le moment ou ils se produiront,car ils ne resultent pas d’instructions normalement inserees dans la sequence qui forme le programme.

Un certain ensemble de types d’evenements, dependant de chaque systeme, est recuperable a travers lemecanisme decrit ici. En general on traite de la meme maniere le vrai et le faux asynchronisme. Une coupurede courant ou une interruption provoquee par l’utilisateur sont des evenements vraiment imprevisibles. Unereference a travers un pointeur invalide ou une division par zero ne sont pas reellement asynchrones (si on avaitconnu parfaitement le programme et les donnees on aurait pu predire tres exactement de tels evenements), maisil est commode de les considerer comme tels et de les recuperer de la meme maniere.

La fonction signal est declaree dans le fichier signal.h de la facon suivante :

void (*signal(int numero, void (*manip)(int)))(int);

Ce prototype n’est pas tres facile a lire. Definissons le type PROCEDURE comme celui d’une fonction sansresultat defini ayant un argument de type int :

typedef void PROCEDURE(int);

Avec cela, la declaration precedente se recrit plus simplement :

PROCEDURE *signal(int numero, PROCEDURE *manip);

et elle nous apprend que la fonction signal prend deux arguments, a savoir un int et l’adresse d’une PROCEDURE,et renvoie l’adresse d’une PROCEDURE.

Lors d’un appel de signal, l’argument numero doit designer un des evenements qu’il est possible de recuperer.L’effet de signal est d’enregistrer la PROCEDURE donnee en argument, de telle maniere que si l’evenement en

c© H. Garreta, 2003 107

Page 108: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.3 Deux ou trois choses bien pratiques... 8 “GENIE LOGICIEL”

question se produit, alors elle sera automatiquement appelee par le systeme avec pour argument le numero del’evenement. La fonction signal rend l’adresse de la PROCEDURE qui etait jusqu’alors enregistree pour ce memeevenement.

Six evenements sont prevus par le standard ANSI (ils ne sont pas tous implantes dans tous les systemes ; enoutre, chaque systeme peut ajouter ses propres particularites) :

SIGABRT : fin anormale du programme (appel de la fonction abort)SIGINT : interruption provoquee par l’utilisateur (touche Ctrl-C, etc.)SIGTERM : demande d’arret de l’utilisateur (interruption ” forte ”)SIGFPE : erreur arithmetique (division par zero, etc.)SIGSEGV : acces memoire illegal (souvent : mauvais pointeur dans l’acces a une donnee, debordement de

tableau)SIGILL : instruction illegale (souvent : mauvais pointeur dans l’appel d’une fonction)

La bibliotheque fournit en outre deux mecanismes de recuperation d’une interruption predefinis :

SIG DFL : le mecanisme par defaut utilise par le systeme pour l’evenement en question lorsque la fonctionsignal n’a pas ete appelee

SIG IGN : le mecanisme trivial qui consiste a ignorer l’evenement

Exemple. Supposons qu’une section d’un certain programme effectue des calculs tres complexes dont la dureerisque d’inquieter l’utilisateur final. On souhaite donc offrir a ce dernier la possibilite d’interrompre le programmelorsqu’il estime que celui-ci devient excessivement long, en appuyant sur une touche particuliere65. L’utilisateurapprendra alors l’etat d’avancement de son calcul (traduit par la valeur de la variable iteration numero) etaura a choisir entre la continuation et la terminaison du programme :

#include <signal.h>

int iteration_numero;typedef void PROCEDURE(int);

void voirSkissPass(int numero) {int c;printf("J’en suis a l’iteration: %d\nOn arrete? ", iteration_numero);do

c = getchar();while ((c = toupper(c)) != ’O’ && c != ’N’);if (c == ’O’)

exit(1); /* abandonner le programme */else

return; /* reprendre le travail interrompu */}

main() {PROCEDURE *maniprec;...autres operations...

/* entree dans la section onereuse */maniprec = signal(SIGINT, voirSkissPass);...calculs terriblement complexes...

/* sortie de la section onereuse */signal(SIGINT, maniprec);...autres operations...

}

65Sur plusieurs systemes, comme UNIX et VMS, il s’agit de la combinaison des deux touches � Ctrl � et � C �.

108 c© H. Garreta, 2003

Page 109: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.4 La bibliotheque standard

8.4 La bibliotheque standard

La bibliotheque standard ANSI se compose d’un ensemble de fonctions, dont le code se trouve dans un ouplusieurs fichiers objets qui participent a l’edition de liens, et d’un ensemble de fichiers en-tete contenant lesdeclarations necessaires pour que les appels de ces fonctions puissent etre correctement compiles. Ces fichiersen-tete sont organises par themes ; nous reprenons cette organisation pour expliquer les principaux elements decette bibliotheque.

Des parties importantes de la bibliotheque standard ont deja ete expliquees ; notamment :– la bibliotheque des entrees - sorties (associee au fichier stdio.h), a la section 7 ;– les listes variables d’arguments (introduites dans le fichier stdarg.h) a la section 4.3.4 ;– les branchements hors-fonction (declares dans le fichier setjmp.h) a la section 8.3.2 ;– la recuperation des interruptions (avec le fichier signal.h) a la section 8.3.3.Nous passerons sous silence certains modules mineurs (comme la gestion de la date et l’heure). Au besoin,

reportez-vous a la documentation de votre systeme.

8.4.1 Aide a la mise au point : assert.h

Cette � bibliotheque � ne contient aucune fonction. Elle se compose d’une macro unique :void assert(int expression)

qui fonctionne de la maniere suivante. Si l’expression indiquee est fausse (c’est-a-dire si elle vaut zero) au momentou la macro est evaluee, un message est imprime sur stderr, de la forme

Assertion failed: expression, file fichier, line numeroensuite l’execution du programme est avortee. Exemple naıf :#include <assert.h>...#define TAILLE 100int table[TAILLE];...for (i = 0; j < TAILLE; i++) {

...assert(0 <= i && i < TAILLE);table[i] = 0;...

}l’execution de ce programme donnera (puisqu’une faute de frappe a rendu la boucle for infinie) :

Assertion failed: 0 <= i && i < TAILLE, file ex.c, line 241

La macro assert se revele un outil precieux pour l’ecriture et la mise au point des programmes (dans dessituations plus complexes que l’exemple trivial montre ici !). Beaucoup de temps peut etre gagne en � postant �de telles assertions aux endroits ou les variables du programme doivent satisfaire des contraintes importantes.Cependant, cette macro n’est utile que pendant le developpement et ses appels doivent etre retires du programmedestine a l’utilisateur final, d’abord parce que ce dernier n’a pas a connaıtre le nom d’un fichier source oule numero d’une ligne ou une incongruite s’est produite, ensuite et surtout parce que ces appels de assertralentissent les programmes.

Lorsque la mise au point d’un programme est terminee, on doit donc effacer tous les appels de assert qui yfigurent. Cela peut se faire automatiquement, tout simplement en definissant l’identificateur NDEBUG :

#define NDEBUG peu importe la valeurjuste avant d’effectuer la compilation finale du programme. Celle-ci se fera alors comme si tous les appels deassert avaient ete gommes.

8.4.2 Fonctions utilitaires : stdlib.h

int atoi(const char *s), long atol(const char *s), double atof(const char *s)

Ces fonctions calculent et rendent l’int (resp. le long, le double) dont la chaıne s est l’expression ecrite.

int rand(void)

Dans l’execution d’un programme, le ieme appel de cette fonction rend le ieme terme d’une suite d’entierspseudo-aleatoires compris entre 0 et RAND MAX (qui vaut au moins 32767). Cette suite ne depend que de

c© H. Garreta, 2003 109

Page 110: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.4 La bibliotheque standard 8 “GENIE LOGICIEL”

la valeur de la semence donnee lors de l’appel de srand, voir ci-dessous. Si srand n’a pas ete appelee, lasuite obtenue est celle qui correspond a srand(1).

void srand(unsigned int semence)

Initialise une nouvelle suite pseudo-aleatoire (voir rand ci-dessus).

void *malloc(size t taille)

Alloue un espace pouvant memoriser un objet ayant la taille indiquee, ou NULL en cas d’echec. Cet espacen’est pas initialise.

void *calloc(size t nombre, size t taille)

Alloue un espace suffisant pour loger un tableau de nombre objets, chacun ayant la taille indiquee, ouNULL en cas d’echec. L’espace alloue est initialise par des zeros.

void free(void *adresse)

Indique au systeme que l’espace memoire ayant l’adresse indiquee n’est plus utile au programme. Cetteadresse doit necessairement provenir d’un appel de malloc, calloc ou realloc.

void *realloc(void *adr, size t taille)

Re-allocation d’espace. La valeur de adr doit provenir d’un appel de malloc, calloc ou realloc. Cettefonction essaie d’agrandir ou de retrecir (usage rare) l’espace pointe par adr afin qu’il ait la la tailledemandee. Si cela peut se faire sur place alors la fonction renvoie la meme adresse adr. Sinon, elle obtientun nouvel espace ayant la taille voulue, y copie le contenu de l’espace pointe par adr, libere cet espace,enfin renvoie l’adresse de l’espace nouvellement alloue.

void exit(int code)

Produit l’arret normal (fermeture des fichiers ouverts, etc.) du programme. La valeur du code indique esttransmise au systeme d’exploitation, cette valeur est utilisee lorsque le programme a ete appele dans lecadre d’un procedure de commandes (ou script). La signification de ce code depend du systeme ; on peututiliser les constantes :

– EXIT SUCCESS : la valeur conventionnelle qui indique, pour le systeme sous-jacent, que leprogramme a reussi a accomplir sa mission ;

– EXIT FAILURE : le programme n’a pas reussi.

void abort(void)

Provoque un arret anormal du programme (c’est un evenement detecte par la fonction signal).

int system(const char *commande)

Suspend momentanement l’execution du programme en cours, demande a l’interprete des commandes dusysteme d’exploitation d’executer la commande indiquee et rend le code donne par cette commande (cettevaleur depend du systeme). La chaıne commande doit etre l’expression complete, avec options et arguments,d’une commande legale pour le systeme utilise.Toutes les versions de C n’offrent pas ce service, mais on peut savoir ce qu’il en est : l’appel system(NULL)rend une valeur non nulle si l’interprete de commandes peut effectivement etre appele depuis un pro-gramme, zero sinon.

char *getenv(char *nom)

Rend la chaıne qui est la valeur de la variable d’environnement ayant le nom indique, ou NULL si unetelle variable n’est pas definie. La notion de variable d’environnement est definie au niveau du systemesous-jacent.

void qsort(void *tab, size t nbr, size t taille, int (*comp)(const void *, const void *))

(Quick sort, tri rapide) Cette fonction trie � sur place �, par ordre croissant, le tableau dont tab donnel’adresse de base, forme de nbr objets chacun ayant la taille indiquee. A cet effet, qsort utilise lafonction de comparaison qui est la valeur de l’argument comp. Cette fonction doit prendre les adresses dedeux objets comme ceux dont le tableau est fait, et rendre une valeur negative, nulle ou positive selon quele premier objet est respectivement inferieur, egal ou superieur au second. Voir la section 6.3.2 pour unexemple d’utilisation de cette fonction.

int bsearch(const void *ptr, const void *tab, size t nbr, size t taille,int (*comp)(const void *, const void *))

110 c© H. Garreta, 2003

Page 111: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8 “GENIE LOGICIEL” 8.4 La bibliotheque standard

(Binary search, recherche dichotomique) Cette fonction recherche dans le tableau trie ayant l’adresse debase donnee par tab, forme de nbr objets chacun ayant la taille indiquee, un objet egal a celui ayant ptrpour adresse. Pour cela, elle utilise la fonction de comparaison donnee par comp, qui est definie commepour qsort, ci-dessus.

int abs(int x), long labs(long x)

Valeur absolue, avec un argument int (resp. long)66.

8.4.3 Traitement de chaınes : string.h

char *strcpy(char *destin, const char *source)

Copie la chaıne source a l’adresse destin. Rend destin.

char *strcat(char *destin, const char *source)

Copie la chaıne source a la suite de la chaıne destin. Rend destin.

int strcmp(const char *a, const char *b)

Compare les chaınes a et b pour l’ordre lexicographique et rend une valeur negative, nulle ou positiveselon que a est, respectivement, inferieur, egal ou superieur a b.

size t strlen(const char *s)

Rend le nombre de caracteres de la chaıne s. Il s’agit du nombre de caracteres utiles : le caractere ’\0’qui se trouve a la fin de toutes les chaınes n’est pas compte. Ainsi, strlen("ABC") vaut 3.

void *memcpy(char *destin, const char *source, size t nombre)

Copie la zone memoire d’adresse source de de taille nombre dans la zone de meme taille et d’adressedestin . Ces deux zones ne doivent pas se rencontrer.

void *memmove(char *destin, const char *source, size t nombre)

Copie la zone memoire d’adresse source de de taille nombre dans la zone de meme taille et d’adressedestin . Fonctionne correctement meme si ces deux zones se rencontrent ou se chevauchent.

8.4.4 Classification des caracteres : ctype.h

Les elements de cette bibliotheque peuvent etre implantes soit par des fonctions, soit par des macros. Lespredicats rendent, lorsqu’ils sont vrais, une valeur non nulle qui n’est pas forcement egale a 1 :

int islower(int c) ⇔ c est une lettre minuscule.

int isupper(int c) ⇔ c est une lettre majuscule.

int isalpha(int c) ⇔ c est une lettre.

int isdigit(int c) ⇔ c est un chiffre decimal.

int isalnum(int c) ⇔ c est une lettre ou un chiffre.

int isspace(int c) ⇔ c est un caractere d’espacement : ’ ’, ’\t’, ’\n’, ’\r’ ou ’\f’.int iscntrl(int c) ⇔ c est un caractere de controle (c’est-a-dire un caractere dont le code ASCII est

compris entre 0 et 31).

int isprint(int c) ⇔ c est un caractere imprimable, c’est-a-dire qu’il n’est pas un caractere de controle.

int isgraph(int c) ⇔ c est un caractere imprimable autre qu’un caractere d’espacement.

int ispunct(int c) ⇔ c est une ponctuation, c’est-a-dire un caractere imprimable qui n’est ni un caractered’espacement, ni une lettre ni un chiffre.

int tolower(int c), int toupper(int c) si c est une lettre majuscule (resp. minuscule) rend la lettre mi-nuscule (resp. majuscule) correspondante, sinon rend le caractere c lui-meme.

66La fonction correspondante pour les reels s’appelle fabs (cf. section 8.4.5).

c© H. Garreta, 2003 111

Page 112: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

8.4 La bibliotheque standard 8 “GENIE LOGICIEL”

8.4.5 Fonctions mathematiques : math.h

double sin(double x) rend la valeur de sinx.double cos(double x) rend la valeur de cos x.double tan(double x) rend la valeur de tanx.double asin(double x) rend la valeur de arcsinx, dans

[−π2 , π

2

]. On doit avoir x ∈ [−1, 1].

double acos(double x) rend la valeur de arccos x, dans [0, π]. On doit avoir x ∈ [−1, 1].double atan(double x) rend la valeur de arctan x, dans

[−π2 , π

2

].

double atan2(double y, double x) rend la limite de la valeur de arctan vu dans

[−π2 , π

2

]lorsque u → x+

et v → y. Pour x 6= 0 c’est la meme chose que arctan yx

double sinh(double x) rend la valeur du sinus hyperbolique de x, soit sh x = ex−e−x

2

double cosh(double x) rend la valeur du cosinus hyperbolique de x, ch x = ex+e−x

2

double tanh(double x) rend la valeur de la tangente hyperbolique de x, th x = chxshx

double exp(double x) rend la valeur de l’exponentielle des, ex.double log(double x) rend la valeur du logarithme neperien de x, log x. On doit avoir x > 0.double log10(double x) rend la valeur du logarithme decimal de x, log10 x. On doit avoir x > 0.double pow(double x, double y) rend la valeur de xy. Il se produit une erreur si x = 0 et y = 0 ou si x < 0

et y n’est pas entier.double sqrt(double x) rend la valeur de

√x. On doit avoir x ≥ 0.

double ceil(double x) rend la valeur du plus petit entier superieur ou egal a x, transforme en double.double floor(double x) rend la valeur du plus grand entier inferieur ou egal a x, transforme en double.double fabs(double x) la valeur absolue de x.

8.4.6 Limites propres a l’implantation : limits.h, float.h

Ces fichiers en-tete definissent les tailles et les domaines de variation pour les types numeriques. On y trouve :CHAR BIT nombre de bits par caractere.CHAR MAX, CHAR MIN valeurs extremes d’un char.SCHAR MAX, SCHAR MIN valeurs extremes d’un signed char.UCHAR MAX valeur maximum d’un unsigned char.SHRT MAX, SHRT MIN valeurs extremes d’un short.USHRT MAX valeur maximum d’un unsigned short.INT MAX, INT MIN valeurs extremes d’un int.UINT MAX valeur maximum d’un unsigned int.LONG MAX, LONG MIN valeurs extremes d’un long.ULONG MAX valeur maximum d’un unsigned long.

Le fichier float.h definit :FLT DIG precision (nombre de chiffres decimaux de la mantisse).FLT EPSILON plus petit nombre e tel que 1.0 + e 6= 1.0.FLT MANT DIG nombre de chiffres de la mantisse.FLT MAX plus grand nombre representable.FLT MAX 10 EXP plus grand n tel que 10n soit representable.FLT MAX EXP plus grand exposant n tel que FLT RADIXn − 1 soit representable.FLT MIN plus petit nombre positif representable (sous forme normalisee).FLT MIN 10 EXP plus petit exposant n tel que 10n soit representable (sous forme normalisee).FLT MIN EXP plus petit exposant n tel que FLT RADIXn soit representable (sous forme normalisee).FLT RADIX base de la representation exponentielle.FLT ROUNDS type de l’arrondi pour l’addition.

Ces fichiers definissent egalement les constantes analogues pour les double (noms en � DBL ... � a la placede � FLT ... �) et les long double (noms en � LDBL ... �).

112 c© H. Garreta, 2003

Page 113: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

Index

( ) (operateur), 18* (operateur), 22*= (operateur), 30++ (operateur), 21+= (operateur), 30, (operateur), 30- (operateur), 22-= (operateur), 30- - (operateur), 21->(operateur), 20. (operateur), 20/= (operateur), 30= (operateur), 29== (operateur), 26? : (operateur), 28[ ] (operateur), 19%= (operateur), 30& (operateur), 22, 27&= (operateur), 30&& (operateur), 28ˆ(operateur), 27ˆ= (operateur), 30˜(operateur), 21|(operateur), 27|= (operateur), 30||(operateur), 28>(operateur), 26>= (operateur), 26>>(operateur), 25>>= (operateur), 30<(operateur), 26<= (operateur), 26<<(operateur), 25<<= (operateur), 30

abort, 110abs, 111acces a un champ (operateur), 20acces relatif, 91acos, 112adresse (operateur), 22adresse d’un objet, 22, 60adresse d’une fonction, 72affectation (operateur), 29appel de fonction, 18, 42, 44argc, 105argument formel, 12arguments des fonctions (passage), 45arguments en nombre variable, 47arguments par adresse, 46argv, 105arithmetique des adresses, 62arithmetiques (operateurs), 25asin, 112assert, 109assert.h, 109atan, 112atan2, 112

atof, 109atoi, 109atol, 109automatique (variable), 12

bits (champs de bits), 53bloc (instruction), 33, 34break, 33, 39bsearch, 110

calloc, 110caractere, 6case, 33, 38ceil, 112chaıne de caracteres, 6, 50chaıne de caracteres (initialisation), 51champs de bits, 53char, 9CHAR BIT, 112CHAR MAX, 112CHAR MIN, 112close, 95commentaire, 4comparaison (operateur), 26complement a 1 (operateur), 21conditionnelle (compilation), 99conjonction (operateur), 28conjonction bit-a-bit (operateur), 27connecteurs logiques (operateurs), 28const, 14, 57continue, 33, 39conversion de type, “cast” (operateur), 23, 59conversions arithmetiques usuelles, 31cos, 112cosh, 112creat, 95ctype.h, 111

decalage de bits (operateur), 25declarateur complexe, 55default, 33, 38define, 97disjonction (operateur), 28disjonction bit-a-bit (operateur), 27do, 33, 36duree de vie (d’une variable), 12

effet de bord, 17else, 35, 99en-tete (fichier), 101endif, 99enumeration, 54etiquette, 35exit, 110EXIT FAILURE, 110EXIT SUCCESS, 110exp, 112expression conditionnelle (operateur), 28extern, 15

Page 114: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

INDEX INDEX

externe (identificateur), 15

F, f (suffixe d’une constante), 6fabs, 112fclose, 80feof, 81ferror, 81fflush, 80fgetc, 82fgetpos, 91fgets, 82fichier binaire, 79fichier de texte, 79FILE, 80float.h, 112floor, 112flots, 79flottante (constante), 6FLT DIG, 112FLT EPSILON, 112FLT MANT DIG, 112FLT MAX, 112FLT MAX 10 EXP, 112FLT MAX EXP, 112FLT MIN, 112FLT MIN 10 EXP, 112FLT MIN EXP, 112FLT RADIX, 112FLT ROUNDS, 112fonction formelle, 72fopen, 80for, 33, 37fprintf, 89fputc, 83fputs, 83fread, 90free, 110fscanf, 90fseek, 90fsetpos, 91fwrite, 90

getc, 82getchar, 82getenv, 110gets, 82globale (variable), 12goto, 33, 35

hexadecimale (ecriture d’un nombre), 6

identificateur, 5if, 33, 35, 99ifdef, 99ifndef, 99include, 96indexation (operateur), 19, 62indirection (operateur), 22initialisation d’un tableau, 49initialisation d’une chaıne de caracteres, 51initialisation d’une structure, 52initialisation d’une variable, 12

INT MAX, 112INT MIN, 112isalnum, 111isalpha, 111iscntrl, 111isdigit, 111isgraph, 111islower, 111isprint, 111ispunct, 111isspace, 111isupper, 111

jmp buf, 106

L, l (suffixe d’une constante), 6labs, 111limits.h, 112locale (variable), 11log, 112log10, 112logiques (operateurs), 28LONG MAX, 112LONG MIN, 112longjmp, 106lseek, 95lvalue, 17

macros, 97main, 105malloc, 110math.h, 112memcpy, 111memmove, 111moins unaire (operateur), 22mot-cle, 5

negation (operateur), 20NDEBUG, 109nombre entier, 6, 8nombre flottant, 6, 10NULL, 62

O RDONLY, 95O RDWR, 95O WRONLY, 95octale (ecriture d’un nombre), 6operateur, 5open, 95ordre d’evaluation des expressions, 32ou exclusif bit-a-bit (operateur), 27

passage des arguments des fonctions, 45pointeur, 60post-decrementation (operateur), 21post-incrementation (operateur), 21pow, 112pre-decrementation (operateur), 21pre-incrementation (operateur), 21preprocesseur, 96printf, 84, 89priorite des operateurs, 18

114 c© H. Garreta, 2003

Page 115: Le langage C - Elektroniqueet du fichier inclus. 2N´eanmoins, les directives pour le pr´eprocesseur (cf. section 8.1) doivent comporter un # dans la premi`ere position de la ligne

INDEX INDEX

prive (dentificateur), 15prototype d’une fonction, 41public (dentificateur), 15putc, 83putchar, 83puts, 83

qsort, 110

rand, 109read, 95realloc, 110register, 14return, 33, 40rewind, 91rvalue, 17

sequence d’echappement, 7scanf, 86, 90SCHAR MAX, 112SCHAR MIN, 112SEEK CUR, 90SEEK END, 90SEEK SET, 90setjmp, 106setjmp.h, 106setvbuf, 81SHRT MAX, 112SHRT MIN, 112signal, 107signal.h, 107sin, 112sinh, 112sizeof, 23sprintf, 89sqrt, 112srand, 110sscanf, 90static, 13statique (variable), 13stderr, 81stdin, 81stdio.h, 80stdlib.h, 109stdout, 81strcat, 111strcmp, 111strcpy, 111string.h, 111strlen, 111struct, 51structure (declaration), 51structure (initialisation), 52structure (signification d’une variable structure), 52structures recursives, 75, 77switch, 33, 38system, 110

tableau (declaration), 49tableau (initialisation), 49tableau (signification d’une variable tableau), 49tableau [dynamique] multidimensionnel, 65

tableau de chaınes de caracteres, 68tableau de fonctions, 74tableau dynamique, 64taille d’un objet (operateur), 23tampon (d’entree ou de sortie), 79tan, 112tanh, 112tmpfile, 81tolower, 111type enumere, 54type enumere, 10typedef, 58types, 8types desincarnes, 59

U, u (suffixe d’une constante), 6UCHAR MAX, 112UINT MAX, 112ULONG MAX, 112ungetc, 83union, 53USHRT MAX, 112

variable, 10virgule (operateur), 30void, 42void *, 61volatile, 14, 57

while, 33, 36write, 95

c© H. Garreta, 2003 115