You are on page 1of 125

Universit e dAix-Marseille Facult e des Sciences

Le langage C
Licences Maths & Informatique Master Math ematiques Master CCI

Henri Garreta D epartement dInformatique & LIF

` TABLE DES MATIERES

` TABLE DES MATIERES

Table des mati` eres


ements de base 1 El 1.1 Structure g en erale dun programme . . . . . . . 1.2 Consid erations lexicales . . . . . . . . . . . . . 1.2.1 Pr esentation du texte du programme . . 1.2.2 Mots-cl es . . . . . . . . . . . . . . . . . 1.2.3 Identicateurs . . . . . . . . . . . . . . 1.2.4 Op erateurs . . . . . . . . . . . . . . . . 1.3 Constantes litt erales . . . . . . . . . . . . . . . 1.3.1 Nombres entiers . . . . . . . . . . . . . 1.3.2 Nombres ottants . . . . . . . . . . . . 1.3.3 Caract` eres et cha nes de caract` eres . . . 1.3.4 Expressions constantes . . . . . . . . . . 1.4 Types fondamentaux . . . . . . . . . . . . . . . 1.4.1 Nombres entiers et caract` eres . . . . . . 1.4.2 Types enum er es . . . . . . . . . . . . . 1.4.3 Nombres ottants . . . . . . . . . . . . 1.5 Variables . . . . . . . . . . . . . . . . . . . . . 1.5.1 Syntaxe des d eclarations . . . . . . . . . 1.5.2 Visibilit e des variables . . . . . . . . . . 1.5.3 Allocation et dur ee de vie des variables 1.5.4 Initialisation des variables . . . . . . . . 1.5.5 Variables locales statiques . . . . . . . . 1.5.6 Variables critiques . . . . . . . . . . . . 1.5.7 Variables constantes et volatiles . . . . . 1.6 Variables, fonctions et compilation s epar ee . . . 1.6.1 Identicateurs publics et priv es . . . . . 1.6.2 D eclaration dobjets externes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 5 6 6 6 7 7 7 7 8 9 9 11 11 11 11 12 13 13 14 14 15 15 15 16 18 18 18 19 19 19 20 21 21 22 22 22 23 23 24 24 25 27 28 28 29 30 31 31 32 33 34 34 34 34

2 Op erateurs et expressions 2.1 G en eralit es . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Lvalue et rvalue . . . . . . . . . . . . . . . 2.1.2 Priorit e des op erateurs . . . . . . . . . . . . 2.2 Pr esentation d etaill ee des op erateurs . . . . . . . . 2.2.1 Appel de fonction () . . . . . . . . . . . . . 2.2.2 Indexation [] . . . . . . . . . . . . . . . . . 2.2.3 S election . . . . . . . . . . . . . . . . . . . 2.2.4 S election dans un objet point e -> . . . . . . 2.2.5 N egation ! . . . . . . . . . . . . . . . . . . . 2.2.6 Compl ement ` a1~ . . . . . . . . . . . . . . 2.2.7 Les c el` ebres ++ et -- . . . . . . . . . . . . . 2.2.8 Moins unaire - . . . . . . . . . . . . . . . . 2.2.9 Indirection * . . . . . . . . . . . . . . . . . 2.2.10 Obtention de ladresse & . . . . . . . . . . . 2.2.11 Op erateur sizeof . . . . . . . . . . . . . . 2.2.12 Conversion de type (cast operator) . . . . 2.2.13 Op erateurs arithm etiques . . . . . . . . . . 2.2.14 D ecalages << >> . . . . . . . . . . . . . . . 2.2.15 Comparaisons == != < <= > >= . . 2.2.16 Op erateurs de bits & | ^ . . . . . . . . . . . 2.2.17 Connecteurs logiques && et || . . . . . . . 2.2.18 Expression conditionnelle ? : . . . . . . . . 2.2.19 Aectation = . . . . . . . . . . . . . . . . . 2.2.20 Autres op erateurs daectation += *= etc. 2.2.21 Lop erateur virgule , . . . . . . . . . . . . 2.3 Autres remarques . . . . . . . . . . . . . . . . . . . 2.3.1 Les conversions usuelles . . . . . . . . . . . 2.3.2 Lordre d evaluation des expressions . . . . 2.3.3 Les op erations non abstraites . . . . . . . . 2

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

c H. Garreta, 1988-2013

` TABLE DES MATIERES

` TABLE DES MATIERES

3 Instructions 3.1 Syntaxe . . . . . . . . . . . . . . . . . . . 3.2 Pr esentation d etaill ee des instructions . . 3.2.1 Blocs . . . . . . . . . . . . . . . . 3.2.2 Instruction-expression . . . . . . . 3.2.3 Etiquettes et instruction goto . . . 3.2.4 Instruction if...else... . . . . 3.2.5 Instructions while et do...while 3.2.6 Instruction for . . . . . . . . . . . 3.2.7 Instruction switch . . . . . . . . . 3.2.8 Instructions break et continue . 3.2.9 Instruction return . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

36 36 37 37 37 38 38 39 40 41 42 43 44 44 44 44 45 46 46 46 47 48 48 48 48 49 50 52 52 52 52 53 54 54 56 56 57 58 58 60 61 62 64 64 64 65 66 66 68 69 70 72 74 74 76 77 77 78 3

4 Fonctions 4.1 Syntaxe ANSI ou avec prototype . . . . . . 4.1.1 D enition . . . . . . . . . . . . . . . . 4.1.2 Type de la fonction et des arguments . 4.1.3 Appel des fonctions . . . . . . . . . . 4.1.4 D eclaration externe dune fonction . 4.2 Syntaxe originale ou sans prototype . . . . 4.2.1 D eclaration et d enition . . . . . . . . 4.2.2 Appel . . . . . . . . . . . . . . . . . . 4.2.3 Coexistence des deux syntaxes . . . . 4.3 Arguments des fonctions . . . . . . . . . . . . 4.3.1 Passage des arguments . . . . . . . . . 4.3.2 Arguments de type tableau . . . . . . 4.3.3 Arguments par adresse . . . . . . . . . 4.3.4 Arguments en nombre variable . . . . 5 Objets structur es 5.1 Tableaux . . . . . . . . . . . . . . . . 5.1.1 Cas g en eral . . . . . . . . . . . 5.1.2 Initialisation des tableaux . . . 5.1.3 Cha nes de caract` eres . . . . . 5.2 Structures et unions . . . . . . . . . . 5.2.1 Structures . . . . . . . . . . . . 5.2.2 Unions . . . . . . . . . . . . . . 5.2.3 Champs de bits . . . . . . . . . 5.3 Enum erations . . . . . . . . . . . . . . 5.4 D eclarateurs complexes . . . . . . . . 5.4.1 Cas des d eclarations . . . . . . 5.4.2 Pointeurs et tableaux constants 5.4.3 La d eclaration typedef . . . . 5.4.4 Cas des types d esincarn es . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . et volatils . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

6 Pointeurs 6.1 G en eralit es . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 D eclaration et initialisation des pointeurs . . . . . . . 6.1.2 Les pointeurs g en eriques et le pointeur NULL . . . . . . 6.2 Les pointeurs et les tableaux . . . . . . . . . . . . . . . . . . 6.2.1 Arithm etique des adresses, indirection et indexation . 6.2.2 Tableaux dynamiques . . . . . . . . . . . . . . . . . . 6.2.3 Tableaux multidimensionnels . . . . . . . . . . . . . . 6.2.4 Tableaux multidimensionnels dynamiques . . . . . . . 6.2.5 Tableaux de cha nes de caract` eres . . . . . . . . . . . 6.2.6 Tableaux multidimensionnels formels . . . . . . . . . . 6.2.7 Tableaux non n ecessairement index es ` a partir de z ero 6.2.8 Matrices non dynamiques de taille inconnue . . . . . . 6.3 Les adresses des fonctions . . . . . . . . . . . . . . . . . . . . 6.3.1 Les fonctions et leurs adresses . . . . . . . . . . . . . . 6.3.2 Fonctions formelles . . . . . . . . . . . . . . . . . . . .
c H. Garreta, 1988-2013

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

` TABLE DES MATIERES

` TABLE DES MATIERES

6.4

6.3.3 Tableaux de fonctions . 6.3.4 Flou artistique . . . . . Structures r ecursives . . . . . . 6.4.1 D eclaration . . . . . . . 6.4.2 Exemple . . . . . . . . . 6.4.3 Structures mutuellement

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . r ecursives

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

79 80 81 81 81 83 85 85 86 88 88 88 90 93 95 97 97 97 98 99 99 100 101 102 104 104 104 105 106 108 109 110 112 112 114 115 116 117 118 120 121 121 121

7 Entr ees-sorties 7.1 Flots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Fonctions g en erales sur les ots . . . . . . . . . . . . . . . 7.1.2 Les unit es standard dentr ee-sortie . . . . . . . . . . . . . 7.2 Lecture et ecriture textuelles . . . . . . . . . . . . . . . . . . . . 7.2.1 Lecture et ecriture de caract` eres et de cha nes . . . . . . . 7.2.2 Ecriture avec format printf . . . . . . . . . . . . . . . . 7.2.3 Lecture avec format scanf . . . . . . . . . . . . . . . . . 7.2.4 A propos de la fonction scanf et des lectures interactives 7.2.5 Les variantes de printf et scanf . . . . . . . . . . . . . . 7.3 Op erations en mode binaire . . . . . . . . . . . . . . . . . . . . . 7.3.1 Lecture- ecriture . . . . . . . . . . . . . . . . . . . . . . . . 7.3.2 Positionnement dans les chiers . . . . . . . . . . . . . . . 7.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.1 Fichiers en vrac . . . . . . . . . . . . . . . . . . . . . . 7.4.2 Fichiers binaires et chiers de texte . . . . . . . . . . . . . 7.4.3 Fichiers en acc` es relatif . . . . . . . . . . . . . . . . . . . 7.5 Les chiers de bas niveau dUNIX . . . . . . . . . . . . . . . . . 8 Autres el ements du langage C 8.1 Le pr eprocesseur . . . . . . . . . . . . . . . . . . . . . 8.1.1 Inclusion de chiers . . . . . . . . . . . . . . . 8.1.2 D enition et appel des macros . . . . . . . . 8.1.3 Compilation conditionnelle . . . . . . . . . . . 8.2 La modularit e de C . . . . . . . . . . . . . . . . . . . . 8.2.1 Fichiers en-t ete . . . . . . . . . . . . . . . . . . 8.2.2 Exemple : stdio.h . . . . . . . . . . . . . . . . 8.3 Deux ou trois choses bien pratiques... . . . . . . . . . . 8.3.1 Les arguments du programme principal . . . . 8.3.2 Branchements hors fonction : setjmp.h . . . . 8.3.3 Interruptions : signal.h . . . . . . . . . . . . . 8.4 La biblioth` eque standard . . . . . . . . . . . . . . . . 8.4.1 Aide ` a la mise au point : assert.h . . . . . . . 8.4.2 Fonctions utilitaires : stdlib.h . . . . . . . . . 8.4.3 Traitement de cha nes : string.h . . . . . . . 8.4.4 Classication des caract` eres : ctype.h . . . . . 8.4.5 Fonctions math ematiques : math.h . . . . . . . 8.4.6 Limites propres ` a limpl ementation : limits.h,

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . float.h

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

Imprim e le 28 janvier 2013

henri.garreta@univ-amu.fr

La plus r ecente version de ce document peut etre t el echarg ee ` a ladresse http://henri.garreta.perso.luminy.univmed.fr/Polys/PolyC.pdf

c H. Garreta, 1988-2013

EMENTS EL DE BASE

1
1.1

ements de base El
Structure g en erale dun programme

La transformation dun texte ecrit en langage C en un programme ex ecutable par lordinateur se fait en deux etapes : la compilation et l edition de liens. La compilation est la traduction des fonctions ecrites en C en des proc edures equivalentes ecrites dans un langage dont la machine peut ex ecuter les instructions. Le compilateur lit toujours un chier, appel e chier source, et produit un chier, dit chier objet. Chaque chier objet est incomplet, insusant pour etre ex ecut e, car il contient des appels de fonctions ou des r ef erences ` a des variables qui ne sont pas d enies dans le m eme chier. Par exemple, le premier programme que vous ecrirez contiendra d ej` a la fonction printf que vous naurez certainement pas ecrite vous-m eme. L edition de liens est lop eration par laquelle plusieurs chiers objets sont mis ensemble pour se compl eter mutuellement : un chier apporte des d enitions de fonctions et de variables auxquelles un autre chier fait r ef erence et r eciproquement. L editeur de liens (ou linker ) prend en entr ee plusieurs chiers objets et biblioth` eques (une vari et e particuli` ere de chiers objets) et produit un unique chier ex ecutable. L editeur de liens est largement ind ependant du langage de programmation utilis e pour ecrire les chiers sources, qui peuvent m eme avoir et e ecrits dans des langages di erents. Chaque chier source entrant dans la composition dun programme ex ecutable est fait dune succession dun nombre quelconque d el ements ind ependants, qui sont : des des des des des directives pour le pr eprocesseur (lignes commen cant par #), constructions de types (struct, union, enum, typedef), d eclarations de variables et de fonctions externes, d enitions de variables et d enitions de fonctions.

Seules les expressions des deux derni` eres cat egories font grossir le chier objet : les d enitions de fonctions laissent leur traduction en langage machine, tandis que les d enitions de variables se traduisent par des r eservations despace, eventuellement garni de valeurs initiales. Les autres directives et d eclarations sadressent au compilateur et il nen reste pas de trace lorsque la compilation est nie. En C on na donc pas une structure syntaxique englobant tout, comme la construction Program ... end. du langage Pascal ; un programme nest quune collection de fonctions assortie dun ensemble de variables globales. Do` u la question : par o` u lex ecution doit-elle commencer ? La r` egle g en eralement suivie par l editeur de liens est la suivante : parmi les fonctions donn ees il doit en exister une dont le nom est main. Cest par elle que lex ecution commencera ; le lancement du programme equivaut ` a lappel de cette fonction par le syst` eme dexploitation. Notez bien que, ` a part cela, main est une fonction comme les autres, sans aucune autre propri et e sp ecique ; en particulier, les variables internes ` a main sont locales, tout comme celles des autres fonctions. Pour nir cette entr ee en mati` ere, voici la version C du c el` ebre programme-qui-dit-bonjour, sans lequel on ne saurait commencer un cours de programmation 1 : #include <stdio.h> int main() { printf("Bonjour\n"); return 0; }

1.2
1.2.1

Consid erations lexicales


Pr esentation du texte du programme

Le programmeur est ma tre de la disposition du texte du programme. Des blancs, des tabulations et des sauts ` a la ligne peuvent etre plac es ` a tout endroit o` u cela ne coupe pas un identicateur, un nombre ou un symbole compos e 2.
1. Le programme montr e ici est ecrit selon des r` egles strictes. En fait, la plupart des compilateurs acceptent que main soit d eclar ee void au lieu de int, ou que ce type ne gure pas, et que linstruction return 0; napparaisse pas explicitement. 2. N eanmoins, les directives pour le pr eprocesseur (cf. section 8.1) doivent comporter un # dans la premi` ere position de la ligne. Cela ne constitue pas une exception ` a la r` egle donn ee ici, car le pr eprocesseur nest pas le compilateur C et ne travaille pas sur la syntaxe du langage.

c H. Garreta, 1988-2013

1.2

Consid erations lexicales

EMENTS EL DE BASE

Les commentaires commencent par /* et se terminent par */ : /* Ce texte est un commentaire et sera donc ignor e par le compilateur */ Les commentaires ne peuvent pas etre imbriqu es : ecrit dans un programme, le texte /* voici un grand /* et un petit */ commentaire */ est erron e, car seul /* voici un grand /* et un petit */ sera vu comme un commentaire par le compilateur. Les langages C et C++ cohabitant dans la plupart des compilateurs actuels, ces derniers acceptent egalement comme commentaire tout texte compris entre le signe // et la n de la ligne o` u ce signe appara t : // Ceci est un commentaire ` a la mode C++. Le caract` ere anti-slash \ pr ec edant imm ediatement un saut ` a la ligne masque ce dernier : la ligne suivante est consid er ee comme devant etre concat en ee ` a la ligne courante. Cela est vrai en toute circonstance, y compris ` a lint erieur dune cha ne de caract` eres. Par exemple, le texte message = "anti\ constitutionnellement"; est compris comme ceci : message = "anti 1.2.2 Mots-cl es

constitutionnellement";

Les mots suivants sont r eserv es. Leur fonction est pr evue par la syntaxe de C et ils ne peuvent pas etre utilis es dans un autre but : auto double int struct 1.2.3 break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while

Identicateurs

Un identicateur est une suite de lettres et chires contigus, dont le premier est une lettre. Lorsque seul le compilateur est concern e, cest-` a-dire lorsquil sagit didenticateurs dont la port ee est incluse dans un seul chier (nous dirons de tels identicateurs quils sont priv es ) : en toute circonstance une lettre majuscule est tenue pour di erente de la lettre minuscule correspondante ; dans les identicateurs, le nombre de caract` eres discriminants est au moins de 31. Attention, lorsquil sagit didenticateurs externes, cest-` a-dire partag es par plusieurs chiers sources, il est possible que sur un syst` eme particulier l editeur de liens sous-jacent soit trop rustique pour permettre le respect de ces deux prescriptions. Le caract` ere (appel e blanc soulign e ) est consid er e comme une lettre ; il peut donc gurer ` a nimporte quelle place dans un identicateur. Cependant, par convention un programmeur ne doit pas utiliser des identicateurs qui commencent par ce caract` ere. Cela assure quil ny aura jamais de conit avec les noms introduits (` a travers les chiers .h ) pour les besoins des biblioth` eques, car ces noms commencent par un tel blanc soulign e. 1.2.4 Op erateurs

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

Symboles compos es : -> ++ -<= >= == != && || << >> += -= *= /= %= <<= >>= |= &= ^= Tous ces symboles sont reconnus par le compilateur comme des op erateurs. Il est interdit dins erer des caract` eres blancs ` a lint erieur dun symbole compos e. En outre, il est conseill e dencadrer par des blancs toute utilisation dun op erateur. Dans certaines circonstances cette r` egle est plus quun conseil, car sa non-observance cr ee une expression ambigu e. 6
c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.3

Constantes litt erales

1.3
1.3.1

Constantes litt erales


Nombres entiers

Les constantes litt erales num eriques enti` eres ou r eelles suivent les conventions habituelles, avec quelques particularit es. Les constantes litt erales sont sans signe : lexpression 123 est comprise comme lapplication de lop erateur unaire ` a la constante 123 ; mais puisque le calcul est fait pendant la compilation, cette subtilit e na aucune cons equence pour le programmeur. Notez aussi quen C original, comme il nexiste pas dop erateur + unaire, la notation +123 est interdite. Les constantes litt erales enti` eres peuvent aussi s ecrire en octal et en hexad ecimal : une constante ecrite en octal (base 8) commence par 0 (z ero) ; une constante ecrite en hexad ecimal (base 16) commence par 0x ou 0X. Voici par exemple trois mani` eres d ecrire le m eme nombre : 27 033 0x1B

D etail ` a retenir : on ne doit pas ecrire de z ero non signicatif ` a gauche dun nombre : 0123 ne repr esente pas la m eme valeur que 123. Le type dune constante enti` ere est le plus petit type dans lequel sa valeur peut etre repr esent ee. Ou, plus exactement : si elle est d ecimale : si possible int, sinon long, sinon unsigned long ; si elle est octale ou hexad ecimale : si possible int, sinon unsigned int, sinon unsigned long. Certains suxes permettent de changer cette classication : U, u : indique que la constante est dun type unsigned ; L, l : indique que la constante est dun type long. Exemples : 1L, 0x7FFFU. On peut combiner ces deux suxes : 16UL.

1.3.2

Nombres ottants

Une constante litt erale est lexpression dun nombre ottant si elle pr esente, dans lordre : une suite de chires d ecimaux (la partie enti` ere), un point, qui joue le r ole de virgule d ecimale, une suite de chires d ecimaux (la partie fractionnaire), une des deux lettres E ou e, eventuellement un signe + ou -, une suite de chires d ecimaux. Les trois derniers el ements forment lexposant. Exemple : 123.456E-78. On peut omettre : la partie enti` ere ou la partie fractionnaire, mais pas les deux, le point ou lexposant, mais pas les deux. Exemples : .5e7, 5.e6, 5000000., 5e6 Une constante ottante est suppos ee de type double, ` a moins de comporter un suxe explicite : les suxes F ou f indiquent quelle est du type float ; les suxes L ou l indiquent quelle est du type long double. Exemples : 1.0L, 5.0e4f

1.3.3

Caract` eres et cha nes de caract` eres

Une constante de type caract` ere se note en ecrivant le caract` ere entre apostrophes. Une constante de type cha ne de caract` eres se note en ecrivant ses caract` eres entre guillemets. Exemples, trois caract` eres : A 2 "

Quatre cha nes de caract` eres : "A" "Bonjour ` a tous !" "" ""

On peut faire gurer nimporte quel caract` ere, m eme non imprimable, dans une constante caract` ere ou cha ne de caract` eres en utilisant les combinaisons suivantes, appel ees s equences d echappement :
c H. Garreta, 1988-2013

1.3

Constantes litt erales

EMENTS EL DE BASE

\n \t \b \r \f \a \\ " \d3 d2 d1

nouvelle ligne (LF) tabulation (HT) espace-arri` ere (BS) retour-chariot (CR) saut de page (FF) signal sonore (BELL) \ " le caract` ere qui a pour code le nombre octal d3 d2 d1 . Sil commence par un ou deux z eros et si cela ne cr ee pas une ambigu t e, on peut aussi le noter \d2 d1 ou \d1

Par exemple, la cha ne suivante d enit la suite des 9 caract` eres 3 A, escape (de code ASCII 27), B, , C, saut de page, D, \ et E : "A\033B\"C\fD\\E" Une constante de type caract` ere appartient au type char, cest-` a-dire entier repr esent e sur un octet. La valeur dune constante caract` ere est le nombre qui repr esente le caract` ere de mani` ere interne ; de nos jours il sagit presque toujours du code ASCII 4 . Une constante de type cha ne de caract` eres repr esente une suite nie de caract` eres, de longueur quelconque. Le codage interne dune cha ne de caract` eres est le suivant (voyez la gure 1) : les caract` eres constituant la cha ne sont rang es en m emoire, de mani` ere contigu e, dans lordre o` u ils gurent dans la cha ne ; ere nul est ajout e imm ediatement apr` es le dernier caract` ere de la cha ne, pour en indiquer la n ; un caract` la constante cha ne repr esente alors, ` a lendroit o` u elle est ecrite, ladresse de la cellule o` ua et e rang e le premier caract` ere de la cha ne.

"Bonjour" B o n j o u r \0
Figure 1 Repr esentation de la cha ne "Bonjour" Par cons equent, une constante cha ne de caract` eres a pour type celui dun tableau de caract` eres (cest-` a-dire char[] ) et pour valeur ladresse dune cellule de la m emoire.

Par caract` ere nul on entend le caract` ere dont le code interne est 0 ; on peut le noter indi eremment 0, \000 ou \0 (mais certainement pas 0) ; il est utilis e tr` es fr equemment 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 lun des types suivants : erale ; exemples : 1, A, "HELLO", 1.5e-2 ; toute constante litt une expression correcte form ee par lapplication dun op erateur courant (arithm etique, logique, etc.) ` a une ou deux expressions constantes ; exemples : -1, A - a, 2 * 3.14159265, "HELLO" + 6 ; ee par lapplication de lop erateur & (op erateur de calcul de ladresse, voyez la sec lexpression constitu tion 2.2.10) ` a une variable statique, ` a un champ dune variable statique de type structure ou ` a un el ement dun tableau statique dont le rang est donn e par une expression constante ; exemples : &x, &fiche.nom, &table[50] ; ee par lapplication de lop erateur sizeof ` a un descripteur de type. Exemples : lexpression constitu sizeof(int), sizeof(char *) ; ee par lapplication de lop erateur sizeof ` a une expression quelconque, qui ne sera lexpression constitu pas evalu ee ; exemples : sizeof x, sizeof(2 * x + 3). Les expressions constantes peuvent etre evalu ees pendant la compilation. Cela est fait ` a titre facultatif par les compilateurs de certains langages. En C ce nest pas facultatif : il est garanti que toute expression constante (et donc toute sous-expression constante dune expression quelconque) sera eectivement evalu ee avant que
3. Nous verrons quen fait cette cha ne comporte un caract` ere de plus qui en marque la n. 4. En standard le langage C ne pr evoit pas le codage Unicode des caract` eres.

c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.4

Types fondamentaux

lex ecution ne commence. En termes de temps dex ecution, l evaluation des expressions constantes est donc enti` erement gratuite .

1.4

Types fondamentaux
Types de base Nombres entiers Anonymes Petite taille sign es non sign es Taille moyenne sign es non sign es Grande taille sign es non sign es Nomm es Nombres ottants Simples Grande pr ecision Pr ecision encore plus grande rive s Types de Tableaux Fonctions Pointeurs Structures Unions

char unsigned char short unsigned short long unsigned long enum float double long double

[ ] ( ) * struct union Table 1 Les types du langage C

Le tableau 1 pr esente lensemble des types connus du compilateur C. Lorganisation g en erale de cet ensemble est evidente : on dispose de deux sortes de types de base, les nombres entiers et les nombres ottants, et dune famille innie de types d eriv es obtenus en appliquant quelques proc ed es r ecursifs de construction soit ` a des types fondamentaux soit ` a des types d eriv es d enis de la m eme mani` ere. Cette organisation r ev` ele un trait de lesprit de C : le pragmatisme lemporte sur lesth etisme, parfois m eme sur la rigueur. Dans dautres langages, les caract` eres, les bool eens, les constantes symboliques, etc., sont cod es de mani` ere interne par des nombres, mais ce fait est ociellement ignor e par le programmeur, qui reste oblig e de consid erer ces donn ees comme appartenant ` a des ensembles disjoints. En C on a fait le choix oppos e, laissant au programmeur le soin de r ealiser lui-m eme, ` a laide des seuls types num eriques, limplantation des types de niveau sup erieur.

1.4.1

Nombres entiers et caract` eres

La classication des types num eriques ob eit ` a deux crit` eres : Si on cherche ` a repr esenter un ensemble de nombres tous positifs on pourra adopter un type non sign e; au contraire si on doit repr esenter un ensemble contenant des nombres positifs et des nombres n egatifs on devra utiliser un type sign e 5. eme crit` ere de classication des donn ees num eriques est la taille requise par leur repr esentation. Le deuxi` Comme pr ec edemment, cest un attribut dun ensemble, et donc dune variable devant repr esenter tout el ement de lensemble, non dune valeur particuli` ere. Par exemple, le nombre 123 consid er e comme un
5. On dit parfois quune donn ee est un entier sign e ou est un entier non sign e . Cest un abus de langage : le caract` ere sign e ou non sign e nest pas un attribut dun nombre (un nombre donn e est positif ou n egatif, cest tout) mais de lensemble de nombres quon a choisi de consid erer et, par extension, de toute variable cens ee pouvoir repr esenter nimporte quelle valeur de cet ensemble.

c H. Garreta, 1988-2013

1.4

Types fondamentaux

EMENTS EL DE BASE

el ement de lensemble {0 ... 65535} est plus encombrant que le m eme nombre 123 quand il est consid er e comme un el ement de lensemble {0 ... 255}. Avec N chires binaires (ou bits ) on peut repr esenter : soit les 2N nombres positifs 0, 1, ... 2N 1 (cas non sign e) ; soit les 2N nombres positifs et n egatifs 2N 1 , ... 2N 1 1 (cas sign e). De plus, la repr esentation sign ee et la repr esentation non sign ee des el ements communs aux deux domaines (les nombres 0, 1, ... 2N 1 1) co ncident. `re. Un objet de type char peut Le type caracte etre d eni, au choix, comme un nombre entier pouvant repr esenter nimporte quel caract` ere du jeu de caract` eres de la machine utilis ee ; un nombre entier occupant la plus petite cellule de m emoire adressable s epar ement 6 . Sur les machines actuelles les plus r epandues cela signie g en eralement un octet (8 bits). Le plus souvent, un char est un entier sign e ; un unsigned char est alors un entier non sign e. Lorsque les char sont par d efaut non sign es, la norme ANSI pr evoit la possibilit e de d eclarer des signed char. On notera que la signication dun char en C, un entier petit, est tr` es di erente de celle dun char en Pascal (dans ce langage, lensemble des caract` eres et celui des nombres sont disjoints). En C, ch etant une variable de type char, rien ne soppose ` a l ecriture de lexpression ch - A + 32 qui est tout ` a fait homog` ene, puisque enti` erement faite de nombres. `re impossible . Toutes les valeurs quil est possible de ranger dans une variable de type Le caracte char sont en principe des caract` eres l egaux. Or la plupart des programmes qui lisent des caract` eres doivent etre capables de manipuler une valeur suppl ementaire, distincte de tous les vrais caract` eres, signiant la n des donn ees . Pour cette raison, les variables et fonctions qui repr esentent ou renvoient des caract` eres sont souvent d eclar ees int, non char : nimporte quelle valeur appartenant au type int mais nappartenant pas au type char peut alors servir dindicateur de n de donn ees. Par exemple, une telle valeur est d enie dans le chier stdio.h, cest la constante symbolique EOF. Les entiers courts et longs. Il est garanti que toute donn ee repr esentable dans le type short est repr esentable aussi dans le type long 7 (en bref : un long nest pas plus court quun short !), mais la taille exacte des donn ees de ces types nest pas x ee par la norme du langage. De nos jours on trouve souvent : unsigned short : 16 bits short : 16 bits unsigned long : 32 bits long : 32 bits pour pour pour pour repr esenter repr esenter repr esenter repr esenter un un un un nombre entier compris entre 0 et 65.535 nombre entier compris entre -32.768 et 32.767 nombre entier entre 0 et 4.294.967.296 entier entre -2.147.483.648 et 2.147.483.647

Le type int. En principe, le type int correspond ` a la taille dentier la plus ecace, cest-` a-dire la plus adapt ee ` a la machine utilis ee. Sur certains syst` emes et compilateurs int est synonyme de short, sur dautres il est synonyme de long. Le type int peut donc poser un probl` eme de portabilit e 8 : le m eme programme, compil e sur deux machines distinctes, peut avoir des comportements di erents. Do` u un conseil important : nutilisez le type int que pour des variables locales destin ees ` a contenir des valeurs raisonnablement petites (inf erieures en valeur absolue ` a 32767) . Dans les autres cas il vaut mieux expliciter char, short ou long selon le besoin. ens. En C il nexiste donc pas de type bool A propos des boole een sp ecique. Il faut savoir qu` a tout endroit o` u une expression bool eenne est requise (typiquement, dans des instructions comme if ou while ) on peut faire gurer nimporte quelle expression ; elle sera tenue pour vraie si elle est non nulle, elle sera consid er ee fausse sinon. Ainsi, dans un contexte conditionnel, expr (cest-` a-dire expr vraie ) equivaut ` a
6. A retenir : un objet de type char est unitaire aussi bien du point de vue des tailles que de celui des adresses. Quelle que soit la machine utilis ee, le compilateur C fera en sorte que le programmeur voie ces objets de la mani` ere suivante : si t est un tableau de char, la taille (au sens de lop erateur sizeof, cf. section 2.2.11) de t[0] vaut une unit e de taille, et l ecart entre les adresses de t[1] et t[0] vaut une unit e dadressage. On peut dire que ces propri et es d enissent le type char (ou, si vous pr ef erez, les unit es de taille et dadressage). 7. Si on consid` ere un type comme lensemble de ses valeurs, on a donc les inclusions larges char short long (et aussi float double long double). 8. Un programme ecrit pour une machine ou un syst` eme A est dit portable sil sut de le recompiler pour quil tourne correctement sur une machine di erente B. Par exemple, putchar(A); est une mani` ere portable dobtenir lachage du caract` ere A, tandis que putchar(65); est (sur un syst` eme utilisant le code ASCII) une mani` ere non portable dobtenir le m eme achage. Etre portable est un crit` ere de qualit e et de abilit e important. On invoque lecacit e pour justier l ecriture de programmes non portables ; lexp erience prouve que, lorsque son ecriture est possible, un programme portable est toujours meilleur quun programme non portable pr etendu equivalent.

10

c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.5 Variables

expr != 0 (expr di erente de 0). Inversement, lorsquun op erateur ( egalit e, comparaison, etc.) produit une valeur bool eenne, il rend 0 pour faux et 1 pour vrai. Signalons aux esth` etes que le chier <types.h> comporte les d eclarations : 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 le moins encombrant dans lequel on peut repr esenter ces deux valeurs. 1.4.2 Types enum er es

Un type enum er e, ou enum eration, est constitu e par une famille nie de nombres entiers, chacun associ e ` a un identicateur qui en est le nom. Mis ` a part ce qui touche ` a la syntaxe de leur d eclaration, il ny a pas grand-chose ` a dire ` a leur sujet. La syntaxe de la d eclaration des enum erations est expliqu ee ` a la section 5.3. Par exemple, l enonc e: enum jour_semaine { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche }; introduit un type enum er e, appel e enum jour semaine, constitu e par les constantes lundi valant 0, mardi valant 1, mercredi valant 2, etc. Ainsi, les expressions mardi + 2 et jeudi repr esentent la m eme valeur. Les valeurs dun type enum er e se comportent comme des constantes enti` eres ; elles font donc double emploi avec celles quon d enit ` a laide de #define (cf. section 8.1.2). Leur unique avantage r eside dans le fait que certains compilateurs d etectent parfois, mais ce nest pas exig e par la norme, les m elanges entre objets de types enum er es distincts ; ces types sont alors le moyen daugmenter la s ecurit e des programmes. A propos des types enum er es voyez aussi la section 5.3. 1.4.3 Nombres ottants

La norme ANSI pr evoit trois types de nombres ottants : float (simple pr ecision), double (double pr ecision) et long double (pr ecision etendue). La norme ne sp ecie pas les caract eristiques de tous ces types. Il est garanti que toute valeur repr esentable dans le type float est repr esentable sans perte dinformation dans le type double, et toute valeur repr esentable dans le type double lest dans le type long double. Typiquement, sur des syst` emes de taille moyenne, un float occupe 32 bits et un double 64, ce qui donne par exemple des float allant de -1.70E38 ` a -0.29E-38 et de 0.29E-38 ` a 1.70E38 avec 7 chires d ecimaux signicatifs, et des double allant de -0.90E308 ` a -0.56E-308 et de 0.56E-308 ` a 0.90E308 avec 15 chires d ecimaux signicatifs. Les long double correspondent g en eralement aux ottants de grande pr ecision manipul es par certains coprocesseurs arithm etiques ou les biblioth` eques de sous-programmes qui les simulent. Mais il nest pas exclu que sur un syst` eme particulier un long double soit la m eme chose quun double.

1.5
1.5.1

Variables
Syntaxe des d eclarations

La forme compl` ete de la d eclaration dune variable sera expliqu ee ` a la section 5.4. Dans le cas le plus simple on trouve sp ecication var-init , var-init , ... var-init ; o` u sp ecication est de la forme : char unsigned auto short signed register const long rien static volatile int extern rien float rien double long double
c H. Garreta, 1988-2013

11

1.5

Variables

EMENTS EL DE BASE

et chaque var-init est de la forme : identicateur Exemples : int x, y = 0, z; extern float a, b; static unsigned short cpt = 1000;

= expression rien

Les d eclarations de variables peuvent se trouver : en dehors de toute fonction, il sagit alors de variables globales ; ` a lint erieur dun bloc, il sagit alors de variables locales ; dans len-t ete dune fonction, il sagit alors darguments formels, plac es soit dans les parenth` eses de len-t ete (fonction d enie en syntaxe ANSI avec un prototype), soit entre le nom de la fonction et le { initial (fonction d enie 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 Visibilit e des variables

La question de la visibilit e des identicateurs (cest-` a-dire quels sont les identicateurs auxquels on peut faire r ef erence en un point dun programme ? ) est r egl ee en C comme dans la plupart des langages comportant la structure de bloc, avec une simplication : les fonctions ne peuvent pas etre imbriqu ees les unes dans les autres, et une complication : tout bloc peut comporter ses propres d enitions de variables locales. Un bloc est une suite de d eclarations et dinstructions encadr ee par une accolade ouvrante { et laccolade fermante } correspondante. Le corps dune fonction est lui-m eme un bloc, mais dautres blocs peuvent etre imbriqu es dans celui-l` a. Variables locales. Tout bloc peut comporter un ensemble de d eclarations de variables, qui sont alors dites locales au bloc en question. Une variable locale ne peut etre r ef erenc ee que depuis lint erieur du bloc o` u elle est d enie ; en aucun cas on ne peut y faire r ef erence depuis un point ext erieur ` a ce bloc. Dans le bloc o` u il est d eclar e, le nom dune variable locale masque toute variable de m eme nom d enie dans un bloc englobant le bloc en question. Toutes les d eclarations de variables locales ` a un bloc doivent etre ecrites au d ebut du bloc, avant la premi` ere instruction. Arguments formels. Pour ce qui concerne leur visibilit e, les arguments formels des fonctions sont consid er es comme des variables locales du niveau le plus haut, cest-` a-dire des variables d eclar ees au d ebut du bloc le plus ext erieur 9 . Un argument formel est accessible de lint erieur de la fonction, partout o` u une variable locale plus profonde ne le masque pas. En aucun cas on ne peut y faire r ef erence depuis lext erieur de la fonction. Variables globales. Le nom dune variable globale ou dune fonction peut etre utilis e depuis nimporte quel point compris entre sa d eclaration (pour une fonction : la n de la d eclaration de son en-t ete) et la n
9. Par cons equent, on ne doit pas d eclarer un argument formel et une variable locale du niveau le plus haut avec le m eme nom.

12

c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.5 Variables

du chier o` u la d eclaration gure, sous r eserve de ne pas etre masqu ee par une variable locale ou un argument formel de m eme nom. La question de la visibilit e inter-chiers est examin ee ` a la section 1.6. On peut noter dores et d ej` a quelle ne se pose que pour les variables globales et les fonctions, et quelle concerne l edition de liens, non la compilation, car le compilateur ne traduit quun chier source ` a la fois et, pendant la traduction dun chier, il ne voit pas les autres. 1.5.3 Allocation et dur ee de vie des variables

Les variables globales sont toujours statiques, cest-` a-dire permanentes : elles existent pendant toute la dur ee de lex ecution. Le syst` eme dexploitation se charge, imm ediatement avant lactivation du programme, de les allouer dans un espace m emoire de taille ad equate, eventuellement garni de valeurs initiales. A loppos e, les variables locales et les arguments formels des fonctions sont automatiques : lespace correspondant est allou e lors de lactivation de la fonction ou du bloc en question et il est rendu au syst` eme lorsque le contr ole quitte cette fonction ou ce bloc. Certains qualieurs (static, register, voir les sections 1.5.5 et 1.5.6) permettent de modier lallocation et la dur ee de vie des variables locales. Remarque. On note une grande similitude entre les variables locales et les arguments formels des fonctions : ils ont la m eme visibilit e et la m eme dur ee de vie. En r ealit e cest presque la m eme chose : les arguments formels sont de vraies variables locales avec lunique particularit e d etre automatiquement initialis es (par les valeurs des arguments eectifs) lors de lactivation de la fonction. 1.5.4 Initialisation des variables

Variables statiques. En toute circonstance la d eclaration dune variable statique peut indiquer une valeur initiale ` a ranger dans la variable. Cela est vrai y compris pour des variables de types complexes (tableaux ou structures). Exemple : double x = 0.5e3; int t[5] = { 11, 22, 33, 44, 55 }; Bien que la syntaxe soit analogue, une telle initialisation na rien en commun avec une aectation comme celles qui sont faites durant lex ecution du programme. Il sagit ici uniquement de pr eciser la valeur qui doit etre d epos ee dans lespace allou e` a la variable, avant que lex ecution ne commence. Par cons equent : la valeur initiale doit etre d enie par une expression constante (calculable durant la compilation) ; une telle initialisation est enti` erement gratuite, elle na aucune incidence ni sur la taille ni sur la dur ee du programme ex ecutable produit. Les variables statiques pour lesquelles aucune valeur initiale nest indiqu ee sont remplies de z eros. Linterpr etation de ces z eros d epend du type de la variable. Variables automatiques. Les arguments formels des fonctions sont automatiquement initialis es lors de leur cr eation (au moment de lappel de la fonction) par les valeurs des arguments eectifs. Cela est la d enition m eme des arguments des fonctions. La d eclaration dune variable locale peut elle aussi comporter une initialisation. Mais il ne sagit pas de la m eme sorte dinitialisation que pour les variables statiques : linitialisation repr esente ici une aectation tout ` a fait ordinaire. Ainsi, plac ee ` a lint erieur dun bloc, la construction int i = exp ; equivaut au couple int i; ... i = exp ; /* d eclaration */ /* affectation */ /* d eclaration + initialisation */

Par cons equent : lexpression qui donne la valeur initiale na pas ` a etre constante, puisquelle est evalu ee ` a lex ecution, chaque fois que la fonction ou le bloc est activ e; une telle initialisation co ute le m eme prix que laectation correspondante, cest-` a-dire le temps d evaluation de lexpression qui d enit la valeur initiale. Les variables automatiques pour lesquelles aucune valeur initiale nest indiqu ee sont allou ees avec une valeur impr evisible.
c H. Garreta, 1988-2013

13

1.5

Variables

EMENTS EL DE BASE

Remarque. Dans le C original, une variable automatique ne peut etre initialis ee que si elle est simple (cest-` a-dire autre que tableau ou structure). Cette limitation ne fait pas partie du C ANSI. 1.5.5 Variables locales statiques

Le qualieur static, plac e devant la d eclaration dune variable locale, produit une variable qui est pour sa visibilit e, locale ; pour sa dur ee de vie, statique (cest-` a-dire permanente). Elle nest accessible que depuis lint erieur du bloc o` u elle est d eclar ee, mais elle est cr e ee au d ebut de lactivation du programme et elle existe aussi longtemps que dure lex ecution de celui-ci. Exemple : void bizarre1(void) { static int cpt = 1000; printf("%d ", cpt); cpt++; } Lorsque la d eclaration dune telle variable comporte une initialisation, il sagit de linitialisation dune variable statique : elle est eectu ee une seule fois avant lactivation du programme. Dautre part, une variable locale statique conserve sa valeur entre deux activations cons ecutives de la fonction. Ainsi, des appels successifs de la fonction ci-dessus produisent lachage des valeurs 1000, 1001, 1002, etc. On aurait pu obtenir un eet analogue avec le programme int cpt = 1000; void bizarre2(void) { printf("%d ", cpt); cpt++; } mais ici la variable cpt est globale et peut donc etre modi ee inconsid er ement par une autre fonction, ou entrer en conit avec un autre objet de m eme nom, tandis que dans la premi` ere version elle nest visible que depuis lint erieur de la fonction et donc ` a labri des manipulations maladroites et des collisions de noms. On notera pour nir que la version suivante est erron ee : void bizarre3(void) { int cpt = 1000; printf("%d ", cpt); cpt++; } En eet, tous les appels de bizarre3 acheront la m eme valeur 1000. Attention. Malgr e tout le bien quon vient den dire, les variables locales statiques ont une particularit e potentiellement fort dangereuse : il en existe une seule instance pour toutes les activations de la fonction dans laquelle elles sont d eclar ees. Ainsi, dans lexemple suivant : void fonction_suspecte(void) { static int i; ... fonction_suspecte(); ... } la valeur de la variable i avant et apr` es lappel de fonction suspecte (cest-` a-dire aux points et ) peut ne pas etre la m eme, car la deuxi` eme activation de fonction suspecte acc` ede aussi ` a i. Cela est tout ` a fait inhabituel pour une variable locale. Cons equence ` a retenir : les variables locales statiques se marient mal avec la r ecursivit e. 1.5.6 Variables critiques

Le qualieur register pr ec edant une d eclaration de variable informe le compilateur que la variable en question est tr` es fr equemment acc ed ee pendant lex ecution du programme et quil y a donc lieu de prendre toutes les dispositions utiles pour en acc el erer lacc` es. Par exemple, dans certains calculateurs de telles variables 14
c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.6 Variables, fonctions et compilation s epar ee

sont log ees dans un registre de lunit e centrale de traitement (CPU) plut ot que dans la m emoire centrale ; de cette mani` ere lacc` es ` a leur valeur ne met pas en uvre le bus de la machine. Les variables ainsi d eclar ees doivent etre locales et dun type simple (nombre, pointeur). Elles sont automatiquement initialis ees ` a z ero chaque fois quelles sont cr e ees. Le compilateur accorde ce traitement sp ecial aux variables dans lordre o` u elles gurent dans les d eclarations. Lorsque cela nest plus possible (par exemple, parce que tous les registres de la CPU sont pris) les d eclarations register restantes sont ignor ees. Il convient donc dappliquer ce qualieur aux variables les plus critiques dabord. Exemple : char *strcpy(char *dest, char *srce) { register char *d = dest, *s = srce; while ((*d++ = *s++) != 0) ; return dest; } Attention. Lutilisation du qualieur register est int eressante lorsque lon doit utiliser un compilateur rustique, peu optimisateur . Or de nos jours les compilateurs de C ont ni par devenir tr` es perfectionn es et int` egrent des algorithmes doptimisation, parmi lesquels la d etermination des variables critiques et leur allocation dans les registres de la CPU. Il sav` ere alors que le programmeur, en appliquant le qualieur register ` a ses variables pr ef er ees (quil croit critiques alors quelles ne le sont pas r eellement), g` ene le travail du compilateur et obtient un programme moins ecace que sil navait jamais utilis e ce qualieur.

1.5.7

Variables constantes et volatiles

Le qualieur const plac e devant une variable ou un argument formel informe le compilateur que la variable ou largument en question ne changera pas de valeur tout au long de lex ecution du programme ou de lactivation de la fonction. Ce renseignement permet au compilateur doptimiser la gestion de la variable, la nature exacte dune telle optimisation n etant pas sp eci ee. Par exemple un compilateur peut juger utile de ne pas allouer du tout une variable quali ee const et de remplacer ses occurrences par la valeur initiale 10 indiqu ee lors de la d eclaration. Il est conseill e de toujours d eclarer const les variables et les arguments formels qui peuvent l etre. Note. Cest regrettable mais, pour la plupart des compilateurs, une variable quali ee const nest pas tout ` a fait une expression constante au sens de la section 1.3.4. En particulier, pour ces compilateurs une variable, m eme quali ee const, ne peut pas etre utilis ee pour indiquer le nombre d el ements dans une d eclaration de tableau. Le C ANSI introduit aussi les notions de pointeur constant et de pointeur sur constante, expliqu ees ` a la section 5.4.2. Le sens du qualieur volatile d epend lui aussi de limpl ementation. Il diminue le nombre dhypoth` eses, et donc doptimisations, que le compilateur peut faire sur une variable ainsi quali ee. Par exemple toute variable dont la valeur peut etre modi ee de mani` ere asynchrone (dans une fonction de d etection dinterruption, ou par un canal dentr ee-sortie, etc.) doit etre quali ee volatile, sur les syst` emes o` u cela a un sens. Cela pr evient le compilateur que sa valeur peut changer myst erieusement, y compris dans une section du programme qui ne comporte aucune r ef erence ` a cette variable. Les compilateurs sont tenus de signaler toute tentative d ecelable de modication dune variable const. Mis ` a part cela, sur un syst` eme particulier ces deux qualieurs peuvent navoir aucun autre eet. Ils nappartiennent pas au C original.

1.6
1.6.1

Variables, fonctions et compilation s epar ee


Identicateurs publics et priv es

Examinons maintenant les r` egles qui r egissent la visibilit e inter-chiers des identicateurs. La question ne concerne que les noms de variables et de fonctions, car les autres identicateurs (noms de structures, de types, etc.) nexistent que pendant la compilation et ne peuvent pas etre partag es par deux chiers. Il ny a pas de probl` eme pour les variables locales, dont la visibilit e se r eduit ` a l etendue de la fonction ou du bloc contenant leur d enition. Il ne sera donc question que des noms des variables globales et des noms des fonctions.
10. La d eclaration dune variable const doit n ecessairement comporter une initialisation car sinon, une telle variable ne pouvant pas etre aect ee par la suite, elle naurait jamais de valeur d enie.

c H. Garreta, 1988-2013

15

1.6

Variables, fonctions et compilation s epar ee

EMENTS EL DE BASE

Jargon. Identicateurs publics et priv es. Un nom de variable ou de fonction d eni dans un chier source et pouvant etre utilis e dans dautres chiers sources est dit public. Un identicateur qui nest pas public est appel e priv e. Regle 1. Sauf indication contraire, tout identicateur global est public ; le qualieur static, pr ec edant la d eclaration dun identicateur global, rend celui-ci priv e. On prendra garde au fait que le qualieur static na pas le m eme eet quand il sapplique ` a un identicateur local (static change la dur ee de vie, dautomatique en statique, sans toucher ` a la visibilit e) et quand il sapplique ` a un identicateur global (static change la visibilit e, de publique en priv ee, sans modier la dur ee de vie). Lorsquun programme est d ecompos e en plusieurs chiers sources il est fortement conseill e, pour ne pas dire obligatoire, dutiliser le qualieur static pour rendre priv es tous les identicateurs qui peuvent l etre. Si on ne suit pas cette recommandation on verra des chiers qui etaient corrects s epar ement devenir erron es lorsquils sont reli es, uniquement parce quils partagent ` a tort des identicateurs publics. 1.6.2 D eclaration dobjets externes

Nous ne consid erons donc d esormais que les noms publics. Un identicateur r ef erenc e dans un chier alors quil est d eni dans un autre chier est appel e externe. En g en eral, les noms externes doivent faire lobjet dune d eclaration : le compilateur ne traitant quun chier ` a la fois, les propri et es de lobjet externe doivent etre indiqu ees pour que la compilation puisse avoir lieu correctement. Jargon. D enition et d eclaration dune variable ou dune fonction. Aussi bien une d eclaration quune d enition dun nom de variable ou de fonction est une formule qui sp ecie la classe syntaxique (variable ou fonction) et les attributs (type, valeur initiale, etc.) de lidenticateur en question. En plus de cela : une d enition produit la cr eation de lobjet dont lidenticateur est le nom ; une d eclaration se limite ` a indiquer que lobjet en question a d u etre cr e e dans un autre chier qui sera fourni lors de l edition de liens. ( Cr eer une variable ou une fonction cest r eserver lespace correspondant, rempli par l eventuelle valeur initiale de la variable ou par le code de la fonction). Regle 2. Toute variable doit avoir et e d enie (cest-` a-dire d eclar ee normalement) ou d eclar ee externe avant son utilisation ; une fonction peut etre r ef erenc ee alors quelle na encore fait lobjet daucune d enition ni d eclaration externe ; elle est alors suppos ee etre externe, ` a r esultat entier (int), sans prototype (cf. section 4.2) ; par cons equent, si une fonction nest pas ` a r esultat entier alors elle doit etre soit d enie soit d eclar ee externe avant son appel, m eme si elle est ult erieurement d enie dans le chier o` u gure lappel. La d eclaration externe dune variable sobtient en faisant pr ec eder une d eclaration ordinaire du mot-cl e extern. Exemple : extern unsigned long n; Dans un autre chier cette variable aura et e d enie : unsigned long n; La d eclaration externe dune variable doit etre identique, au mot extern pr` es, ` a sa d enition. Sauf pour les deux points suivants : une d eclaration externe ne doit pas comporter dinitialisateur (puisque la d eclaration externe nalloue pas la variable), eclaration externe de tableau, il est inutile dindiquer la taille de celui-ci (puisque la d eclaration dans une d externe nalloue pas le tableau). Exemple. Dans le chier o` u sont d enies les variables n et table, on ecrira : unsigned long n = 1000; int table[100]; Dans un autre chier, o` u ces variables sont uniquement r ef erenc ees, on ecrira : extern unsigned long n; extern int table[]; 16
c H. Garreta, 1988-2013

EMENTS EL DE BASE

1.6 Variables, fonctions et compilation s epar ee

La d eclaration externe dune fonction sobtient en ecrivant len-t ete de la fonction, pr ec ed e du mot extern et suivi dun point-virgule ; le mot extern est facultatif. Exemple : d enition de la fonction double carre(double x) { return x * x; } D eclaration externe dans un autre chier : double carre(double x); ou double carre(double); ou lun ou lautre de ces enonc es, pr ec ed e du mot extern. En syntaxe originale (cest-` a-dire D enition :

sans prototype ) il faut en outre ne pas ecrire les arguments formels.

double carre(x) double x; { return x * x; } D eclaration externe dans un autre chier : double carre(); Regle 3. Dans lensemble des chiers qui constituent un programme, chaque nom public : doit faire lobjet dune et une seule d enition ; peut etre d eclar e externe (y compris dans le chier o` u il est d eni) un nombre quelconque de fois. Cette r` egle volontariste est simple et elle exprime la meilleure fa con de programmer. Il faut savoir cependant que chaque syst` eme tol` ere des ecarts, qui r ev` elent surtout la rusticit e de l editeur de liens sous-jacent. La clart e des concepts et la abilit e des programmes y perdent beaucoup. Un comportement fr equent est le suivant : appelons momentan ement sion g en erale de la forme { externopt declaration Nous pouvons donner la r` egle rel ach ee : Regle 3. Dans lensemble des chiers qui constituent un programme, chaque nom public peut faire lobjet dun nombre quelconque de d eclarations-d enitions, mais : il doit y avoir au moins une d eclaration-d enition sans le mot-cl e extern ; il peut y avoir au plus une d eclaration-d enition comportant un initialisateur. Des techniques et conseils pour ecrire des programmes modulaires en C sont expos es ` a la section 8.2.

d eclaration-d enition une expres} ;

= initialisateur rien

c H. Garreta, 1988-2013

17

OPERATEURS ET EXPRESSIONS

2
2.1

Op erateurs et expressions
G en eralit es

Dans cette section nous etudions les op erateurs et les expressions du langage C. Les expressions simples sont les constantes litt erales (0, A, 0.31416e1, etc.), les constantes symboliques (lundi, false, etc.) et les noms de variables (x, nombre, etc.). Les op erateurs servent ` a construire des expressions complexes, comme 2 * x + 3 ou sin(0.31416e1). Les propri et es dune expression complexe d ecoulent essentiellement de la nature de lop erateur qui chapeaute lexpression. On peut distinguer les expressions pures des expressions avec eet de bord 11 . Dans tous les cas une expression repr esente une valeur. Une expression pure ne fait que cela : l etat du syst` eme est le m eme avant et apr` es son evaluation. Au contraire, une expression ` a eet de bord modie le contenu dune ou plusieurs variables. Par exemple, lexpression y + 1 est pure, tandis que laectation x = y + 1 (qui en C est une expression) est ` a eet de bord, car elle modie la valeur de la variable x. Comme nous le verrons, en C un grand nombre dexpressions ont de tels eets. Remarque 1. Les op erateurs dont il sera question ici peuvent aussi appara tre dans les d eclarations, pour la construction des types d eriv es (tableaux, pointeurs et fonctions) comme dans la d eclaration complexe : char (*t[20])(); La signication des expressions ainsi ecrites est alors tr` es di erente de celle des expressions gurant dans la partie ex ecutable des programmes, mais on peut signaler dores et d ej` a que toutes ces constructions ob eissent aux m emes r` egles de syntaxe, notamment pour ce qui concerne la priorit e des op erateurs et lusage des parenth` eses. La question des d eclarations complexes sera vue ` a la section 5.4. Remarque 2. Cest une originalit e de C que de consid erer les d esignateurs complexes (les objets point es, les el ements des tableaux, les champs des structures, etc.) comme des expressions construites avec des op erateurs et ob eissant ` a la loi commune, notamment pour ce qui est des priorit es. On se souvient quen Pascal les signes qui permettent d ecrire de tels d esignateurs (cest-` a-dire les s electeurs [], ^ et .) nont pas statut dop erateur. Il ne viendrait pas ` a lid ee dun programmeur Pascal d ecrire 2 + (t[i]) an de lever une quelconque ambigu t e sur la priorit e de + par rapport ` a celle de [], alors quen C de telles expressions sont habituelles. Bien s ur, dans 2 + (t[i]) les parenth` eses sont superues, car la priorit e de lop erateur [] est sup erieure ` a celle de +, mais ce nest pas le cas dans (2 + t)[i], qui est une expression egalement l egitime. 2.1.1 Lvalue et rvalue

Toute expression poss` ede au moins deux attributs : un type et une valeur. Par exemple, si i est une variable enti` ere valant 10, lexpression 2 * i + 3 poss` ede le type entier et la valeur 23. Dans la partie ex ecutable des programmes on trouve deux sortes dexpressions : Lvalue (expressions signiant le contenu de... ). Certaines expressions sont repr esent ees par une formule qui d etermine un emplacement dans la m emoire ; la valeur de lexpression est alors d enie comme le contenu de cet emplacement. Cest le cas des noms des variables, des composantes des enregistrements et des tableaux, etc. Une lvalue poss` ede trois attributs : une adresse, un type et une valeur. Exemples : x, table[i], fiche.numero. Rvalue (expressions signiant la valeur de... ). Dautres expressions ne sont pas associ ees ` a un emplacement de la m emoire : elles ont un type et une valeur, mais pas dadresse. Cest le cas des constantes et des expressions d enies comme le r esultat dune op eration arithm etique. Une rvalue ne poss` ede que deux attributs : type, valeur. Exemples : 12, 2 * i + 3. Toute lvalue peut etre vue comme une rvalue ; il sut dignorer le contenant (adresse) pour ne voir que le contenu (type, valeur). La raison principale de la distinction entre lvalue et rvalue est la suivante : seule une lvalue peut gurer ` a gauche du signe = dans une aectation ; nimporte quelle rvalue peut appara tre ` a droite. Cela justie les appellations lvalue ( left value ) et rvalue ( right value ). Les sections suivantes pr eciseront, pour chaque sorte dexpression complexe, sil sagit ou non dune lvalue. Pour les expressions simples, cest-` a-dire les constantes litt erales et les identicateurs, la situation est la suivante : sont des lvalue : les noms des variables simples (nombres et pointeurs), les noms des variables dun type struct ou union
11. Eet de bord est un barbarisme ayant pour origine lexpression anglaise side eect quon peut traduire par eet secondaire souvent un peu cach e, parfois dangereux.

18

c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

ne sont pas des lvalue : les constantes, les noms des variables de type tableau, les noms des fonctions. 2.1.2 Priorit e des op erateurs

C comporte de tr` es nombreux op erateurs. La plupart des caract` eres sp eciaux d esignent des op erations et subissent les m emes r` egles syntaxiques, notamment pour ce qui concerne le jeu des priorit es et la possibilit e de parenth esage. La table 2 montre lensemble de tous les op erateurs, class es par ordre de priorit e. prior 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 op erateurs () [] ! ~ *bin / + -bin << >> < <= == != &bin ^ | && || ? : = *= , sens de lassociativit e sizeof (type) >>= &= ^= |=

. ++ %

-> --

-un

*un

&un

>

>=

/=

%=

+=

-=

<<=

Table 2 Op erateurs du langage C Remarque. Dans certains cas un m eme signe, comme -, d esigne deux op erateurs, lun unaire (` a un argument), lautre binaire (` a deux arguments). Dans le tableau 2, les suxes un et bin pr ecisent de quel op erateur il sagit. Le signe indique lassociativit e de gauche ` a droite ; par exemple, lexpression x - y - z signie (x y) - z. Le signe indique lassociativit e de droite ` a gauche ; par exemple, lexpression x = y = z signie x = (y = z). Notez que le sens de lassociativit e des op erateurs pr ecise la signication dune expression, mais en aucun cas la chronologie de l evaluation des sous-expressions.

2.2
2.2.1

Pr esentation d etaill ee des op erateurs


Appel de fonction ()

Op eration : application dune fonction ` a une liste de valeurs. Format : exp 0 ( exp 1 , ... exp n ) exp 1 , ... exp n sont appel es les arguments eectifs de lappel de la fonction. exp 0 doit etre de type fonction rendant une valeur de type T ou bien 12 adresse dune fonction rendant une valeur de type T. Alors exp 0 ( exp 1 , ... exp n ) poss` ede le type T. La valeur de cette expression d ecoule de la d enition de la fonction (` a lint erieur de la fonction, cette valeur est pr ecis ee par une ou plusieurs instructions return exp ; ). Lexpression exp 0 ( exp 1 , ... exp n ) nest pas une lvalue. Exemple : y = carre(2 * x) + 3; sachant que carre est le nom dune fonction rendant un double, lexpression carre(2 * x) a le type double. Un coup dil au corps de la fonction (cf. section 1.6.2) pourrait nous apprendre que la valeur de cette expression nest autre que le carr e de son argument, soit ici la valeur 4x2 .
12. Cette double possibilit e est comment ee ` a la section 6.3.4.

c H. Garreta, 1988-2013

19

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

Les contraintes support ees par les arguments eectifs ne sont pas les m emes dans le C ANSI et dans le C original (ceci est certainement la plus grande di erence entre les deux versions du langage) : A. En C ANSI, si la fonction a et e d enie ou d eclar ee avec prototype (cf. section 4.1) : le nombre des arguments eectifs doit correspondre ` a celui des arguments formels 13 ; chaque argument eectif doit etre compatible, au sens de laectation, avec largument formel correspondant ; la valeur de chaque argument eectif subit eventuellement les m emes conversions quelle subirait dans laectation argument formel = argument eectif B. En C original, ou en C ANSI si la fonction na pas et e d enie ou d eclar ee avec prototype : aucune contrainte (de nombre ou de type) nest impos ee aux arguments eectifs ; tout argument eectif de type char ou short est converti en int ; tout argument eectif de type float est converti en double ; les autres arguments eectifs ne subissent aucune conversion. Par exemple, avec la fonction carre d enie ` a la section 1.6.2, lappel y = carre(2); est erron e en C original (la fonction re coit la repr esentation interne de la valeur enti` ere 2 en croyant que cest la repr esentation interne dun double) mais il est correct en C ANSI (lentier 2 est converti en double au moment de lappel). Toutes ces questions sont reprises plus en d etail ` a la section 4. Remarque 1. Bien noter que les parenth` eses doivent appara tre, m eme lorsque la liste des arguments est vide. Leur absence ne provoque pas derreur, mais change compl` etement le sens de lexpression. Cela constitue un pi` ege assez vicieux tendu aux programmeurs dont la langue maternelle est Pascal. Remarque 2. Il faut savoir que lorsquune fonction a et e d eclar ee comme ayant des arguments en nombre variable (cf. section 4.3.4) les arguments correspondant ` a la partie variable sont trait es comme les arguments des fonctions sans prototype, cest-` a-dire selon les r` egles de la section B ci-dessus. Cette remarque est loin d etre marginale car elle concerne, excusez du peu, les deux fonctions les plus utilis ees : printf et scanf. Ainsi, quand le compilateur rencontre linstruction printf(expr 0 , expr 1 , ... expr k ); il v erie que expr 0 est bien de type char *, mais les autres param` etres eectifs expr 1 , ... expr k sont trait es selon les r` egles de la section B. 2.2.2 Indexation []

finition restreinte ( De el ement dun tableau). Op eration : acc` es au ieme el ement dun tableau. Format : exp 0 [ exp 1 ] exp 0 doit etre de type tableau dobjets de type T , exp 1 doit etre dun type entier. Alors exp 0 [exp 1 ] est de type T ; cette expression d esigne l el ement du tableau dont lindice est donn e par la valeur de exp 1 . Deux d etails auxquels on tient beaucoup en C : el ement dun tableau a toujours lindice 0 ; le premier il nest jamais v eri e que la valeur de lindice dans une r ef erence ` a un tableau appartient ` a lintervalle 0...N 1 d etermin e par le nombre N d el ements allou es par la d eclaration du tableau. Autrement dit, il ny a jamais de test de d ebordement . Exemple. Lexpression t[0] d esigne le premier el ement du tableau t, t[1] le second, etc. En C, les tableaux sont toujours ` a un seul indice ; mais leurs composantes peuvent etre ` a leur tour des tableaux. Par exemple, un el ement dune matrice rectangulaire sera not e: m[i][j] Une telle expression suppose que m[i] est de type tableau dobjets de type T et donc que m est de type tableau de tableaux dobjets de type T . Cest le cas, par exemple, si m a et e d eclar ee par une expression de la forme (NL et NC sont des constantes) :

double m[NL][NC];
13. Il existe n eanmoins un moyen pour ecrire des fonctions avec un nombre variable darguments (cf. section 4.3.4)

20

c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

finition comple `te (indexation au sens large). Op De eration : acc` es ` a un objet dont ladresse est donn ee par une adresse de base et un d eplacement. Format : exp 0 [ exp 1 ] exp 0 doit etre de type adresse dun objet de type T , exp 1 doit etre de type entier. Alors exp 0 [exp 1 ] d esigne lobjet de type T ayant pour adresse (voir la gure 2) : valeur(exp 0 ) + valeur(exp 1 ) taille(T)

exp0

exp1

Figure 2 Lindexation Il est clair que, si exp 0 est de type tableau, les deux d enitions de lindexation donn ees co ncident. Exemple : si t est un tableau dentiers et p un pointeur vers entier auquel on a aect e ladresse de t[0], alors les expressions suivantes d esignent le m eme objet : t[i] p[i] *(p + i) *(t + i)

Dans un cas comme dans lautre, lexpression exp 0 [exp 1 ] est une lvalue, sauf si elle est de type tableau. 2.2.3 S election .

Op eration : acc` es ` a un champ dune structure ou dune union. Format : exp . identif exp doit poss eder un type struct ou union, et identif doit etre le nom dun des champs de la structure ou de lunion en question. En outre, exp doit etre une lvalue. Alors, exp .identif d esigne le champ identif de lobjet d esigne par exp. Cette expression est une lvalue, sauf si elle est de type tableau. Exemple. Avec la d eclaration struct personne { long int num; struct { char rue[32]; char *ville; } adresse; } fiche; les expressions fiche.num fiche.adresse fiche.adresse.rue etc. d esignent les divers champs de la variable che. Seules les deux premi` eres sont des lvalue. 2.2.4 S election dans un objet point e ->

Op eration : acc` es au champ dune structure ou dune union point ee. Format : exp ->identif exp doit poss eder le type adresse dune structure ou dune union et identif doit etre le nom dun des champs de la structure ou de lunion en question. Dans ces conditions, exp ->identif d esigne le champ identif de la structure ou de lunion dont ladresse est indiqu ee par la valeur de exp. Cette expression est une lvalue, sauf si elle est de type tableau. Ainsi, exp ->identif est strictement synonyme de (*exp ).identif (remarquez que les parenth` eses sont indispensables). Par exemple, avec la d eclaration :
c H. Garreta, 1988-2013

21

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

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

N egation !

Op eration : n egation logique. Format : !exp Aucune contrainte. Cette expression d esigne une valeur de lensemble {0, 1}, d enie par : { !exp 1, si exp = 0 0, si exp = 0

Cette expression nest pas une lvalue. Remarque. Bien que cela ne soit pas exig e par le langage C, on evitera de nier (et plus g en eralement de comparer ` a z ero) des expressions dun type ottant (float, double). A cause de limpr ecision inh erente ` a la plupart des calculs avec de tels nombres, l egalit e` a z ero dun ottant nest souvent que le fruit du hasard.

2.2.6

Compl ement ` a1~

Op eration : n egation bit ` a bit. Format : ~exp exp doit etre dun type entier. Cette expression d esigne lobjet de m eme type que exp qui a pour codage interne la conguration de bits obtenue en inversant chaque bit du codage interne de la valeur de exp : 1 devient 0, 0 devient 1. Cette expression nest pas une lvalue. Remarque. Le compl ement ` a un nest pas une op eration abstraite (cf. section 2.3.3). La portabilit e dun programme o` u cet op erateur gure nest donc pas assur ee.

2.2.7

Les c el` ebres ++ et --

Il existe deux op erateurs unaires ++ di erents : lun est postx e ( ecrit derri` ere lop erande), lautre pr ex e ( ecrit devant). 1. Op eration : post-incr ementation. Format : exp ++ exp doit etre de type num erique (entier ou ottant) ou pointeur. Ce doit etre une lvalue. Cette expression est caract eris ee par : un type : celui de exp ; eme que exp avant l evaluation de exp ++ ; une valeur : la m un eet de bord : le m eme que celui de laectation exp = exp + 1. 2. Op eration : pr e-incr ementation. Format : ++exp exp doit etre de type num erique (entier ou ottant) ou pointeur. Ce doit etre une lvalue. Cette expression est caract eris ee par : un type : celui de exp ; une valeur : la m eme que exp apr` es l evaluation de exp ++ ; eme que celui de laectation exp = exp + 1. un eet de bord : le m Les expressions exp ++ et ++exp ne sont pas des lvalue. 22
c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

Exemple. Laectation y = x++; y = ++x; equivaut ` a y = x; x = x + 1; x = x + 1; y = x;

Lop erateur ++ b en ecie de larithm etique des adresses au m eme titre que +. Ainsi, si exp est de type pointeur vers un objet de type T , la quantit e eectivement ajout ee ` a exp par lexpression exp ++ d epend de la taille de T.

Il existe de m eme deux op erateurs unaires -- donnant lieu ` a des expressions exp -- et --exp. Lexplication est la m eme, en rempla cant +1 par 1. Application : r ealisation dune pile. Il est tr` es agr eable de constater que les op erateurs ++ et -- et la mani` ere dindexer les tableaux en C se combinent harmonieusement et permettent par exemple la r ealisation simple et ecace de piles par des tableaux, selon le sch ema suivant : d eclaration et initialisation dune pile (OBJET est un type pr ed eni, d ependant du probl` eme particulier consid er e ; MAXPILE est une constante repr esentant un majorant du nombre d el ements dans la pile) : OBJET espace[MAXPILE]; int nombreElements = 0; op eration

empiler la valeur de x :

if (nombreElements >= MAXPILE) erreur("tentative dempilement dans une pile pleine"); espace[nombreElements++] = x; eration op

d epiler une valeur et la ranger dans x :

if (nombreElements <= 0) erreur("tentative de depilement dune pile vide"); x = espace[--nombreElements]; On notera que, si on proc` ede comme indiqu e ci-dessus, la variable nombreElements poss` ede constamment la valeur que son nom sugg` ere : le nombre d el ements eectivement pr esents dans la pile.

2.2.8

Moins unaire -

Op eration : changement de signe. Format : -exp exp doit etre une expression num erique (enti` ere ou r eelle). Cette expression repr esente lobjet de m eme type que exp dont la valeur est loppos ee de celle de exp. Ce nest pas une lvalue.

2.2.9

Indirection * d er eference . Format : *exp


Op eration : acc` es ` a un objet point e. On dit aussi exp doit etre une expression de type ayant pour adresse la valeur de exp. Lexpression *exp est une lvalue.

adresse dun objet de type T

. *exp repr esente alors lobjet de type T

Remarque 1. On prendra garde au fait quil existe un bon nombre dop erateurs ayant une priorit e sup erieure ` a celle de *, ce qui oblige souvent ` a utiliser des parenth` eses. Ainsi par exemple les expressions Pascal e[i] et e[i] doivent respectivement s ecrire, en C, (*e)[i] et *(e[i]), la deuxi` eme pouvant aussi s ecrire *e[i]. Remarque 2. Lexpression *p signie acc` es ` a la m emoire dont p contient ladresse. Cest une op eration sans let . Quel que soit le contenu de p, la machine pourra toujours le consid erer comme une adresse et acc eder ` a la m emoire correspondante. Il appartient au programmeur de prouver que cete m emoire a et e eectivement allou ee au programme en question. Si cest le pas on dit que la valeur de p est une adresse valide ; dans le cas contraire, les pires erreurs sont ` a craindre ` a plus ou moins court terme.

c H. Garreta, 1988-2013

23

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

2.2.10

Obtention de ladresse &

Op eration : obtention de ladresse dun objet occupant un emplacement de la m emoire. Format : &exp exp doit etre une expression dun type quelconque T. Ce doit etre une lvalue. Lexpression &exp a pour type adresse dun objet de type T par exp. Lexpression &exp nest pas une lvalue.

et pour valeur ladresse de lobjet repr esent e

Ainsi, si i est une variable de type int et p une variable de type de linstruction p = &i; i et *p d esignent le m eme objet.

pointeur vers un int , alors ` a la suite

Exemple 1. Une utilisation fr equente de cet op erateur est lobtention de ladresse dune variable en vue de la passer ` a une fonction pour quelle modie la variable : scanf("%d%lf%s", &i, &t[j], &p->nom); Exemple 2. Une autre utilisation el egante de cet op erateur est la cr eation de composantes xes dans les structures cha n ees. Le programme suivant d eclare une liste cha n ee circulaire repr esent ee par le pointeur entree et r eduite pour commencer ` a un unique maillon qui est son propre successeur (voir gure 3) ; dautres maillons seront cr e es dynamiquement durant lex ecution :

entree

en_tete_fixe
Figure 3 Maillon xe en t ete dune liste cha n ee

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 r eduite ` a un nom de tableau, ` a un nom de fonction ou ` a une constante cha ne de caract` eres est consid er ee comme une constante de type adresse ; lop erateur & appliqu e` a de telles expressions est donc sans objet. Un tel emploi de & devrait etre consid er e comme erron e, mais beaucoup de compilateurs se contentent de lignorer. 2.2.11 Op erateur sizeof

Op eration : calcul de la taille correspondant ` a un type. Premi` ere forme : sizeof ( descripteur-de-type ) Cette expression repr esente un nombre entier qui exprime la taille quoccuperait en m emoire un objet poss edant le type indiqu e (les descripteurs de types sont expliqu es ` a la section 5.4). Deuxi` eme forme : sizeof exp exp est une expression quelconque (qui nest pas evalu ee par cette evaluation de sizeof). Lexpression sizeof exp repr esente la taille quoccuperait en m emoire un objet poss edant le m eme type que exp. Dans un cas comme dans lautre sizeof... est une expression constante. En particulier, ce nest pas une lvalue. La taille des objets est exprim ee en nombre doctets. Plus exactement, lunit e choisie est telle que la valeur de sizeof(char) soit 1 (on peut aussi voir cela comme une d enition du type char). 24
c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

Remarque 1. Dans le C original, le type de lexpression sizeof... est int. Dans le C ANSI, ce type peut changer dun syst` eme ` a un autre (int, long, unsigned, etc.). Pour cette raison il est d eni, sous lappellation size t, dans le chier stddef.h. Il est recommand e de d eclarer de type size t toute variable (resp. toute fonction) devant contenir (resp. devant renvoyer) des nombres qui repr esentent des tailles. Remarque 2. Lorsque son op erande est un tableau (ou une expression de type tableau) la valeur rendue par sizeof est lencombrement eectif du tableau. Par exemple, avec la d eclaration char tampon[80]; lexpression sizeof tampon vaut 80, m eme si cela para t en contradiction avec le fait que tampon peut etre vu comme de type adresse dun char . Il en d ecoule une propri et e bien utile : quel que soit le type du tableau t, la formule sizeof t / sizeof t[0] exprime toujours le nombre d el ements de t. 2.2.12 Conversion de type (cast operator)

Op eration : conversion du type dune expression. Format : ( type 2 ) exp type 2 repr esente un descripteur de type ; exp est une expression quelconque. Lexpression ci-dessus d esigne un el ement de type type 2 qui est le r esultat de la conversion vers ce type de l el ement repr esent e par exp. Lexpression (type 2 )exp nest pas une lvalue. Les conversions l egitimes (et utiles) sont 14 : Entier vers un entier plus long (ex. char int). Le codage de exp est etendu de telle mani` ere que la valeur repr esent ee soit inchang ee. Entier vers un entier plus court (ex. long short). Si la valeur de exp est assez petite pour etre repr esentable dans le type de destination, sa valeur est la m eme apr` es conversion. Sinon, la valeur de exp est purement et simplement tronqu ee (une telle troncation, sans signication abstraite, est rarement utile). Entier sign e vers entier non sign e, ou le contraire. Cest une conversion sans travail : le compilateur se borne ` a interpr eter autrement la valeur de exp, sans eectuer aucune transformation de son codage interne. Flottant vers entier : la partie fractionnaire de la valeur de exp est supprim ee. Par exemple, le ottant 3.14 devient lentier 3. Attention, on peut voir cette conversion comme une r ealisation de la fonction math ematique partie enti` ere, mais uniquement pour les nombres positifs : la conversion en entier de -3.14 donne -3, non -4. Entier vers ottant. Sauf cas de d ebordement (le r esultat est alors impr evisible), le ottant obtenu est celui qui approche le mieux lentier initial. Par exemple, lentier 123 devient le ottat 123.0. Adresse dun objet de type T1 vers adresse dun objet de type T2 . Cest une conversion sans travail : le compilateur donne une autre interpr etation de la valeur de exp, sans eectuer aucune transformation de son codage interne. Danger ! Une telle conversion est enti` erement plac ee sous la responsabilit e du programmeur, le compilateur laccepte toujours. Entier vers adresse dun objet de type T. Cest encore une conversion sans travail : la valeur de exp est interpr et ee comme un pointeur, sans transformation de son codage interne. Danger ! Une telle conversion est enti` erement plac ee sous la responsabilit e du programmeur. De plus, si la repr esentation interne de exp na pas la taille voulue pour un pointeur, le r esultat est impr evisible. Toutes les conversions o` u lun des types en pr esence, ou les deux, sont des types struct ou union sont interdites. Note 1. Si type 2 et le type de exp sont num eriques, alors la conversion eectu ee ` a loccasion de l evaluation de lexpression ( type 2 ) exp est la m eme que celle qui est faite lors dune aectation x = expr ; o` u x repr esente une variable de type type 2 . Note 2. En toute rigueur, le fait que lexpression donn ee par un op erateur de conversion de type ne soit pas une lvalue interdit des expressions qui auraient pourtant et e pratiques, comme ((int) x)++; /* DANGER ! */

14. Attention, il y a quelque chose de trompeur dans la phrase conversion de exp . Noubliez pas que, contrairement ` a ce que sugg` ere une certaine mani` ere de parler, l evaluation de lexpression (type )exp ne change ni le type ni la valeur de exp.

c H. Garreta, 1988-2013

25

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

Cependant, certains compilateurs acceptent cette expression, la traitant comme x = (le type de x)((int) x + 1); Exemple 1. Si i et j sont deux variables enti` eres, lexpression i / j repr esente leur division enti` ere (ou euclidienne, ou encore leur quotient par d efaut). Voici deux mani` eres de ranger dans x (une variable de type float) leur quotient d ecimal : x = (float) i / j; x = i / (float) j; Et voici deux mani` eres de se tromper (en nobtenant que le r esultat de la conversion vers le type float de leur division enti` ere) x = i / j; /* ERREUR */ x = (float)(i / j); /* ERREUR */

Exemple 2. Une utilisation pointue et dangereuse, mais parfois n ecessaire, de lop erateur de conversion de type entre types pointeurs consiste ` a sen servir pour voir un espace m emoire donn e par son adresse comme poss edant une certaine structure alors quen fait il na pas et e ainsi d eclar e: d eclaration dune structure : struct en_tete { long taille; struct en_tete *suivant; }; d eclaration dun pointeur void *ptr; imaginez que pour des raisons non d etaill ees ici ptr poss` ede ` a un endroit donn e une valeur quil est l egitime de consid erer comme ladresse dun objet de type struct en tete (alors que ptr na pas ce type-l` a). Voici un exemple de manipulation cet objet : ((struct en_tete *) ptr)->taille = n; Bien entendu, une telle conversion de type est faite sous la responsabilit e du programmeur, seul capable de garantir qu` a tel moment de lex ecution du programme ptr pointe bien un objet de type struct en tete. crite ne requiert jamais un cast . Lop N.B. Lappel dune fonction bien e erateur de changement de type est parfois n ecessaire, mais son utilisation diminue toujours la qualit e du programme qui lemploie, pour une raison facile ` a comprendre : cet op erateur fait taire le compilateur. En eet, si expr est dun type pointeur et type 2 est un autre type pointeur, lexpression (type 2 )expr est toujours accept ee par le compilateur sans le moindre avertissement. Cest donc une mani` ere de cacher des erreurs sans les r esoudre. Exemple typique : si on a oubli e de mettre en t ete du programme la directive #include <stdlib.h>, lutilisation de la fonction malloc de la biblioth` eque standard soul` eve des critiques : ... MACHIN *p; ... p = malloc(sizeof(MACHIN)); ... A la compilation on a des avertissements. Par exemple (avec gcc) : monProgramme.c: In function main monProgramme.c:9:warning: assignment makes pointer from integer without a cast On croit r esoudre le probl` eme en utilisant lop erateur de changement de type ... p = (MACHIN *) malloc(sizeof(MACHIN)); /* CECI NE REGLE RIEN! */ ... et il est vrai que la compilation a lieu maintenant en silence, mais le probl` eme nest pas r esolu, il est seulement cach e. Sur un ordinateur o` u les entiers et les pointeurs ont la m eme taille cela peut marcher, mais ailleurs la valeur rendue par malloc sera endommag ee lors de son aectation ` a p. A ce sujet, voyez la remarque de la page 65. Il sut pourtant de bien lire lavertissement ach e par le compilateur pour trouver la solution. Laectation p = malloc(...) lui fait dire quon fabrique un pointeur (p) ` a partir dun entier (le r esultat de malloc) sans op erateur cast. Ce qui est anormal nest pas labsence de cast, mais le fait que le r esultat de malloc soit tenu pour un entier 15 , et il ny a aucun moyen de rendre cette aectation juste aussi longtemps que le compilateur
15. Rappelez-vous que toute fonction appel ee et non d eclar ee est suppos ee de type int.

g en erique :

26

c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

fera une telle hypoth` ese fausse. La solution est donc dinformer ce dernier ` a propos du type de malloc, en faisant pr ec eder laectation litigieuse soit dune d eclaration de cette fonction void *malloc (size_t); soit, cest mieux, dune directive ad hoc : #include <stdlib.h> ... p = malloc(sizeof(MACHIN)); ... 2.2.13 Op erateurs arithm etiques

Ce sont les op erations arithm etiques classiques : addition, soustraction, multiplication, division et modulo (reste de la division enti` ere). Format : + - * exp1 exp2 / % Aucune de ces expressions nest une lvalue. Avant l evaluation de lexpression, les op erandes subissent les conversions langage C supporte larithm etique des adresses (cf. section 6.2.1).

usuelles (cf. section 2.3.1). Le

A propos de la division. En C on note par le m eme signe la division enti` ere, qui prend deux op erandes entiers (short, int, long, etc.) et donne un r esultat entier, et la division ottante dans laquelle le r esultat est ottant (float, double). Dautre part, les r` egles qui commandent les types des op erandes et du r esultat des expressions arithm etiques (cf. section 2.3.1), et notamment la r` egle dite du plus fort , ont la cons equence importante suivante : dans lexpression expr 1 / expr 2 si expr 1 et expr 2 sont toutes deux enti` eres alors / est traduit par lop eration division enti` ere 16 , et le r esultat est entier, si au moins une des expressions expr 1 ou expr 2 nest pas enti` ere, alors lop eration faite est la division ottante des valeurs de expr 1 et expr 2 toutes deux converties dans le type double. Le r esultat est une 1 valeur double qui approche le rationnel expr ecision du type double. expr2 du mieux que le permet la la pr Il r esulte de cette r` egle un pi` ege auquel il faut faire attention : 1/2 ne vaut pas 0.5 mais 0. De m eme, dans int somme, nombre; float moyenne; ... moyenne = somme / 100; la valeur de moyenne nest pas ce que ce nom sugg` ere, car somme et 100 sont tous deux entiers et lexpression somme / 100 est enti` ere, donc tronqu ee (et il ne faut pas croire que laectation ult erieure ` a la variable ottante moyenne pourra retrouver les d ecimales perdues). Dans ce cas particulier, la solution est simple : moyenne = somme / 100.0; M eme probl` eme si le d enominateur avait et e une variable enti` ere, comme dans moyenne = somme / nombre; ici la solution est un peu plus compliqu ee : moyenne = somme / (double) nombre;
16. On prendra garde au fait que si les op erandes ne sont pas tous deux positifs la division enti` ere du langage C ne coincide pas avec le quotient par d efaut (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 3 alors que le quotient par d efaut de 17 par 5 est plut ot 4 (17 = 5 (4) + 3).

c H. Garreta, 1988-2013

27

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

2.2.14

D ecalages << >>

Op eration : d ecalages de bits. Format : { exp1 << >> } exp2

exp 1 et exp 2 doivent etre dun type entier (char, short, long, int...). Lexpression exp 1 << exp 2 (resp. exp 1 >> exp 2 ) repr esente lobjet de m eme type que exp 1 dont la repr esentation interne est obtenue en d ecalant les bits de la repr esentation interne de exp 1 vers la gauche 17 (resp. vers la droite) dun nombre de positions egal ` a la valeur de exp 2 . Autrement dit, exp 1 << exp 2 est la m eme chose (si << 1 appara t exp 2 fois) que ((exp 1 << 1) << 1) ... << 1 Remarque analogue pour le d ecalage ` a droite >>. Les bits sortants sont perdus. Les bits entrants sont : dans le cas du d ecalage ` a gauche, des z eros ; dans le cas du d ecalage ` a droite : si exp 1 est dun type non sign e, des z eros ; si exp 1 est dun type sign e, des copies du bit de signe 18 . Par exemple, si on suppose que la valeur de exp 1 est cod ee sur huit bits, not es b 7b 6b5b4b3b2b1b0 (chaque bi vaut 0 ou 1), alors exp 1 << 1 exp 1 >> 1 exp 1 >> 1 b6b5b4b3b2b1b00 0b 7 b 6 b 5 b 4 b 3 b 2 b 1 b7b7b6b5b4b3b2b1

(cas non sign e) (cas sign e)

Avant l evaluation du r esultat, les op erandes subissent les conversions usuelles (cf. section 2.3.1). Ces expressions ne sont pas des lvalue. Remarque. Les op erateurs de d ecalage de bits ne sont pas des op erations abstraites (cf. section 2.3.3). La portabilit e dun programme o` u ils gurent nest donc pas assur ee. 2.2.15 Comparaisons == != < <= > >=

Il sagit des comparaisons usuelles : egal, di erent, inf erieur, inf erieur ou egal, sup erieur, sup erieur ou egal. Format : == != < exp1 exp2 <= > >= exp 1 et exp 2 doivent etre dun type simple (nombre ou pointeur). Cette expression repr esente lun des el ements (de type int) 1 ou 0 selon que la relation indiqu ee est ou non v eri ee par les valeurs de exp 1 et exp 2 . Avant la prise en compte de lop erateur, les op erandes 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 op erateurs ont une priorit e sup erieure ` a celle des connecteurs logiques, ce qui evite beaucoup de parenth` eses disgracieuses. Ainsi, en C, lexpression 0 <= x && x < 10 est correctement ecrite. A propos de

tre vrai e

et

tre non nul . Comme on a dit (cf. section 1.4.1), le type bool e een

17. Par convention la droite dun nombre cod e en binaire est le c ot e des bits de poids faible (les unit es) tandis que la gauche est celui des bits de poids forts (correspondant aux puissances 2k avec k grand). 18. De cette mani` ere le d ecalage ` a gauche correspond (sauf d ebordement) ` a la multiplication par 2exp2 , tandis que le d ecalage ` a ee que si elle est non sign ee droite correspond ` a la division enti` ere par 2exp2 , aussi bien si exp 1 est sign

28

c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

nexiste pas en C. Nimporte quelle expression peut occuper la place dune condition ; elle sera tenue pour fausse si elle est nulle, pour vraie dans tous les autres cas. Une cons equence 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 respectivement if (i) etc. while (*ptchar) etc. for (p = liste; p; p = p->suiv) etc. On admet g en eralement qu` a cause de propri et es techniques des processeurs, ces expressions raccourcies repr esentent une certaine optimisation des programmes. Cest pourquoi les premiers programmeurs C, qui ne disposaient que de compilateurs simples, ont pris lhabitude de les utiliser largement. On ne peut plus aujourdhui conseiller cette pratique. En eet, les compilateurs actuels sont susamment perfectionn es pour eectuer spontan ement cette sorte doptimisations et il ny a plus aucune justication de lemploi de ces comparaisons implicites qui rendent les programmes bien moins expressifs. Attention. La relation d egalit e se note ==, non =. Voici une faute possible chez les nouveaux venus ` aC en 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 davoir leet escompt e. En tant quexpression, a = 0 vaut 0 (avec, comme eet de bord, la mise ` a z ero de a). Bien s ur, il fallait ecrire if (a == 0) etc.

2.2.16

Op erateurs de bits & | ^ et ou exclusif sur les bits des repr esentations internes exp2

Ces op erateurs d esignent les op erations logiques et, ou des valeurs des op erandes. Format : & | exp1 ^

exp 1 et exp 2 doivent etre dun type entier. Cette expression repr esente un objet de type entier dont le codage interne est construit bit par bit ` a partir des bits correspondants des valeurs de exp 1 et exp 2 , selon la table suivante, dans laquelle (exp )i signie le ieme bit de exp : (exp 1 )i 0 0 1 1 (exp 2 )i 0 1 0 1 (exp 1 & exp 2 )i 0 0 0 1 (exp 1 ^ exp 2 )i 0 1 1 0 (exp 1 | exp 2 )i 0 1 1 1

Avant l evaluation du r esultat, les op erandes subissent les conversions usuelles (cf. section 2.3.1). Ces expressions ne sont pas des lvalue. Exemple. Il ny a pas beaucoup dexemples de haut niveau 19 dutilisation de ces op erateurs. Le plus utile est sans doute la r ealisation densembles de nombres naturels inf erieurs ` a une certaine constante pas trop grande, cest-` a-dire des sous-ensembles de lintervalle dentiers [ 0 ... N 1 ], N valant 8, 16 ou 32. Par exemple, les biblioth` eques graphiques comportent souvent une fonction qui eectue lachage dun texte aect e dun ensemble dattributs (gras, italique, soulign e, capitalis e, etc.). Cela ressemble ` a ceci : void afficher(char *texte, unsigned long attributs); Pour faire en sorte que largument attributs puisse repr esenter un ensemble dattributs quelconque, on associe les attributs ` a des entiers conventionnels :
19. En eet, ces op erateurs sont principalement destin es ` a l ecriture de logiciel de bas niveau, comme les pilotes de p eriph erique (les c el` ebres drivers ), cest-` a-dire des programmes qui re coivent les informations transmises par les composants mat eriels ou qui commandent le fonctionnement de ces derniers. Dans de telles applications on est souvent aux pries avec des nombres dont chaque bit a une signication propre, ind ependante des autres. Ces programmes sortent du cadre de ce cours.

c H. Garreta, 1988-2013

29

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

#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 utilis ees pour repr esenter les attributs sont des puissances de 2 distinctes, cest-` a-dire des nombres qui, ecrits en binaire, comportent un seul 1 plac e di eremment de lun ` a lautre. Lutilisateur dune telle fonction emploie lop erateur | pour composer, lors de lappel, lensemble dattributs quil souhaite : afficher("Bonjour", GRAS | SOULIGNE); /* affichage en gras et italique */ (avec les constantes de notre exemple, la valeur de lexpression GRAS | SOULIGNE est un nombre qui, en binaire, s ecrit 00...000101). De son c ot e, le concepteur de la fonction utilise lop erateur & pour savoir si un attribut appartient ou non ` a lensemble donn e: ... if ((attributs & GRAS) != 0) prendre les dispositions n ecessaires pour acher en gras else if ((attributs & ITALIQUE) != 0) prendre les dispositions n ecessaires pour acher en italique ...

2.2.17

Connecteurs logiques && et ||

Op erations : conjonction et disjonction. Format : { } && exp1 exp2 || exp 1 et exp 2 sont deux expressions de nimporte quel type. Cette expression repr esente un el ement de type int parmi { 0, 1 } d eni de la mani` ere suivante : Pour evaluer exp 1 && exp 2 : exp 1 est evalu ee dabord et si la valeur de exp 1 est nulle, exp 2 nest pas evalu ee et exp 1 && exp 2 vaut 0 ; evalu ee et exp 1 && exp 2 vaut 0 ou 1 selon que la valeur de exp 2 est nulle ou non. sinon exp 2 est Pour evaluer exp 1 || exp 2 : exp 1 est evalu ee dabord et si la valeur de exp 1 est non nulle, exp 2 nest pas evalu ee et exp 1 || exp 2 vaut 1 ; sinon exp 2 est evalu ee et exp 1 || exp 2 vaut 0 ou 1 selon que la valeur de exp 2 est nulle ou non. Ces expressions ne sont pas des lvalue. Applications. Ainsi, C garantit que le premier op erande sera evalu e dabord et que, sil sut ` a d eterminer le r esultat de la conjonction ou de la disjonction, alors le second op erande ne sera m eme pas evalu e. Pour le programmeur, cela est une bonne nouvelle. En eet, il nest pas rare quon ecrive des conjonctions dont le premier op erande prot` ege (dans lesprit du programmeur) le second ; si cette protection ne fait pas partie de la s emantique de lop erateur pour le langage utilis e, le programme r esultant peut etre faux. Consid erons lexemple suivant : un certain tableau table est form e de nombre cha nes de caract` eres ; soit ch une variable cha ne. Lop eration 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 nest evalu ee quapr` es avoir v eri e que la condition i < nombre est vraie (un appel de strcmp(table[i], ch) avec un premier argument table[i] invalide peut avoir des cons equences tout ` a fait d esastreuses). Une autre cons equence de cette mani` ere de concevoir && et || est stylistique. En C la conjonction et la disjonction s evaluent dans lesprit des instructions plus que dans celui des op erations. Lapplication pratique de cela est la possibilit e d ecrire sous une forme fonctionnelle des algorithmes qui dans dautres langages seraient 30
c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

s equentiels. Voici un exemple : le pr edicat qui caract erise la pr esence dun el ement dans une liste cha n ee. Version (r ecursive) habituelle : int present(INFO x, LISTE L) { /* linformation 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 s emantique des op erateurs && et || : int existe(INFO x, LISTE L) { /* linformation x est-elle dans la liste L ? */ return L != NULL && (x == L->info || existe(x, L->suivant)); } 2.2.18 Expression conditionnelle ? :

Op eration : sorte de if...then...else... pr esent e sous forme dexpression, cest-` a-dire renvoyant une valeur. Format : exp 0 ? exp 1 : exp 2 exp 0 est dun type quelconque. exp 1 et exp 2 doivent etre de types compatibles. Cette expression est evalu ee de la mani` ere suivante : La condition exp 0 est evalu ee dabord si sa valeur est non nulle, exp 1 est evalu ee et d enit la valeur de lexpression conditionnelle. Dans ce cas, exp 2 nest pas evalu ee ; sinon, exp 2 est evalu ee et d enit la valeur de lexpression conditionnelle. Dans ce cas, exp 1 nest pas evalu ee. Lexpression exp 0 ?exp 1 :exp 2 nest pas une lvalue. Exemple. Lop erateur conditionnel nest pas forc ement plus facile ` a lire que linstruction conditionnelle, mais permet quelquefois de r eels all egements du code. Imaginons un programme devant acher un des textes non ou oui selon que la valeur dune variable reponse est nulle ou non. Solutions classiques de ce micro-probl` eme : if (reponse) printf("la r eponse est oui"); else printf("la r eponse est non"); ou bien, avec une variable auxiliaire : char *texte; ... if (reponse) texte = "oui"; else texte = "non"; printf("la r eponse est %s", texte); Avec lop erateur conditionnel cest bien plus compact : printf("la r eponse est %s", reponse ? "oui" : "non"); 2.2.19 Aectation =

Op eration : aectation, consid er ee comme une expression. Format : exp 1 = exp 2 exp 1 doit etre une lvalue. Soit type 1 le type de exp 1 ; laectation ci-dessus repr esente le m eme objet que ( type 1 ) exp 2
c H. Garreta, 1988-2013

31

2.2

Pr esentation d etaill ee des op erateurs

OPERATEURS ET EXPRESSIONS

(la valeur de exp 2 convertie dans le type de exp 1 ), avec pour eet de bord le rangement de cette valeur dans lemplacement de la m emoire d etermin e par exp 1 . Lexpression exp 1 = exp 2 nest pas une lvalue. Contrairement ` a ce qui se passe dans dautres langages, une aectation est donc consid er ee en C comme une expression : elle fait quelque chose, mais aussi elle vaut une certaine valeur et, ` a ce titre, elle peut gurer comme op erande dans une sur-expression. On en d eduit la possibilit e des aectations multiples, comme dans lexpression : a = b = c = 0; comprise comme a = (b = (c = 0)). Elle aura donc le m eme eet que les trois aectations a = 0; b = 0; c = 0; Autre exemple, lecture et traitement dune suite de caract` eres dont la n est indiqu ee par un point : ... while ((c = getchar()) != .) exploitation de c ... Des contraintes p` esent sur les types des deux op erandes dune aectation exp 1 = exp 2 . Lorsquelles sont satisfaites on dit que exp 1 et exp 2 sont compatibles pour laectation. Essentiellement : deux types num eriques sont toujours compatibles pour laectation. La valeur de exp 2 subit eventuellement une conversion avant d etre rang ee dans exp 1 . La mani` ere de faire cette conversion est la m eme que dans le cas de lop erateur de conversion (cf. section 2.2.12) ; a la norme si exp 1 et exp 2 sont de types adresses distincts, certains compilateurs (dont ceux conformes ` ANSI) les consid ereront comme incompatibles tandis que dautres compilateurs se limiteront ` a donner un message davertissement lors de laectation de exp 2 ` a exp 1 ; dans les autres cas, exp 1 et exp 2 sont compatibles pour laectation si et seulement si elles sont de m eme type. Dautre part, de la signication du nom dune variable de type tableau (cf. 5.1.1) et de celle dune variable de type structure (cf. 5.2.1) on d eduit que : on ne peut aecter un tableau ` a un autre, m eme sils sont d enis de mani` ere rigoureusement identique (un nom de tableau nest pas une lvalue ) ; on peut aecter le contenu dune variable de type structure ou union ` a une autre, ` a la condition quelles aient et e explicitement d eclar ees comme ayant exactement le m eme type.

2.2.20

Autres op erateurs daectation += *= etc.

Op eration binaire vue comme une modication du premier op erande. Format : exp1 += -= *= /= %= >>= <<= &= ^= |= exp2

exp 1 doit etre une lvalue. Cela fonctionne de la mani` ere suivante : si repr esente lun des op erateurs + - * / % >> << & ^ |, alors exp 1 = exp 2 peut etre vue comme ayant la m eme valeur et le m eme eet que exp 1 = exp 1 exp 2 mais avec une seule evaluation de exp 1 . Lexpression r esultante nest pas une lvalue. Exemple. Ecrivons la version it erative usuelle de la fonction qui calcule xn avec x ottant et n entier non n egatif : 32
c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.2

Pr esentation d etaill ee des op erateurs

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 m emes commentaires qu` a loccasion de plusieurs autres el ements du langage C : lemploi de ces op erateurs constitue une certaine optimisation du programme. En langage machine, la suite dinstructions 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 voir l equivalence avec la premi` ere forme. h elas, lemploi de ces op erateurs rend les programmes plus denses et moins faciles ` a lire, ce qui favorise lapparition derreurs de programmation. de nos jours les compilateurs de C sont devenus assez perfectionn es pour d eceler automatiquement la possibilit e de telles optimisations. Par cons equent, largument de lecacit e ne justie plus quon obscurcisse un programme par lemploi de tels op erateurs. Il faut savoir cependant quil existe des situations o` u lemploi de ces op erateurs nest pas quune question decacit e. En eet, si exp 1 est une expression sans eet de bord, alors les expressions exp 1 = exp 2 et exp 1 = exp 1 exp 2 sont r eellement equivalentes. Mais ce nest plus le cas si exp 1 a un eet de bord. Il est clair, par exemple, que les deux expressions suivantes ne sont pas equivalentes (la premi` ere est tout simplement erron ee) 20 : nombre[rand() % 100] = nombre[rand() % 100] + 1; nombre[rand() % 100] += 1; /* ERRONE ! */ /* CORRECT */

2.2.21

Lop erateur virgule ,

Op eration : evaluation en s equence. Format : exp 1 , exp 2 exp 1 et exp 2 sont quelconques. L evaluation de exp 1 , exp 2 consiste en l evaluation de exp 1 suivie de l evaluation de exp 2 . Lexpression exp 1 , exp 2 poss` ede le type et la valeur de exp 2 ; le r esultat de l evaluation de exp 1 est oubli e , mais non son eventuel eet de bord (cet op erateur nest utile que si exp 1 a un eet de bord). Lexpression exp 1 , exp 2 nest pas une lvalue. Exemple. Un cas fr equent dutilisation de cet op erateur concerne la boucle for (cf. section 3.2.6), dont la syntaxe requiert exactement trois expressions ` a trois endroits bien pr ecis. Parfois, certaines de ces expressions doivent etre doubl ees : ... for (pr = NULL, p = liste; p != NULL; pr = p, p = p->suivant) if (p->valeur == x) break; ... Remarque syntaxique. Dans des contextes o` u des virgules apparaissent normalement, par exemple lors dun appel de fonction, des parenth` eses sont requises an de forcer le compilateur ` a reconna tre lop erateur virgule. Par exemple, lexpression uneFonction(exp 1 ,(exp 2 , exp 3 ),exp 4 ); repr esente un appel de uneFonction avec trois arguments eectifs : les valeurs de exp 1 , exp 3 et exp 4 . Au passage, lexpression exp 2 aura et e evalu ee. Il est certain que exp 2 aura et e evalu ee avant exp 3 , mais les sp ecications du langage ne permettent pas de placer les evaluations de exp 1 et exp 4 par rapport ` a celles de exp 2 et exp 3 .
20. La fonction rand() renvoie un entier al eatoire distinct chaque fois quelle est appel ee.

c H. Garreta, 1988-2013

33

2.3

Autres remarques

OPERATEURS ET EXPRESSIONS

2.3
2.3.1

Autres remarques
Les conversions usuelles

Les r` egles suivantes sappliquent aux expressions construites ` a laide dun des op erateurs *, /, %, +, -, <, <=, >, >=, ==, !=, &, ^, |, && et ||. Mutatis mutandis, elles sappliquent aussi ` a celles construites avec les op erateurs *=, /=, %=, +=, -=, <<=, >>=, &=, ^= et |=. Dans ces expressions, les op erandes subissent certaines conversions avant que lexpression ne soit evalu ee. En C ANSI ces conversions, dans l ordre logique o` u elles sont faites, sont les suivantes : Si un des op erandes est de type long double, convertir lautre dans le type long double ; le type de lexpression sera long double. Sinon, si un des op erandes est de type double, convertir lautre dans le type double ; le type de lexpression sera double. erandes est de type float, convertir lautre dans le type float ; le type de lexpression Sinon, si un des op sera float 21 . Eectuer la promotion enti` ere : convertir les char, les short, les enum erations et les champs de bits en des int. Si lune des valeurs ne peut pas etre repr esent ee dans le type int, les convertir toutes dans le type unsigned int. Ensuite, si un des op erandes est unsigned long, convertir lautre en unsigned long ; le type de lexpression sera unsigned long. Sinon, si un des op erandes est long et lautre unsigned int 22 : si un long peut repr esenter toutes les valeurs unsigned int, alors convertir lop erande de type unsigned int en long. Le type de lexpression sera long ; sinon, convertir les deux op erandes en unsigned long. Le type de lexpression sera unsigned long. Sinon, si un des op erandes est de type long, convertir lautre en long ; le type de lexpression sera long. Sinon, si un des op erandes est de type unsigned int, convertir lautre en unsigned int ; le type de lexpression sera unsigned int. Sinon, et si lexpression est correcte, cest que les deux op erandes sont de type int ; le type de lexpression sera int. 2.3.2 Lordre d evaluation des expressions

Les seules expressions pour lesquelles lordre (chronologique) d evaluation des op erandes est sp eci e sont les suivantes : exp 1 && exp 2 et exp 1 || exp 2 : exp 1 est evalu ee dabord ; exp 2 nest evalu ee que si la valeur de exp 1 ne permet pas de conclure ; exp 0 ? exp 1 : exp 2 exp 0 est evalu ee dabord. Une seule des expressions exp 1 ou exp 2 est evalu ee ensuite ; exp 0 , exp 0 : exp 1 est evalu ee dabord, exp 2 est evalu ee ensuite. Dans tous les autres cas, C ne garantit pas lordre chronologique dans lequel les op erandes intervenant dans une expression ou les arguments eectifs dun appel de fonction sont evalu es ; il ne faut donc pas faire dhypoth` ese ` a ce sujet. La question ne se pose que lorsque ces op erandes et arguments sont ` a leur tour des expressions complexes ; elle est importante dans la mesure o` u C favorise la programmation avec des eets de bord. Par exemple, si i vaut 1, lexpression a[i] + b[i++] peut aussi bien additionner a[1] et b[1] que a[2] et b[1]. Lordre d evaluation des op erandes dune aectation nest pas x e non plus. Pour evaluer exp 1 = exp 2 , on peut evaluer dabord ladresse de exp 1 et ensuite la valeur de exp 2 , ou bien faire linverse. Ainsi, le r esultat de laectation a[i] = b[i++] est impr evisible. 2.3.3 Les op erations non abstraites

Beaucoup dop erateurs etudi es dans cette section (op erateurs arithm etiques, comparaisons, logiques, etc.) repr esentent des op erations abstraites, cest-` a-dire poss edant une d enition formelle qui ne fait pas intervenir les particularit es de limplantation du langage. Bien s ur, les op erandes sont repr esent es dans la machine par des
21. Cette r` egle est apparue avec le C ANSI : le compilateur accepte de faire des calculs sur des ottants en simple pr ecision. Dans le C original, elle s enonce plus simplement : sinon, si lun des op erandes est de type float, convertir les deux op erandes dans le type double ; le type de lexpression sera double . 22. Cette r` egle compliqu ee est apparue avec le C ANSI. En C original, le type unsigned tire vers lui les autres types.

34

c H. Garreta, 1988-2013

OPERATEURS ET EXPRESSIONS

2.3

Autres remarques

congurations de bits, mais seule leur interpr etation comme des entit es de niveau sup erieur (nombres entiers, ottants...) est utile pour d enir leet de lop eration en question ou les contraintes quelle subit. A loppos e, un petit nombre dop erateurs, le compl ement ` a un (~), les d ecalages (<< et >>) et les op erations bit-` a-bit (&, ^ et |), nont pas forc ement de signication abstraite. Les transformations quils eectuent sont d enies au niveau des bits constituant le codage des op erandes, non au niveau des nombres que ces op erandes repr esentent. De telles op erations sont r eserv ees aux programmes qui remplissent des fonctions de tr` es bas niveau, cest-` a-dire qui sont aux points de contact entre la composante logicielle et la composante mat erielle dun syst` eme informatique. Laspect de cette question qui nous int eresse le plus ici est celui-ci : la portabilit e des programmes contenant des op erations non abstraites nest pas assur ee. Ce d efaut, qui nest pas r edhibitoire dans l ecriture de fonctions de bas niveau (ces fonctions ne sont pas destin ees ` a etre port ees), doit rendre le programmeur tr` es pr ecautionneux d` es quil sagit dutiliser ces op erateurs dans des programmes de niveau sup erieur, et le pousser ` a: isoler les op erations non abstraites dans des fonctions de petite taille bien rep er ees ; documenter soigneusement ces fonctions ; constituer des jeux de tests validant chacune de ces fonctions.

c H. Garreta, 1988-2013

35

3 INSTRUCTIONS

3
3.1

Instructions
Syntaxe

Dans les descriptions syntaxiques suivantes le suxe opt indique que la formule quil qualie est optionnelle. Une formule avec des points de suspension, comme el ement ... el ement , indique un el ement pouvant appara 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 identicateur : instruction instruction-bloc { d eclaration ... d eclaration 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 ( expression opt ; expression opt ; expression opt ) instruction instruction-break break; instruction-continue continue; instruction-switch switch ( expression ) { instruction-ou-case ... instruction-ou-case } instruction-ou-case case expression-constante : instruction opt default: instruction instruction instruction-return return expression opt ; instruction-vide ; C et le point-virgule. Comme lindique la syntaxe de linstruction-bloc, en C le point-virgule nest pas 36
c H. Garreta, 1988-2013

INSTRUCTIONS

3.2

Pr esentation d etaill ee des instructions

un s eparateur dinstructions mais un terminateur de certaines instructions. Autrement dit, il appartient ` a la syntaxe de chaque instruction de pr eciser si elle doit ou non etre termin ee par un point-virgule, ind ependamment de ce par quoi linstruction est suivie dans le programme. Loubli du point-virgule ` a la n dune instruction qui en requiert un est toujours une erreur, quelle que soit la situation de cette instruction. Un surnombre de points-virgules cr ee des instructions vides.

3.2
3.2.1

Pr esentation d etaill ee des instructions


Blocs

Un bloc est une suite de d eclarations et dinstructions encadr ee par les deux accolades { et }. Du point de vue de la syntaxe il se comporte comme une instruction unique et peut gurer en tout endroit o` u une instruction simple est permise. Le bloc le plus ext erieur dune fonction et les autres blocs plus profonds ont le m eme statut. En particulier, quelle que soit sa position dans le programme, un bloc peut comporter ses propres d eclarations de variables. Sauf si elle est d eclar ee extern, une variable d enie dans un bloc est locale ` a ce bloc et donc inconnue ` a lext erieur. Dans le bloc o` u elle est d enie, une telle variable masque, sans le d etruire, tout autre objet de m eme nom connu ` a lext erieur du bloc. Sauf si elles sont quali ees static (ou extern), de telles variables sont cr e ees et eventuellement initialis ees lors de lactivation du bloc ; elles sont d etruites d` es que le contr ole quitte le bloc. Il ne faut donc pas esp erer quune telle variable conserve sa valeur entre deux passages dans le bloc. Les variables locales aux blocs permettent doptimiser la gestion de lespace local. Par exemple, dans un programme tel que if (...) { type 1 n; ... } else { type 2 x; ... } les variables n et x nexisteront jamais simultan ement ; le compilateur peut donc leur allouer le m eme emplacement de la m emoire.

3.2.2

Instruction-expression

Format : expression ; Mais oui, il sut d ecrire un point-virgule derri` ere nimporte quelle expression pour en faire une instruction. Exemples : 123; i++; x = 2 * x + 3; printf("%d\n", n); a b c d

Intuitivement, une instruction-expression repr esente lordre evaluez cette expression, ensuite oubliez le r esultat . Il est clair que si lexpression na pas deet de bord, cela naura servi ` a rien (exemple a ). Laspect utile de cette notion est : toute expression avec eet de bord pourra etre evalu ee uniquement pour son eet de bord (exemple b ). Avec deux cas particuliers tr` es int eressants : puisque laectation est une expression, on retrouve bien linstruction daectation, fondamentale dans tous les langages de programmation (exemple c ) ; etre appel ee comme une proc edure (exemple 23 d ), cest-` a-dire en ignorant la toute fonction peut valeur quelle rend.
23. On verra le moment venu (cf. section 7.2.5 d ) que printf rend un r esultat parfois utile.

c H. Garreta, 1988-2013

37

3.2

Pr esentation d etaill ee des instructions

3 INSTRUCTIONS

Remarque. Une cons equence regrettable de tout cela est un pi` ege assez vicieux tendu aux pascaliens. Imaginons que lirecaractere soit le nom dune fonction sans argument. Lappel correct de cette fonction s ecrit : lirecaractere(); Cependant, puisque le nom dune fonction est une expression (une constante valant ladresse de la fonctio) et que toute expression suivie dun point-virgule est une instruction correcte, l enonc e lirecaractere; sera trouv e l egal par le compilateur. Or cette expression (tout ` a fait analogue ` a lexemple a ci-dessus) ne produit pas lappel de la fonction et ne traduit donc probablement pas la pens ee du programmeur. 3.2.3 Etiquettes et instruction goto

Format : etiquette : instruction Une etiquette est un identicateur ; elle doit etre plac ee devant une fonction, s epar ee de celle-ci par un caract` ere deux points. Elle na pas ` a faire lobjet dune d eclaration explicite ; il sut de l ecrire devant une instruction pour quelle soit automatiquement connue comme un nom ` a port ee locale. Elle est alors utilisable partout dans la fonction o` u elle appara t (avant et apr` es linstruction quelle pr exe) et elle reste inconnue en dehors de la fonction. Linstruction goto etiquette ; transf` ere le contr ole ` a linstruction pr ex ee par l etiquette en question. Th eoriquement, tout algorithme peut etre programm e sans utiliser linstruction goto. Dans certains langages comme Pascal, elle est utilis ee pour obtenir labandon dune structure de contr ole (exemple : une boucle) depuis lint erieur de la structure. Un tel emploi de goto est avantageusement remplac e en C par lutilisation des instructions return, break et continue. Il est donc rare que lon ait besoin de linstruction goto en C. Elle ne se r ev` ele utile que lorsquil faut abandonner plusieurs structures de contr ole (if, while, for...) imbriqu ees 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 quitt e 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 ) instruction 1 else instruction 2 et if (expression ) instruction 1 Dans la premi` ere forme, expression est evalu ee : si elle est vraie (i.e. non nulle) instruction 1 est ex ecut ee ; si elle est fausse (nulle) instruction 2 est ex ecut ee. Dans la deuxi` eme forme, expression est evalu ee : si elle est vraie instruction 1 est ex ecut ee ; sinon, rien nest ex ecut e. 38
c H. Garreta, 1988-2013

INSTRUCTIONS

3.2

Pr esentation d etaill ee des instructions

On notera que lexpression conditionnelle doit gurer entre parenth` eses. Celles-ci font partie de la syntaxe du if, non de celle de lexpression. Lorsque plusieurs instructions if sont imbriqu ees, il est convenu que chaque else se rapporte au dernier if pour lequel le compilateur a rencontr e une condition suivie dexactement une instruction. Le listing du programme peut (et doit !) traduire cela par une indentation (marge blanche) expressive, mais il ne faut pas oublier que le compilateur ne tient pas compte des marges. Par exemple, le programme suivant est probablement incorrect ; 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 ny a personne!"); Ce probl` eme se pose dans tous les langages qui orent deux vari et es dinstruction conditionnelle. On le r esout soit par lutilisation dinstructions vides : if (nombrePersonnes != 0) if (nombrePersonnes != nombreAdultes) printf("Il y a des enfants!"); else ; else printf("Il ny a personne!"); soit, plus simplement, en utilisant des blocs : if (nombrePersonnes != 0) { if (nombrePersonnes != nombreAdultes) printf("Il y a des enfants!"); } else printf("Il ny a personne!"); Remarque. La syntaxe pr evoit exactement une instruction entre la condition et le else. Par cons equent, un exc` es de points-virgules ` a la suite de instruction 1 constitue une erreur. Voici une faute quon peut faire quand on d ebute : if (nombrePersonnes != 0) { if (nombrePersonnes != nombreAdultes) printf("Il y a des enfants!"); }; else printf("Il ny a personne!"); Il y a maintenant deux instructions entre la ligne du if et celle du else : une instruction-bloc { ... } et une instruction 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... du langage Pascal. Notez que la syntaxe exige que la condition gure entre parenth` eses. Formats : while (expression ) instruction et do instruction while (expression ); Le fonctionnement de ces instructions est d ecrit par les organigrammes de la gure 4. Fondamentalement, il sagit de r eit erer lex ecution dune certaine instruction tant quune certaine instruction, vue comme une condition, reste vraie. Dans la structure while on v erie la condition avant dex ecuter linstruction, tandis que dans la structure do...while on la v erie apr` es.
c H. Garreta, 1988-2013

39

3.2

Pr esentation d etaill ee des instructions

3 INSTRUCTIONS

expression != 0
oui non oui

instruction

instruction

expression != 0
non

suite du programme

suite du programme

Figure 4 Instruction while (` a gauche) et do...while (` a droite)

Linstruction do...while... ex ecute donc au moins une fois linstruction qui constitue son corps avant d evaluer la condition de continuation. Cest en cela quelle est lanalogue de linstruction repeat...until du Pascal. Mais on notera que la condition gurant dans une instruction do...while ( faire tant que... ) et celle qui gurerait dans une instruction repeat...until equivalente ( r ep eter jusqu` a ce que... ) sont inverses lune de lautre. 3.2.6 Instruction for

Format : for ( expr 1 ; expr 2 ; expr 3 ) instruction Par d enition, cette construction equivaut strictement ` a celle-ci : expr 1 ; while (expr 2 ) { instruction expr 3 ; } Ainsi, dans la construction for(expr 1 ; expr 2 ; expr 3 ) : expr 1 est lexpression qui eectue les initialisations n ecessaires avant lentr ee dans la boucle ; expr 2 est le test de continuation de la boucle ; il est evalu e avant lex ecution du corps de la boucle ; expr 3 est une expression (par exemple une incr ementation) evalu ee ` a la n du corps de la boucle. Par exemple, linstruction Pascal : for i:=0 to 9 do t[i]:=0 se traduit tout naturellement en C for (i = 0; i < 10; i++) t[i] = 0; Les expressions expr 1 et expr 3 peuvent etre absentes (les points-virgules doivent cependant appara tre). Par exemple for ( ; expr 2 ; ) instruction equivaut ` a while (expr 2 ) instruction La condition de continuation expr 2 peut elle aussi etre absente. On consid` ere alors quelle est toujours vraie. Ainsi, la boucle ind enie peut se programmer : for ( ; ; ) instruction Bien entendu, il faut dans ce cas que labandon de la boucle soit programm e` a lint erieur de son corps (sinon cette boucle ind enie devient innie !). Exemple : 40
c H. Garreta, 1988-2013

INSTRUCTIONS

3.2

Pr esentation d etaill ee des instructions

for (;;) { printf("donne un nombre (0 pour sortir): "); scanf("%d", &n); if (n == 0) break; ... exploitation de la donn ee n ... } Lorsque labandon de la boucle correspond aussi ` a labandon de la proc edure courante, break est avantageusement remplac ee par return comme dans : char *adresse_de_fin(char *ch) { /* renvoie adresse du caract` ere qui suit la cha^ ne ch */ for (;;) if (*ch++ == 0) return ch; } 3.2.7 Instruction switch

Format : switch ( expression ) corps Le corps de linstruction switch prend la forme dun bloc {...} renfermant une suite dinstructions entre lesquelles se trouvent des constructions de la forme case expression-constante : ou bien default : Le fonctionnement de cette instruction est le suivant : expression est evalu ee ; enonc e case avec une constante qui egale la valeur de expression, le contr ole est transf er e` a sil existe un linstruction qui suit cet enonc e; si un tel case nexiste pas, et si l enonc e default existe, alors le contr ole est transf er e` a linstruction qui suit l enonc e default ; si la valeur de expression ne correspond ` a aucun case et sil ny a pas d enonc e default, alors aucune instruction nest ex ecut ee. Attention. Lorsquil y a branchement r eussi ` a un enonc e case, toutes les instructions qui le suivent sont ex ecut ees, jusqu` a la n du bloc ou jusqu` a une instruction de rupture (break). Autrement dit, linstruction switch sapparente beaucoup plus ` a une sorte de goto param etr e (par la valeur de lexpression) qu` a linstruction 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 linstruction ci-dessus a le m eme eet que laectation j = i. Pour obtenir un comportement similaire ` a celui du case...of... de Pascal, on doit utiliser linstruction break, comme dans lexemple suivant, dans lequel on compte la fr equence de chaque chire et des caract` eres blancs dans un texte :
c H. Garreta, 1988-2013

41

3.2

Pr esentation d etaill ee des instructions

3 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 port ee dune structure de contr ole (instructions for, while, do et switch), linstruction break provoque labandon de la structure et le passage ` a linstruction ecrite imm ediatement derri` ere. Lutilisation de break ailleurs qu` a lint erieur dune de ces quatre instructions constitue une erreur (que le compilateur signale). Par exemple la construction for (expr 1 ; expr 2 ; expr 3 ) { ... break; ... } equivaut ` a { for (expr 1 ; expr 2 ; expr 3 ) { ... goto sortie; ... } sortie: ; } Linstruction continue est moins souvent utilis ee. Dans la port ee dune structure de contr ole de type boucle (while, do ou for), elle produit labandon de lit eration courante et, le cas ech eant, le d emarrage de lit eration suivante. Elle agit comme si les instructions qui se trouvent entre linstruction continue et la n du corps de la boucle avaient et e supprim ees pour lit eration en cours : lex ecution continue par le test de la boucle (pr ec ed e, dans le cas de for, par lex ecution de lexpression dincr ementation). Lorsque plusieurs boucles sont imbriqu ees, cest la plus profonde qui est concern ee par les instructions break et continue. Dans ce cas, lemploi de ces instructions ne nous semble pas uvrer pour la clart e des programmes. 42
c H. Garreta, 1988-2013

INSTRUCTIONS

3.2

Pr esentation d etaill ee des instructions

3.2.9

Instruction return

Formats : return expression ; et return ; Dans un cas comme dans lautre linstruction return provoque labandon de la fonction en cours et le retour ` a la fonction appelante. Dans la premi` ere forme expression est evalu ee ; son r esultat est la valeur que la fonction renvoie ` a la fonction appelante ; cest donc la valeur que lappel de la fonction repr esente dans lexpression o` u il gure. Si n ecessaire, la valeur de expression est convertie dans le type de la fonction (d eclar e dans len-t ete), les conversions autoris ees etant les m emes que celles faites ` a loccasion dune aectation. Dans la deuxi` eme forme, la valeur retourn ee par la fonction reste ind etermin ee. On suppose dans ce cas que la fonction appelante nutilise pas cette valeur ; il est donc prudent de d eclarer void cette fonction. Absence dinstruction return dans une fonction. Lorsque la derni` ere instruction (dans lordre de lex ecution) dune fonction est termin ee, le contr ole est rendu egalement ` a la proc edure appelante. Tout se passe comme si laccolade fermante qui termine le corps de la fonction etait en r ealit e ecrite sous la forme ... return; }

c H. Garreta, 1988-2013

43

4 FONCTIONS

Fonctions

Beaucoup de langages distinguent deux sortes de sous-programmes 24 : les fonctions et les proc edures. Lappel dune fonction est une expression, tandis que lappel dune proc edure est une instruction. Ou, si on pr ef` ere, lappel dune fonction renvoie un r esultat, alors que lappel dune proc edure ne renvoie rien. En C on retrouve ces deux mani` eres dappeler les sous-programmes, mais du point de la syntaxe le langage ne conna t que les fonctions. Autrement dit, un sous-programme est toujours suppos e renvoyer une valeur, m eme lorsque celle-ci na pas de sens ou na pas et e sp eci ee. Cest pourquoi on ne parlera ici que de fonctions. Cest dans la syntaxe et la s emantique de la d enition et de lappel des fonctions que r esident les principales di erences entre le C original et le C ANSI. Nous expliquons principalement le C ANSI, rassemblant dans une section sp ecique (cf. section 4.2) la mani` ere de faire du C original.

4.1
4.1.1

Syntaxe ANSI ou avec prototype


D enition

Une fonction se d enit par la construction : type opt ident ( d eclaration-un-ident , ... d eclaration-un-ident ) instruction-bloc Notez quil ny a pas de point-virgule derri` ere le ) de la premi` ere ligne. La pr esence dun point-virgule ` a cet endroit provoquerait des erreurs bien bizarres, car la d enition serait prise pour une d eclaration (cf. section 4.1.4). La syntaxe indiqu ee ici est incompl` ete ; elle ne convient quaux fonctions dont le type est d eni simplement, par un identicateur. On verra ult erieurement (cf. section 5.4.1) la mani` ere de d eclarer des fonctions rendant un r esultat dun type plus complexe. La premi` ere ligne de la d enition dune fonction sappelle len-t ete, ou parfois le prototype, de la fonction. Chaque formule d eclaration-un-ident poss` ede la m eme syntaxe quune d eclaration de variable 25 . Exemple. int extract(char *dest, char *srce, int combien) { /* copie dans dest les combien premiers caract` eres de srce */ /* renvoie le nombre de caract` eres effectivement copi es */ int compteur; for (compteur = 0; compteur < combien && *srce != \0; compteur++) *dest++ = *srce++; *dest = \0; return compteur; } Contrairement ` a dautres langages, en C on ne peut pas d enir une fonction ` a lint erieur dune autre : toutes les fonctions sont au m eme niveau, cest-` a-dire globales. Cest le cas notamment de main, qui est une fonction comme les autres ayant pour seule particularit e un nom convenu.

4.1.2

Type de la fonction et des arguments

Len-t ete de la fonction d enit le type des objets quelle renvoie. Ce peut etre : erique ; tout type num tout type pointeur ; tout type struct ou union. Si le type de la fonction nest pas indiqu e, le compilateur suppose quil sagit dune fonction ` a r esultat entier. Ainsi, lexemple pr ec edent aurait aussi pu etre ecrit de mani` ere equivalente (mais cette pratique nest pas conseill ee) :
24. La notion de sous-programme (comme les procedures de Pascal, les subroutines de Fortran, etc.) est suppos ee ici connue du lecteur. 25. Restriction : on na pas le droit de mettre en facteur un type commun ` a plusieurs arguments. Ainsi, len-t ete de la fonction extract donn ee en exemple ne peut pas s ecrire sous la forme char *extract(char *dest, *srce, int n).

44

c H. Garreta, 1988-2013

FONCTIONS

4.1 Syntaxe ANSI ou avec prototype

extract(char *dest, char *srce, int combien) etc. sultat. Lorsquune fonction ne renvoie pas une valeur, cest-` a-dire lorsquelle corresFonctions sans re pond plus ` a une proc edure qu` a une vraie fonction, il est prudent de la d eclarer comme rendant un objet de type voidvoid. Ce type est garanti incompatible avec tous les autres types : une tentative dutilisation du r esultat de la fonction provoquera donc une erreur ` a la compilation. Le programmeur se trouve ainsi ` a labri dune utilisation intempestive du r esultat de la fonction. Exemple : void extract(char *dest, char *srce, int combien) { /* copie dans dest les combien premiers caract` eres 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. Lorsquune fonction na pas darguments, sa d enition prend la forme type opt ident ( void ) instruction-bloc On notera que, sauf cas exceptionnel, on ne doit pas ecrire une paire de parenth` eses vide dans la d eclaration ou la d enition dune fonction. En eet, un en-t ete de la forme type ident () ne signie pas que la fonction na pas darguments, mais (cf. section 4.2.2) que le programmeur ne souhaite pas que ses appels soient contr ol es par le compilateur. 4.1.3 Appel des fonctions

Lappel dune fonction se fait en ecrivant son nom, suivi dune paire de parenth` eses contenant eventuellement une liste darguments eectifs. Notez bien que les parenth` eses doivent toujours appara tre, m eme si la liste darguments est vide : si un nom de fonction appara t dans une expression sans les parenth` eses, alors il a la valeur dune constante adresse (ladresse de la fonction) et aucun appel de la fonction nest eectu e. Passage des arguments. En C, le passage des arguments se fait par valeur. Cela signie que les arguments formels 26 de la fonction repr esentent dauthentiques variables locales initialis ees, lors de lappel de la fonction, par les valeurs des arguments eectifs 27 correspondants. Supposons quune fonction ait et e d eclar ee ainsi type fonction ( type 1 arg formel 1 , ... type k arg formel k ) etc. alors, lors dun appel tel que fonction ( arg eectif 1 , ... arg eectif k ) la transmission des valeurs des arguments se fait comme si on ex ecutait les aectations : arg formel 1 = arg eectif 1 ... arg formel k = arg eectif k Cela a des cons equences tr` es importantes : les erreurs dans le nombre des arguments eectifs sont d etect ees et signal ees, si le type dun argument eectif nest pas compatible avec celui de largument formel correspondant, une erreur est signal ee, ecessaires avant d etre rang ees dans les argu les valeurs des arguments eectifs subissent les conversions n ments formels correspondants (exactement les m emes conversions qui sont faites lors des aectations).
26. Les arguments formels dune fonction sont les identicateurs qui apparaissent dans la d enition de la fonction, d eclar es ` a lint erieur de la paire de parenth` eses. 27. Les arguments eectifs dun appel de fonction sont les expressions qui apparaissent dans lexpression dappel, ecrites ` a lint erieur de la paire de parenth` eses caract eristiques de lappel.

c H. Garreta, 1988-2013

45

4.2

Syntaxe originale ou sans prototype

4 FONCTIONS

Remarque. Le langage ne sp ecie pas lordre chronologique des evaluations des arguments eectifs dun appel de fonction. Ces derniers doivent donc etre sans eets de bord les uns sur les autres. Par exemple, il est impossible de pr evoir quelles sont les valeurs eectivement pass ees ` a une fonction lors de lappel : x = une_fonction(t[i++], t[i++]); /* ERREUR !!! */ Si i0 est la valeur de i juste avant lex ecution de linstruction ci-dessus, alors cet appel peut aussi bien se traduire par une fonction(t[i0 ], t[i0 + 1]) que par une fonction(t[i0 + 1], t[i0 ]) ou m eme par une fonction(t[i0 ], t[i0 ]). Ce nest s urement pas indi erent ! Appel dune fonction inconnue. En C une fonction peut etre appel ee alors quelle na pas et e d enie (sous-entendu : entre le d ebut du chier et lendroit o` u lappel gure). 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 eectifs de lappel (ce qui, en particulier, emp eche les contr oles et conversions mentionn es plus haut). De plus, ces hypoth` eses sur la fonction constituent pour le compilateur une premi` ere d eclaration de la fonction. Toute d enition ou d eclaration ult erieure tant soit peu di erente sera quali ee de red eclaration ill egale 28 . Lorsque les hypoth` eses ci-dessus ne sont pas justes, en particulier lorsque la fonction ne renvoie pas un int, il faut : soit ecrire la d enition de la fonction appel ee avant celle de la fonction appelante, soit ecrire, avant lappel de la fonction, une d eclaration externe de cette derni` ere, comme expliqu e` a la section 4.1.4. 4.1.4 D eclaration externe dune fonction

Une d eclaration externe dune fonction est une d eclaration qui nest pas en m eme temps une d enition. On annonce lexistence de la fonction (d enie plus loin, ou dans un autre chier) tout en pr ecisant le type de son r esultat et le nombre et les types de ses arguments, mais on ne donne pas son corps, cest-` a-dire les instructions qui la composent.

Cela se fait en ecrivant ete identique ` a celui qui gure dans la d enition de la fonction (avec les noms des arguments soit un en-t formels), suivi dun point-virgule ; soit la formule obtenue ` a partir de len-t ete pr ec edent, en y supprimant les noms des arguments formels. Par exemple, si une fonction a et e d enie ainsi void truc(char dest[80], char *srce, unsigned long n, float x) { corps de la fonction } alors des d eclarations 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 appel ees des prototypes de la fonction. Le mot extern est facultatif, mais le pointvirgule est essentiel. Dans la premi` ere forme, les noms des arguments formels sont des identicateurs sans aucune port ee.

4.2
4.2.1

Syntaxe originale ou sans prototype


D eclaration et d enition

finition. En syntaxe originale la d De enition dune fonction prend la forme type opt ident ( ident , ... ident ) d eclaration ... d eclaration instruction-bloc
28. Cest une erreur surprenante, car le programmeur, oubliant que lappel de la fonction a entra n e une d eclaration implicite, con coit sa d enition ou d eclaration ult erieure comme etant la premi` ere d eclaration de la fonction.

46

c H. Garreta, 1988-2013

FONCTIONS

4.2 Syntaxe originale ou sans prototype

Les parenth` eses de len-t ete ne contiennent ici que la liste des noms des arguments formels. Les d eclarations de ces arguments se trouvent imm ediatement apr` es len-t ete, avant laccolade qui commence le corps de la fonction. Exemple : int extract(dest, srce, combien) /* copie dans dest les combien premiers caract` eres de srce */ /* renvoie le nombre de caract` eres effectivement copi es */ char *dest, char *srce; int combien; { int compteur; for (compteur = 0; compteur < combien && *srce != \0; compteur++) *dest++ = *srce++; *dest = \0; return compteur; } claration externe. En syntaxe originale, la d De eclaration externe de la fonction pr ec edente prend une des formes suivantes : extern int extract(); ou int extract(); Comme on le voit, ni les types, ni m eme les noms, des arguments formels napparaissent dans une d eclaration externe 29 . 4.2.2 Appel

En syntaxe originale, lors dun appel de la fonction le compilateur ne tient aucun compte ni du nombre ni des types des arguments formels 30 , indiqu es lors de la d enition de la fonction. Chaque argument est evalu e s epar ement, puis les valeurs des arguments eectifs de type char ou short sont converties dans le type int ; les valeurs des arguments eectifs de type float sont converties dans le type double ; les valeurs des arguments eectifs dautres types sont laiss ees telles quelles. Juste avant le transfert du contr ole ` a la fonction, ces valeurs sont copi ees dans les arguments formels correspondants. Ou plus exactement, dans ce que la fonction appelante croit etre les emplacements des arguments formels de la fonction appel ee : il ny a aucune v erication de la concordance, ni en nombre ni en type, des arguments formels et des arguments eectifs. Cela est une source derreurs tr` es importante. Par exemple, la fonction carre ayant et e ainsi d enie double carre(x) double x; { return x * x; } lappel x = carre(2); est erron e, car la fonction appelante d epose la valeur enti` ere 2 ` a ladresse du premier argument formel (quelle croit entier). Or la fonction appel ee croit recevoir le codage dun nombre ottant double. Le r esultat na aucun sens. Des appels corrects auraient et e x = carre((double) 2); ou x = carre(2.0);
29. Par cons equent, les d eclarations externes montr ees ici sont sans aucune utilit e, puisque int est le type suppos e des fonctions non d eclar ees. 30. Mais oui, vous avez bien lu ! Cest l` a la principale caract eristique de la s emantique dappel des fonctions du C original.

c H. Garreta, 1988-2013

47

4.3

Arguments des fonctions

4 FONCTIONS

4.2.3

Coexistence des deux syntaxes

A c ot e de ce que nous avons appel e la syntaxe ANSI, la syntaxe originale pour la d enition et la d eclaration externe des fonctions fait partie elle aussi du C ANSI ; de nos jours les programmeurs ont donc le choix entre lune ou lautre forme. Lorsque la fonction a et e d enie ou d eclar ee sous la syntaxe originale, les appels de fonction sont faits sans v erication ni conversion des arguments eectifs. Au contraire, lorsque la fonction a et e sp eci ee sous la syntaxe ANSI, ces contr oles et conversions sont eectu es. A cause de cette coexistence, si une fonction na pas dargument, la d enition de son en-t ete en C ANSI doit s ecrire sous la forme bien peu heureuse : type opt ident ( void ) car une paire de parenth` eses sans rien dedans signierait non pas que la fonction na pas darguments, mais quelle est d eclar ee sans prototype, cest-` a-dire que ses appels doivent etre trait es sans contr ole des arguments. Exemples : int getchar(void); double moyenne(); /* une fonction sans arguments dont les appels seront contr ol es */ /* une fonction avec ou sans arguments, aux appels incontr ol es */

4.3
4.3.1

Arguments des fonctions


Passage des arguments

Lid ee ma tresse est quen C le passage des arguments des fonctions se fait toujours par valeur. Apr` es avoir fait les conversions opportunes la valeur de chaque argument eectif est aect ee ` a largument formel correspondant. Si largument eectif est dun type simple (nombre, pointeur) ou dun type struct ou union, sa valeur est recopi ee dans largument formel correspondant, quelle que soit sa taille, cest-` a-dire quel que soit le nombre doctets quil faut recopier. 4.3.2 Arguments de type tableau

Apparaissant dans la partie ex ecutable dun programme, le nom dun tableau, et plus g en eralement toute expression d eclar ee tableau de T , est consid er ee comme ayant pour type adresse dun T et pour valeur ladresse du premier el ement du tableau. Cela ne fait pas intervenir la taille eective du tableau 31 . Ainsi lorsquun argument eectif est un tableau, lobjet eectivement pass e` a la fonction est uniquement ladresse du premier el ement et il sut que lespace r eserv e dans la fonction pour un tel argument ait la taille, xe, dun pointeur. Dautre part, en C il nest jamais v eri e que les indices des tableaux appartiennent bien ` a lintervalle 0...N 1 d etermin e par le nombre d el ements indiqu e dans la d eclaration. Il en d ecoule la propri et e suivante : Dans la d eclaration dun argument formel t de type tableau de T : lindication du nombre d el ements de t est sans utilit e 32 ; les formules type t[ ] et type *t sont tout ` a fait equivalentes ; etre appel ee indi eremment avec un tableau ou un pointeur pour argument eectif. la fonction pourra Exemple. Len-t ete

vague de la fonction suivante

int strlen(cha ne de caract` eres s) { int i = 0; while (s[i] != 0) i++; return i; } peut indi eremment etre concr etis ee de lune des trois mani` eres suivantes : int strlen(char s[80]) int strlen(char s[]) int strlen(char *s)
31. Il nen est pas de m eme lors de la d enition, o` u la taille du tableau ne peut pas etre ignor ee (elle est indispensable pour lallocation de lespace m emoire). 32. Attention, cette question se complique notablement dans le cas des tableaux multidimensionnels. Cf. 6.2.3

48

c H. Garreta, 1988-2013

FONCTIONS

4.3 Arguments des fonctions

Dans les trois cas, si t est un tableau de char et p ladresse dun char, les trois appels suivants sont corrects : l1 = strlen(t); l2 = strlen(p); l3 = strlen("Bonjour"); A retenir : parce que les arguments eectifs sont pass es par valeur, les tableaux sont pass es par adresse. Passage par valeur des tableaux. Cependant, puisque les structures sont pass ees par valeur, elles fournissent un moyen pour obtenir le passage par valeur dun tableau, bien que les occasions o` u cela est n ecessaire semblent assez rares. Il sut de d eclarer le tableau comme le champ unique dune structure enveloppe ne servant 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 ach ee est 1, ce qui prouve que la fonction appel ee na modi e quune copie locale du tableau envelopp e, cest-` a-dire que celui-ci a bien et e pass e par valeur (les structures seront vues ` a la section 5.2.1).

4.3.3

Arguments par adresse

Les arguments qui sont dun type simple (char, int, pointeur...) ou bien des struc ou des union sont toujours pass es par valeur. Lorsque largument eectif est une variable, ce m ecanisme emp eche quelle puisse etre modi ee par la fonction appel ee. Or une telle modication est parfois souhaitable ; elle requiert que la fonction puisse acc eder non seulement ` a la valeur de la variable, mais aussi ` a son adresse. Cest ce qui sappelle le passage par adresse des arguments. Alors que certains langages le prennent en charge, C ne fournit aucun m ecanisme sp ecique : le passage de ladresse de largument doit etre programm e explicitement en utilisant comme argument un pointeur vers la variable. Par exemple, supposons avoir ` a ecrire une fonction int quotient(a, b) cens ee renvoyer le quotient de la division euclidienne de lentier a par lentier b et devant en plus remplacer a par le reste de cette division. On devra la d enir : int quotient(int *a, int b) { int r = *a / b; *a = *a % b; return r; } Ci-dessus, b repr esente un entier ; a ladresse dun entier. La notation *a fait r ef erence ` a une variable (ou, plus g en eralement, une lvalue ) appartenant ` a la proc edure appelante. Largument eectif correspondant ` a un tel argument formel doit etre une adresse. Souvent, cela est obtenu par lutilisation de lop erateur &. Par exemple, si x, y et z sont des variables enti` eres, un appel correct de la fonction pr ec edente sera z = quotient(&x, y); Bien entendu, lop erateur & ne doit pas etre utilis e si largument eectif est d ej` a une adresse. Le sch ema suivant r esume les principales situations possibles. Soient les fonctions
c H. Garreta, 1988-2013

49

4.3

Arguments des fonctions

4 FONCTIONS

f(int a) { ... } g(int *b) { ... } h(int c, int *d) { ... } A lint erieur de la fonction h, des appels corrects de f et g sont : f(c); f(*d); g(&c); g(d); cette derni` ere expression correspondant, bien s ur, ` a g(&*d). 4.3.4 Arguments en nombre variable

Le langage C permet de d enir des fonctions dont le nombre darguments nest pas x e et peut varier dun appel ` a un autre. Nous exposons ici la mani` ere dont cette facilit e est r ealis ee dans le C ANSI. Dans le C original elle existe aussi ; elle consiste ` a utiliser, sans garde-fou, une faiblesse du langage (la non-v erication du nombre darguments eectifs) et une caract eristique du syst` eme sous-jacent (le sens dempilement des arguments eectifs). Mais tout cela est susamment casse-cou pour que nous pr ef erions nexpliquer que la mani` ere ANSI de faire, un peu plus able. Une fonction avec un nombre variable darguments est d eclar ee en explicitant quelques arguments xes, au moins un, suivis dune virgule et de trois points : , ... . Exemple : int max(short n, int x0, ...) Une telle fonction peut etre appel ee avec un nombre quelconque darguments eectifs, mais il faut quil y en ait au moins autant quil y a darguments formels nomm es, ici deux. Exemple dappel : m = max(3, p, q, r); Pour les arguments nomm es, laectation des valeurs des arguments eectifs aux arguments formels est faite comme dordinaire. Pour les arguments anonymes elle seectue comme pour une fonction sans prototype : les char et les short sont convertis en int, les float en double et il ny a aucune autre conversion. A lint erieur de la fonction on acc` ede aux arguments anonymes au moyen des macros va start et va arg d enies dans le chier stdarg.h, comme le montre lexemple 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 a b c appels corrects de cette fonction seraient : = max(3, p, q, r); = max(8, x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7]); = max(2, u, 0);

Les el ements d enis dans le chier stdarg.h sont : eclaration de la variable pointeur, qui sera automatiquement g er ee par le dispositif dacc` es va list pointeur D aux arguments. Le programmeur choisit le nom de cette variable, mais il nen fait rien dautre que la mettre comme argument dans les appels des macros va start et va arg. 50
c H. Garreta, 1988-2013

FONCTIONS

4.3 Arguments des fonctions

va start(pointeur , dernier argument ) Initialisation de la variable pointeur ; dernier argument doit etre le dernier des arguments explicitement nomm es dans len-t ete de la fonction. va arg(pointeur , type ) Parcours des arguments anonymes : le premier appel de cette macro donne le premier argument anonyme ; chaque appel suivant donne largument suivant le dernier d ej` a obtenu. Chaque fois, type doit d ecrire le type de largument qui est en train d etre r ef erenc e. Comme lexemple le montre, le principe des fonctions avec un nombre variable darguments est de d eclarer eectivement au moins un argument, dont la fonction peut d eduire ladresse des autres. Il faut aussi choisir le moyen dindiquer ` a la fonction le nombre de valeurs r eellement fournies. Dans lexemple ci-dessus ce nombre est pass e comme premier argument ; une autre technique consiste ` a utiliser une valeur intruse (ex : le pointeur NULL parmi des pointeurs valides). La fonction printf est un autre exemple de fonction avec un nombre variable darguments. Elle est d eclar ee (dans le chier 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 dautres fonctions) et dautres remarques sur les fonctions et leurs adresses sont trait ees ` a la section 6.3.1.

c H. Garreta, 1988-2013

51

OBJETS STRUCTURES

5
5.1
5.1.1

Objets structur es
Tableaux
Cas g en eral

Dans le cas le plus simple la d eclaration dun tableau se fait par une formule comme : type-de-base nom [ expression opt ]; Exemple : unsigned long table[10]; Lexpression optionnelle qui gure entre les crochets sp ecie le nombre d el ements du tableau. Le premier el ement poss` ede toujours lindice 0. Par cons equent, un tableau t d eclar e de taille N poss` ede les el ements t0 , t1 , ... tN 1 . Ainsi la d enition pr ec edente alloue un tableau de 10 el ements, nomm es respectivement table[0], table[1], ... table[9]. Lexpression optionnelle qui gure entre les crochets doit etre de type entier et constante au sens de la section 1.3.4, cest-` a-dire une expression dont tous les el ements sont connus au moment de la compilation. De cette mani` ere le compilateur peut l evaluer et conna tre la quantit e despace n ecessaire pour loger le tableau. Elle est obligatoire lorsquil sagit dune d enition, car il y a alors allocation eective du tableau. Elle est facultative dans le cas des d eclarations qui ne sont pas des d enitions, cest-` a-dire : lors de la d eclaration dun tableau qui est un argument formel dune fonction ; lors de la d eclaration dun tableau externe (d eni dans un autre chier). mantique du nom dun tableau. En g Se en eral lorsque le nom dun tableau appara t dans une expression il y joue le m eme r ole quune constante de type adresse ayant pour valeur ladresse du premier el ement du tableau. Autrement dit, si t est de type tableau, les deux expressions t sont equivalentes. Les exceptions ` a cette r` egle, cest-` a-dire les expressions de type tableau qui ne sont pas equivalentes ` a ladresse du premier el ement du tableau, sont loccurrence du nom du tableau dans sa propre d eclaration ; lapparition dun nom du tableau comme argument de sizeof. Ce sont les seules occasions o` u le compilateur veut bien se souvenir quun nom de tableau repr esente aussi un espace m emoire poss edant une certaine taille. De tout cela, il faut retenir en tout cas que le nom dun tableau nest pas une lvalue et donc quil nest pas possible daecter un tableau ` a un autre. Si a et b sont deux tableaux, m eme ayant des types identiques, laectation b = a sera comprise comme laectation dune adresse ` a une autre, et rejet ee, car b nest pas modiable. Tableaux multidimensionnels. C ne pr evoit que les tableaux ` a un seul indice, mais les el ements des tableaux peuvent ` a leur tour etre des tableaux. La d eclaration dun tableau avec un nombre quelconque dindices suit la syntaxe : type nom [ expression opt ] [ expression opt ] ... [ expression opt ] qui est un cas particulier de formules plus g en erales expliqu ees ` a la section 5.4.1. Par exemple, la d eclaration double matrice[10][20]; introduit matrice comme etant le nom dun tableau de 10 el ements 33 qui, chacun ` a son tour, est un tableau de 20 el ements de type double. 5.1.2 Initialisation des tableaux &t[0]

Une variable de type tableau peut etre initialis ee lors de sa d eclaration. Cela se fait par une expression ayant la syntaxe : { expression , expression , ... expression }
33. Dans le cas dune matrice, la coutume est dappeler ces el ements les lignes de la matrice. Cela permet de dire alors : les matrices sont rang ees par lignes .

en C

52

c H. Garreta, 1988-2013

OBJETS STRUCTURES

5.1 Tableaux

Exemple : int t[5] = { 10, 20, 30, 40, 50 }; Les expressions indiqu ees doivent etre des expressions constantes au sens de la section 1.3.4. Sil y a moins dexpressions que le tableau a d el ements, les el ements restants sont remplis de z eros (cas des variables globales) ou bien restent ind etermin es (cas des variables locales). Sil y a plus dexpressions dinitialisation que d el ements dans le tableau, une erreur est signal ee. Dautre part, la pr esence dun initialisateur dispense dindiquer la taille du tableau. Il est convenu que ce dernier doit avoir alors pour taille le nombre eectif de valeurs initiales. Par exemple, la d enition int t[] = { 10, 20, 30, 40, 50 }; alloue un tableau ` a 5 composantes garni avec les valeurs indiqu ees. Lorsque le tableau comporte des sous-tableaux, cest-` a-dire lorsque cest un tableau ` a plusieurs indices, la liste des expressions dinitialisation peut comporter des sous-listes indiqu ees ` a leur tour avec des accolades, mais ce nest pas une obligation. Le cas ech eant, la r` egle de compl etion par des z eros sapplique aussi aux sous-listes. Par exemple, les d eclarations 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 gure 5.

1 4 7 0

2 5 8 0

3 6 9 0

0 0 0 0

1 5 9 0

2 6 0 0

3 7 0 0

4 8 0 0

t1

t2

Figure 5 Tableaux initialis es Remarque. Rappelons que ces repr esentations rectangulaires sont tr` es conventionnelles. Dans la m emoire de lordinateur ces deux tableaux sont plut ot arrang es comme sur la gure .

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
Figure 6 Tableaux

a plat `

Si la taille dune composante est un diviseur de la taille dun entier, les tableaux sont tass es : chaque composante occupe le moins de place possible. En particulier, dans un tableau de char (resp. de short), chaque composante occupe un (resp. deux) octet(s). 5.1.3 Cha nes de caract` eres

Les cha nes de caract` eres sont repr esent ees comme des tableaux de caract` eres. Un caract` ere nul suit le dernier caract` ere utile de la cha ne et en indique la n. Cette convention est suivie par le compilateur (qui range les cha nes constantes en leur ajoutant un caract` ere nul), ainsi que par les fonctions de la librairie standard qui construisent des cha nes. Elle est suppos ee v eri ee par les fonctions de la librairie standard qui exploitent des cha nes. Par cons equent, avant de passer une cha ne ` a une telle fonction, il faut sassurer quelle comporte bien un caract` ere nul ` a la n.
c H. Garreta, 1988-2013

53

5.2

Structures et unions

OBJETS STRUCTURES

Donnons un exemple classique de traitement de cha nes : la fonction strlen (extraite de la biblioth` eque standard) qui calcule le nombre de caract` eres utiles dune cha ne : int strlen(char s[]) { register int i = 0; while (s[i++] != \0) ; return i - 1; } Une constante-cha ne de caract` eres apparaissant dans une expression a le m eme type quun tableau de caract` eres cest-` a-dire, pour lessentiel, le type adresse dun caract` ere . Par exemple, si p a et e d eclar e char *p; laectation suivante est tout ` a fait l egitime : p = "Bonjour. Comment allez-vous?"; Elle aecte ` a p ladresse de la zone de la m emoire o` u le compilateur a rang e le texte en question (ladresse du B de "Bonjour..."). Dans ce cas, bien que lexpression *p soit une lvalue (ce qui est une propri et e syntaxique), elle ne doit pas etre consid er ee comme telle car *p repr esente un objet m emoris e dans la zone des constantes, et toute occurrence de *p comme op erande gauche dune aectation : *p = expression ; constituerait une tentative de modication dun objet constant. Cette erreur est impossible ` a d eceler ` a la compilation. Selon le syst` eme sous-jacent, soit une erreur sera signal ee ` a lex ecution, provoquant labandon imm ediat du programme, soit aucune erreur ne sera signal ee mais la validit e de la suite du traitement sera compromise. Initialisation. Comme tous les tableaux, les variables-cha nes de caract` eres peuvent etre initialis ees lors de leur d eclaration. De ce qui a et e dit pour les tableaux en g en eral il d ecoule que les expressions suivantes sont correctes : char message1[80] = { S, a, l, u, t, \0 }; char message2[] = { S, a, l, u, t, \0 }; (la deuxi` eme formule d enit un tableau de taille 6). Heureusement, C ore un raccourci : les deux expressions pr ec edentes peuvent s ecrire de mani` ere tout ` a fait equivalente : char message1[80] = "Salut"; char message2[] = "Salut"; Il faut bien noter cependant que lexpression "Salut" qui appara t dans ces deux exemples nest pas du tout trait e par le compilateur comme les autres cha nes de caract` eres constantes rencontr ees dans les programmes. Ici, il ne sagit que dun moyen commode pour indiquer une collection de valeurs initiales ` a ranger dans un tableau. A ce propos, voir aussi la remarque 2 de la section 6.2.1.

5.2
5.2.1

Structures et unions
Structures

Les structures sont des variables compos ees de champs de types di erents. Elles se d eclarent sous la syntaxe suivante : { }{ } { declaration ... declaration } nom , ... nom struct nomopt ; rien rien A la suite du mot-cl e struct on indique : a la structure. Par la suite, lexpression struct facultativement, le nom que lon souhaite donner ` nom pourra etre employ ee comme une sorte de nom de type ; eclarations des champs de la structure. Chaque champ peut facultativement, entre accolades, la liste des d avoir un type quelconque (y compris un autre type structure, bien s ur) ; enit ou d eclare comme poss edant la structure ici d enie. facultativement, la liste des variables que lon d Exemple. struct fiche { int numero; char nom[32], prenom[32]; } a, b, c; 54
c H. Garreta, 1988-2013

OBJETS STRUCTURES

5.2

Structures et unions

Cette d eclaration introduit trois variables a, b et c, chacune constitu ee des trois champs numero, nom et prenom ; en m eme temps elle donne ` a cette structure le nom fiche. Plus loin, on pourra d eclarer de nouvelles variables analogues ` a a, b et c en ecrivant simplement struct fiche x, y; (remarquez quil faut ecrire d eclarations :

struct fiche et non pas

fiche ). Nous aurions pu aussi bien faire les

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 derni` ere forme nous aurait emp ech e par la suite de d eclarer aussi facilement dautres variables de m eme type, ou de cr eer un pointeur sur une telle structure (ce qui est indispensable dans le cas des structures r ecursives, par exemple les maillons des listes cha n ees). Comme dans beaucoup de langages, on acc` ede aux champs dune variable de type structure au moyen de lop erateur . . Exemple : a.numero = 1234; Les structures supportent les manipulations globales 34 : on peut aecter une expression dun type structure ` a une variable de type structure (pourvu que ces deux types soient identiques) ; les structures sont pass ees par valeur lors de lappel des fonctions ; le r esultat dune fonction peut etre une structure. La possibilit e pour une fonction de rendre une structure comme r esultat est ocielle dans le C ANSI ; sur ce point les compilateurs plus anciens pr esentent des di erences de comportement. Dautre part il est facile de voir que -sauf dans le cas de structures vraiment tr` es simples- les transmettre comme r esultats des fonctions est en g en eral peu ecace. Voici quelques informations sur la disposition des champs des structures. Elles sont sans importance lorsque les structures sont exploit ees par le programme qui les cr ee, mais deviennent indispensables lorsque les structures sont partag ees entre sous-programmes ecrits dans divers langages ou lorsquelles servent ` a d ecrire les articles dun chier existant en dehors du programme. Position. C garantit que les champs dune structure sont allou es dans lordre o` u ils apparaissent dans la d eclaration. Ainsi, avec la d enition struct modele { type 1 a, b; type 2 c, d, e; type 3 f; } on trouve, dans le sens des adresses croissantes : x.a, x.b, x.c, x.d, x.e et enn x.f 35 . . Les champs des structures subissent en g Contigu te en eral des contraintes dalignement d ependant du syst` eme sous-jacent, qui engendrent des trous anonymes entre les champs. Il sagit souvent des m emes r` egles que celles qui p` esent sur les variables simples. Deux cas relativement fr equents sont les suivants : a nimporte quelle adresse, tous les autres champs devant commencer ` a une les champs de type char sont ` adresse paire ; a une adresse multiple de leur taille (les short ` a une les champs dun type simple doivent commencer ` adresse paire, les long ` a une adresse multiple de quatre, etc.).
34. Lexpression correcte de cette propri et e consisterait ` a dire quune structure, comme une expression dun type primitif, b en ecie de la s emantique des valeurs, par opposition ` a un tableaux qui, lui, est assujetti ` a la s emantique des valeurs. 35. Alors que, avec la d eclaration correspondante en Pascal (facile ` a imaginer), beaucoup de compilateurs alloueraient les champs dans lordre : x.b, x.a, x.e, x.d, x.c et enn x.f.

c H. Garreta, 1988-2013

55

5.2

Structures et unions

OBJETS STRUCTURES

Initialisation. Une variable de type structure peut etre initialis ee au moment de sa d eclaration, du moins dans le cas dune variable globale. La syntaxe est la m eme que pour un tableau. Exemple : 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 les champs restants sont initialis es par des z eros. Donner plus de valeurs quil ny a de champs constitue une erreur. 5.2.2 Unions

Tandis que les champs des structures se suivent sans se chevaucher, les champs dune union commencent tous au m eme endroit et, donc, se superposent. Ainsi, une variable de type union peut contenir, ` a des moments di erents, des objets de types et de tailles di erents. La taille dune union est celle du plus volumineux de ses champs. La syntaxe de la d eclaration et de lutilisation dune union est exactement la m eme que pour les structures, avec le mot union ` a la place du mot struct. Exemple (la struct adresse virt est d enie ` 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 tant ot comme un entier long, tant ot comme ladresse dun caract` ere, tant ot comme la structure bizarre d enie ci-apr` es. Par exemple, le programme suivant teste si les deux bits hauts dune certaine variable m, de type unsigned long, sont nuls : mix.mot = m; if (mix.adrv.tp == 0) etc. Remarque 1. Dans ce genre de probl` emes, les op erateurs binaires de bits (&, |, ^) sav` erent egalement tr` es utiles. Linstruction pr ec edente pourrait aussi s ecrire (sur une machine 32 bits, o` u int est synonyme de long) : if (m & 0xC0000000 == 0) etc. (l ecriture binaire du chire hexa C est 1100). Bien s ur, les programmes de cette sorte n etant pas portables, ces deux mani` eres de faire peuvent etre equivalentes sur une machine et ne pas l etre sur une autre. Remarque 2. Les unions permettent de consid erer un m eme objet comme poss edant plusieurs types ; elles semblent donc faire double emploi avec lop erateur de conversion de type. Ce nest pas le cas. Dune part, cet op erateur ne supporte pas les structures, tandis quun champ dune union peut en etre une. Dautre part, lorsque lon change le type dun objet au moyen de lop erateur de changement de type, C convertit la donn ee, an que lexpression ait, sous le nouveau type, une valeur vraisemblable et utile. Par exemple, si x est une variable r eelle (float), lexpression (int) x convertit x en lentier dont la valeur est la plus voisine de la valeur quavait le r eel x. Cela implique une transformation, eventuellement assez co uteuse, de la valeur de x. A loppos e, lorsquon fait r ef erence ` a un champ dune union, la donn ee ne subit aucune transformation, m eme si elle a et e aect ee en faisant r ef erence ` a un autre champ. Par exemple, si lon d eclare union { float reel; int entier; } x; et quensuite on ex ecute les aectations : x.reel = 12.5; i = x.entier; lentier i obtenu sera celui dont la repr esentation binaire co ncide avec la repr esentation binaire du nombre ottant 12.5 ; cet entier na aucune raison d etre voisin de 12. 5.2.3 Champs de bits

Le langage C permet aussi dutiliser des structures et des unions dont les champs, tous ou seulement certains, sont faits dun nombre quelconque de bits. De tels champs doivent etre ainsi d eclar es : 56
c H. Garreta, 1988-2013

OBJETS STRUCTURES { } : constante-entiere

5.3 Enum erations

unsignedopt intopt identicateur rien

Chaque constante pr ecise le nombre de bits quoccupe le champ. Le type doit obligatoirement etre entier. Lorsque le type et le nom du champ sont absents, le nombre indiqu e de bits est quand m eme r eserv e : on suppose quil sagit de bits de rembourrage entre deux champs nomm es. Chacun de ces champs est log e imm ediatement apr` es le champ pr ec edent, sauf si cela le met ` a cheval sur deux mots (de type int) ; dans ce cas, le champ est log e au d ebut du mot suivant. Exemple : la structure struct adresse_virt { unsigned depl : 9; unsigned numpagv : 16; : 5; unsigned tp : 2; }; d ecoupe un mot de 32 bits en en quatre champs, comme indiqu e 36 sur la gure 7.
29 24 8 0

tp

numpag

depl

Figure 7 Champs de bits Attention. Dans tous les cas, les champs de bits sont tr` es d ependants de limplantation. Par exemple, certaines machines rangent les champs de gauche ` a droite, dautres de droite ` a gauche. Ainsi, la portabilit e des structures contenant des champs de bits nest pas assur ee ; cela limite leur usage ` a une certaine cat egorie de programmes, comme ceux qui communiquent avec leur machine-h ote ` a un niveau tr` es bas (noyau dun syst` eme dexploitation, gestion des p eriph eriques, etc.). Ces programmes sont rarement destin es ` a etre port es dun syst` eme ` a un autre.

5.3

Enum erations

Les enum erations ne constituent pas un type structur e. Si elles gurent ici cest uniquement parce que la syntaxe de leur d eclaration poss` ede des points communs avec celle des structures. Dans les cas simples, cette syntaxe est la suivante : { enum nomopt { id-valeur , ... id-valeur } rien }{ nom , ... nom rien } ;

o` u chaque id-valeur est ` a son tour de la forme { identicateur Exemple : enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi } x, y; A la suite dune telle d eclaration, le type enum jourouvrable est connu ; les variables x et y le poss` edent. Ce type est form e dune famille nie de symboles qui repr esentent des constantes enti` eres : 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 d eclarations enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi }; ... enum jourouvrable x, y, a, b, c;
36. Sur un syst` eme o` u un mot est fait de 32 bits (sinon un champ serait ` a cheval sur deux mots, ce qui est interdit).

= expression-constante rien

c H. Garreta, 1988-2013

57

5.4

D eclarateurs complexes

OBJETS STRUCTURES

ou encore enum { lundi, mardi, mercredi, jeudi, vendredi } x, y, a, b, c; mais cette derni` ere forme nous aurait emp ech e par la suite de d eclarer aussi facilement dautres variables du m eme type. Bien s ur, les enum erations peuvent se combiner avec la d eclaration typedef (cf. section 5.4.3) : apr` es la d eclaration 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 enum er es sont totalement compatibles entre eux. Par cons equent, laectation dun entier ` a une variable dun type enum er e ou laectation r eciproque ne provoquent en principe aucun avertissement de la part du compilateur . Ainsi, en C, les types enum er es ne sont quune deuxi` eme mani` ere de donner des noms ` a des nombres entiers (la premi` ere mani` ere etant lemploi de la directive #define, voir section 8.1.2). Par d efaut, la valeur de chacune de ces constantes est egale au rang de celle-ci dans l enum eration (lundi vaut 0, mardi vaut 1, etc.). On peut alt erer cette s equence en associant explicitement des valeurs ` a certains el ements : 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

D eclarateurs complexes

Jusquici nous avons utilis e des formes simpli ees des d eclarations. Tous les objets etaient soit simples, soit des tableaux, des fonctions ou des adresses dobjets simples. Il nous reste ` a voir comment d eclarer des tableaux de pointeurs , des tableaux dadresses de fonctions , des fonctions rendant des adresses de tableaux , etc. Autrement dit, nous devons expliquer la syntaxe des formules qui permettent de d ecrire des types de complexit e quelconque. La question concerne au premier chef les descripteurs de types, qui apparaissent dans trois sortes d enonc es : les d enitions de variables (globales et locales), les d eclarations des param` etres formels des fonctions, les d eclarations des variables externes et les d eclarations des champs des structures et des unions ; les d enitions des fonctions et les d eclarations des fonctions externes ; les d eclarations de types (typedef) La m eme question r eappara t dans des constructions qui d ecrivent des types sans les appliquer ` a des identicateurs. Appelons cela des types d esincarn es. Ils sont utilis es par lop erateur de changement de type lorsque le type de destination nest pas d eni par un identicateur : (char *) expression ; comme argument de sizeof (rare) : sizeof(int [100]) ; dans les prototypes des fonctions : strcpy(char *, char *); 5.4.1 Cas des d eclarations

Un d eclarateur complexe se compose de trois sortes dingr edients : erique), enum, struct ou union ou bien un type un type terminal qui est un type de base (donc num auquel on a donn e un nom par une d eclaration typedef ; un certain nombre doccurrences des op erateurs (), [] et * qui repr esentent des proc ed es r ecursifs de construction de types complexes ; lidenticateur quil sagit de d eclarer. Ces formules ont mauvaise r eputation. Si elles sont faciles ` a lire et ` a traiter par le compilateur, elles sav` erent diciles ` a composer et ` a lire par le programmeur, car les symboles qui repr esentent les proc ed es r ecursifs de construction sont peu expressifs et se retrouvent plac es ` a lenvers de ce qui aurait sembl e naturel. Pour cette raison nous allons donner un proc ed e m ecanique qui, ` a partir dune sorte de description ` a lendroit , facile ` a concevoir, fabrique la d eclaration C souhait ee. Appelons description naturelle dun type une expression de lun des types suivants : la description correcte en C dun type de base, dun type enum, dune struct ou union ou le nom dun type baptis e par typedef ; 58
c H. Garreta, 1988-2013

OBJETS STRUCTURES

5.4 D eclarateurs complexes

la formule la formule la formule

tableau de T , o` u T est la description naturelle dun type ; adresse dun T (ou pointeur sur un T ), o` u T est la description naturelle dun type ; fonction rendant un T , o` u T est la description naturelle dun type.

Dans ces conditions, nous pouvons enoncer la

recette de cuisine du tableau 3.

Pour obtenir la d eclaration dune variable ou dune fonction : 1. Ecrivez : ` a gauche, la description naturelle du type, ` a droite, lidenticateur ` a d eclarer (un seul). 2. Tant que lexpression ` a gauche nest pas un type de base, enum, struct ou union, transformez les deux expressions de la mani` ere suivante : (a) fonction rendant un exp g exp d exp g (exp d )() (b) tableau de exp g exp d exp g (exp d )[] (c) pointeur sur exp g exp d exp g *exp d De plus, dans les r` egles (a) et (b), si exp d nest pas de la forme *exp, alors les parenth` eses autour de exp d peuvent etre omises. Table 3 Construction dun d eclarateur complexe Exemple 1. Soit ` a d eclarer tabPtr comme un tableau (de dimension ind etermin ee) de pointeurs dentiers. Appliquant les r` egles du tableau 3, nous ecrirons successivement : tableau de pointeur sur int pointeur sur int pointeur sur int int tabPtr (tabPtr)[] tabPtr[] *tabPtr[]

La partie gauche de la ligne ci-dessus se r eduisant ` a un type simple, cette ligne constitue la d eclaration C cherch ee, comme il faudra l ecrire dans un programme : int *tabPtr[]; . Exemple 2. Pour la comparer ` a la pr ec edente, cherchons ` a ecrire maintenant la d eclaration de ptrTab, un pointeur sur un tableau 37 dentiers : pointeur sur un tableau de int tableau de int int ptrTab *ptrTab (*ptrTab)[]

Les parenth` eses ci-dessus ne sont pas facultatives, car elles encadrent une expression qui commence par * (dit autrement, elles sont n ecessaires, car [] a une priorit e sup erieure ` a *). Les d eclarations pour lesquelles le membre gauche est le m eme (` a la n des transformations) peuvent etre regroup ees. Ainsi, les d eclarations de tabPtr et ptrTab peuvent etre ecrites ensemble : int *tabPtr[], (*ptrTab)[]; En toute rigueur les r` egles a et b de ce tableau devraient etre plus complexes, car la description naturelle du type dune fonction inclut en g en eral la sp ecication des types des arguments (le prototype de la fonction), et la description naturelle dun type tableau comporte lindication du nombre de composantes de celui-ci. En r ealit e ces indications sajoutent aux r` egles donn ees ici, presque sans modier le processus que ces r` egles etablissent : tout simplement, des informations annexes sont ecrites ` a lint erieur des crochets des tableaux et des parenth` eses des fonctions. Exemple 3. Soit ` a d eclarer une matrice de 10 lignes et 20 colonnes. Nos pouvons ecrire successivement : tableau de 10 tableau de 20 double tableau de 20 double tableau de 20 double double double matrice (matrice)[10] matrice[10] (matrice[10])[20] matrice[10][20]

37. La notion de pointeur sur un tableau est assez acad emique, puisquune variable de type tableau repr esente d ej` a ladresse de celui-ci (dans la notion d adresse dun tableau il y a une double indirection). En pratique on nen a presque jamais besoin.

c H. Garreta, 1988-2013

59

5.4

D eclarateurs complexes

OBJETS STRUCTURES

Exemple 4. Quand on ecrit des programmes relevant du calcul num erique on a souvent besoin de variables de type adresse dune fonction R R . Voici comment ecrire un tel type adresse de fonction (avec argument double) rendant double fonction (avec argument double) rendant double double La d eclaration cherch ee est donc

adrFon *adrFon (*adrFon)(double)

double (*adrFon)(double) .

Exemple 5. Cet exemple abominable est extrait de la biblioth` eque UNIX. La fonction signal renvoie ladresse dune proc edure , cest-` a-dire dune fonction rendant void. Voici comment il faudrait la d eclarer en syntaxe originale, cest-` a-dire sans sp ecier les arguments de la fonction signal (cette d eclaration est fournie dans le chier <signal.h>) : fonction pointeur pointeur fonction void rendant un pointeur sur fonction rendant void sur fonction rendant un void sur fonction rendant un void rendant un void signal (signal)() signal() *signal() (*signal())()

En syntaxe ANSI il faut en plus donner les types des arguments des fonctions. On nous dit que les arguments de 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 d ej` a ecrire le type pointeur sur une fonction prenant un int et rendant un void (ce nest pas tr` es di erent de lexemple pr ec edent) : 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 void signal(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 d eclaration cherch ee est void (*signal(int sig, int (*fon)(int)))(int); Comme annonc e, les expressions nales obtenues sont assez lourdes ` a dig erer pour un humain. A leur d echarge on peut tout de m eme dire quune fois ecrites elles se r ev` elent bien utiles puisquelles font appara tre les types de leurs el ements terminaux comme ils seront employ es dans la partie ex ecutable des programmes. Ainsi, ` a la vue de lexpression float matrice[10][20]; on comprend sans eort suppl ementaire quun el ement de la matrice s ecrira matrice[i][j] et que cette formule repr esentera un objet de type float. De m eme, l enonc e void (*signal(sig, func))(); nous fera reconna tre imm ediatement lexpression (*signal(2,f))(n) comme un appel correct de la proc edure rendue par lappel de signal. 5.4.2 Pointeurs et tableaux constants et volatils

Les qualieurs const et volatile peuvent aussi appara tre dans les d eclarateurs complexes. Plut ot que de rendre encore plus lourdes des explications qui le sont d ej` a beaucoup, contentons-nous de montrer des exemples signicatifs. Les cas les plus utiles sont les suivants : 1. La formule : const type *nom d eclare nom comme un pointeur vers un objet constant ayant le type indiqu e (la variable point ee, *nom, ne doit pas etre modi ee). 2. La formule 60
c H. Garreta, 1988-2013

OBJETS STRUCTURES

5.4 D eclarateurs complexes

type *const nom d eclare nom comme un pointeur constant vers un objet ayant le type indiqu e (cest la variable nom qui ne doit pas etre modi ee). 3. Plus simplement, la formule const type nom [expr opt ] d eclare nom comme un tableau dobjets constants 38 (nom [i] ne doit pas etre modi e). Ainsi par exemple les d eclarations int e, *pe, *const pce = &e, te[100]; const int ec = 0, *pec, *const pcec = &ec, tec[100]; d eclarent successivement : e : un entier pe : un pointeur vers un entier pce : un pointeur constant vers un entier (qui doit etre obligatoirement initialis e, puisquil 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 aectation avec pour membre gauche pce, ec, *pec, pcec, *pcec ou tec[i] est ill egale. En r esum e, la seule vraie nouveaut e introduite ici est la notion de pointeur constant et la syntaxe de sa d eclaration type-point e *const pointeur . Signalons enn que toute cette explication reste valable en rempla cant le mot const et la notion dobjet constant par le mot volatile et le concept d objet volatil (cf. section 1.5.7). On a donc la possibilit e de d eclarer des tableaux dobjets volatils, des pointeurs volatils vers des objets, volatils ou non, etc. 5.4.3 La d eclaration typedef

Cette construction est lhomologue de la d eclaration type du langage Pascal. Elle permet de donner un nom ` a un type dans le but dall eger par la suite les expressions o` u il gure. Syntaxe : typedef d eclaration Pour d eclarer un nom de type, on fait suivre le mot r eserv e typedef dune expression identique ` a une d eclaration de variable, dans laquelle le r ole du nom de la variable est jou e par le nom du type quon veut d enir. Exemple : les d eclarations typedef float MATRICE[10][20]; ... MATRICE mat; sont equivalentes ` a float mat[10][20]; Voici un autre exemple : typedef struct noeud { int info; struct noeud *fils_gauche, *fils_droit; } NOEUD; Cette expression d eclare lidenticateur NOEUD comme le nom du type la structure noeud . Des variables a, b, c de ce type pourront par la suite etre d eclar ees aussi bien par lexpression struct noeud a, b, c; que par NOEUD a, b, c; Remarquez lutilisation di erente dun nom de structure (pr ec ed e du mot struct) et dun nom de type. Par tradition, les noms des types d enis ` a laide de la d eclaration typedef sont ecrits en majuscules. Lensemble des r` egles de syntaxe qui sappliquent aux d eclarations de variables sappliquent egalement ici. Par exemple, on peut introduire plusieurs noms de types dans un seul enonc e typedef. Ainsi, ` a la suite de
38. Notez quil ny a pas besoin dune notation sp eciale pour d eclarer un tableau constant dobjets (variables) puisque la signication ordinaire des tableaux est exactement celle-l` a.

c H. Garreta, 1988-2013

61

5.4

D eclarateurs complexes

OBJETS STRUCTURES

typedef struct noeud { int info; struct noeud *fils_gauche, *fils_droit; } NOEUD, *ARBRE; les identicateurs NOEUD et ARBRE sont des synonymes pour

struct noeud et

struct noeud * .

Dans la construction des d eclarateurs complexes, le nom dun type qui a fait lobjet dune d eclaration typedef peut etre utilis e comme type de base. Par exemple : typedef float LIGNE[20]; typedef LIGNE MATRICE[10]; est une autre mani` ere dintroduire le type MATRICE d ej` a vu. De m eme typedef void PROCEDURE(int); PROCEDURE *signal(int sig, PROCEDURE *func); est une autre mani` ere (nettement plus facile ` a lire, nest-ce pas ?) de d eclarer la fonction signal vue pr ec edemment. 5.4.4 Cas des types d esincarn es

Tout cela devient encore plus esot erique lorsquon doit construire un type sans lappliquer ` a un identicateur. Le principe est alors le suivant : construisez la d eclaration correcte dun nom quelconque X, puis eacez X. De telles expressions sont utiles dans trois circonstances : 1. Pour construire un op erateur de changement de type. Exemple : la fonction malloc alloue un bloc de m emoire de taille donn ee, dont elle renvoie ladresse. Dans les biblioth` eques du C original 39 , cette fonction est d eclar ee comme rendant ladresse dun char, et lutilisateur doit convertir le r esultat de malloc dans le type qui lint eresse. Supposons avoir besoin despace pour ranger un tableau de N r eels en double pr ecision. D eclarations 40 : char *malloc(size_t); ... double *p; Utilisation : p = (double *) malloc(N * sizeof(double)); /* D ECONSEILL E EN C ANSI */

Le type d esincarn e double * apparaissant ci-dessus a et e d eduit dune d eclaration de pointeur, double *ptr en ea cant le nom d eclar e, ptr. Rappelons quen C ANSI lexpression ci-dessus s ecrit plus simplement (voyez ` a la section 2.2.12, notamment ` a la page 26, des indications et mises en garde ` a propos de lop erateur de changement de type) : p = malloc(N * sizeof(double)); 2. Comme argument de lop erateur sizeof. Essayons par exemple d ecrire autrement laectation pr ec edente. D eclarons un tableau T de N doubles : double T[N] Supprimons T, il reste double [N] . Par cons equent, lappel de la fonction malloc montr e ci-dessus peut s ecrire aussi 41 : p = (double *) malloc(sizeof(double [N])); 3. Dans les prototypes des fonctions, en C ANSI. Par exemple, la biblioth` eque standard C comporte la fonction qsort, qui impl emente une m ethode de tri rapide dun tableau (algorithme Quicksort ) programm e en toute g en eralit e. Les arguments de cette fonction sont le tableau ` a trier, le nombre de ses el ements, la taille de chacun et une fonction qui repr esente la relation dordre utilis ee (le crit` ere de tri ) : elle re coit deux adresses d el ements du tableau et rend un nombre n egatif, nul ou positif exprimant la comparaison. La fonction qsort est donc ainsi d eclar ee dans le chier stdlib.h : void qsort(const void*, size_t, size_t, int (*)(const void*, const void*));
39. Dans la biblioth` eque ANSI (chier stdlib.h) la fonction malloc est d eclar ee comme rendant une valeur de type void * . Ce type etant compatible avec tous les autres types pointeur, le probl` eme expos e ici ne se pose pas. 40. Le type size t (d eni dans <stddef.h>) repr esente un entier sans signe 41. Lexemple montr e ici, sizeof(double [N]) , est franchement esot erique. La notation N * sizeof(double) est bien plus raisonnable.

62

c H. Garreta, 1988-2013

OBJETS STRUCTURES

5.4 D eclarateurs complexes

Note. Ce nest pas requis par la syntaxe mais, lorsquils sont bien choisis, les noms des arguments sont une aide pour le programmeur. Exemple : void qsort(const void *tableau, size_t nombre, size_t taille, int (*compare)(const void *adr1, const void *adr2));

c H. Garreta, 1988-2013

63

POINTEURS

6
6.1

Pointeurs
G en eralit es

Un pointeur est une variable dont la valeur est ladresse dune cellule de la m emoire. Les premiers langages evolu es ne permettaient pas les manipulations de pointeurs, de telles op erations etant obligatoirement conn ees dans les programmes ecrits en assembleur. Le langage Pascal a introduit les pointeurs parmi les types de donn ees disponibles dans des langages g en eralistes. Mais ils y sont pr esent es comme des entit es fort myst erieuses, dont lutilisateur nest cens e conna tre ni la structure ni le comportement. Cela nen facilite pas le maniement aux programmeurs d ebutants. Beaucoup des dicult es ` a comprendre les pointeurs proviennent plus du secret qui les entoure que de leur r eelle dicult e demploi. Un pointeur nest pas un concept unique, sans ressemblance avec les autres types de donn ees, car une adresse nest en n de compte rien dautre quun nombre entier (lindice dun el ement du tableau quest la m emoire de lordinateur). Un pointeur devrait donc supporter toutes les op erations quon peut faire subir ` a un nombre et qui gardent un sens lorsque ce nombre exprime un rang. Ce qui est tout ` a fait sp ecique dun pointeur p est la possibilit e de mentionner la variable dont la valeur de p est ladresse. Un pointeur correctement initialis e est toujours le renvoi ` a une deuxi` eme variable, la variable point ee, accessible ` a partir du contenu de p. Dans tous les langages o` u ils existent, le principal int er et des pointeurs r eside dans la possibilit e de r ealiser des structures de donn ees r ecursives (listes et arbres) : des structures dans lesquelles un champ est virtuellement aussi complexe que la structure elle-m eme. En plus de cet aspect, en C les pointeurs sont le moyen dam eliorer lacc` es aux el ements des tableaux ordinaires, en incorporant au langage des techniques dadressage indirect couramment utilis ees en assembleur. 6.1.1 D eclaration et initialisation des pointeurs

Nous avons etudi e ` a la section pr ec edente la syntaxe des d eclarations des pointeurs. Dans le cas le plus simple, cette syntaxe est type *variable Si par la suite on aecte ` a une telle variable ladresse dun certain objet, on fait r ef erence ` a ce dernier par lexpression : *variable Par exemple, lexpression char *p; d enit la variable p comme pointeur vers un char . Lorsquelle aura et e correctement initialis ee, elle contiendra ladresse dun caract` ere, auquel on pourra faire r ef erence par lexpression *p Initialisation des pointeurs. Un pointeur contient en principe une adresse valide. Il existe trois mani` eres s ures de donner une telle valeur ` a un pointeur : 1. Prendre ladresse dune variable existant par ailleurs. Exemple, d eclaration : type x, *p; Initialisation : p = &x; p contient maintenant une adresse valide, ladresse de la variable x. Par la suite, les expressions x et *p d esigneront le m eme objet (le contenu de x). 2. Provoquer lallocation dun nouvel espace, cest-` a-dire allouer dynamiquement une variable anonyme. Exemple, d eclaration : type *p; Initialisation : p = malloc(sizeof(type )); Apr` es cette instruction, une variable tout ` a fait analogue ` a la variable x de lexemple pr ec edent existe. Comme dans lexemple pr ec edent, *p en d esigne le contenu. La di erence est quici elle na pas de nom propre : 64
c H. Garreta, 1988-2013

POINTEURS

6.1

G en eralit es

si la valeur de p venait ` a etre alt er ee (sans avoir et e auparavant sauvegard ee dans un autre pointeur), la variable point ee deviendrait inaccessible et serait d enitivement perdue. 3. Obtenir une valeur par des calculs l egitimes sur des pointeurs. Par exemple, comme on le verra bient ot, avec les d eclarations type t[10], *p, *q; suivies de linitialisation p = &t[0]; lexpression q = p + 3; aecte ` a q ladresse du quatri` eme el ement du tableau t, cest-` a-dire &t[3]. Il existe aussi une mani` ere risqu ee, en tout cas non portable, de donner une valeur ` a un pointeur : lui aecter une constante ou une expression num erique, dans le style de : p = (struct machin *) 0x2A3E; De telles expressions, indispensables dans certaines fonctions de bas niveau (contr ole dorganes mat eriels, etc.), rendent non portables les programmes o` u elles gurent. Elles doivent donc etre evit ees chaque fois que cela est possible. A propos de la taille des pointeurs et celle des entiers. Une expression comme laectation ci-dessus soul` eve un autre probl` eme : existe-t-il une relation entre la taille dun nombre entier (types int et unsigned int) et celle dun pointeur ? Le langage ne prescrit rien ` a ce sujet ; il est donc parfois erron e et toujours dangereux de supposer quun entier puisse contenir un pointeur. Cette question peut sembler tr` es secondaire, lutilisation de variables enti` eres pour conserver ou transmettre des pointeurs paraissant bien marginale. Or cest ce quon fait, peut- etre sans sen rendre compte, lorsquon appelle une fonction qui renvoie un pointeur sans avoir pr ealablement d eclar e la fonction. Par exemple, supposons que sans avoir d eclar e la fonction malloc (ni inclus le chier <stdlib.h>) on ecrive laectation p = (struct machin *) malloc(sizeof(struct machin)); Le compilateur rencontrant malloc pour la premi` ere fois, il postule que cette fonction renvoie un int et ins` ere ` a cet endroit le code qui convertit un entier en un pointeur. Cette conversion peut fournir un r esultat tout ` a fait erron e. Notamment, sur un syst` eme o` u les entiers occupent deux octets et les pointeurs quatre, les deux octets hauts du r esultat de malloc seront purement et simplement remplac es par z ero. La question est dautant plus vicieuse que le programme ci-dessus se comportera correctement sur tous les syst` emes dans lesquels les entiers et les pointeurs ont la m eme taille. 6.1.2 Les pointeurs g en eriques et le pointeur NULL (ou tout simplement p = t; )

Certaines fonctions de la biblioth` eque, comme malloc, rendent comme r esultat une adresse quelconque , ` a charge pour lutilisateur de la convertir en ladresse sp ecique qui lui convient. La question est : comment d eclarer de tels pointeurs peu typ es ou pas typ es du tout ? Le C original nayant rien pr evu ` a ce sujet, la tradition s etait cr e ee de prendre le type char * pour pointeur vers nimporte quoi . En eet, sur beaucoup de syst` emes les adresses des objets de taille non unitaire supportent des contraintes dalignement (les short aux adresse paires, les long aux adresses multiples de quatre, etc.), alors que les adresses des caract` eres sont les seules ` a ne subir aucune restriction. Le C ANSI introduit un type sp ecique pour repr esenter de telles adresses g en eriques : le type void *. Ce type est compatible avec tout autre type pointeur : nimporte quel pointeur peut etre aect e` a une variable de type void * et r eciproquement 42 . Par exemple, le chier standard <stdlib.h> contient la d eclaration suivante de la fonction malloc : void *malloc(size_t size); Appel : p = malloc(sizeof(struct machin)); Note. A la section 2.2.12 (page 26) nous expliquons pourquoi lemploi dun op erateur de changement de type en association avec malloc est dangereux et d econseill e, en C ANSI.
42. Avec une protection suppl ementaire : le type void etant incompatible avec tous les autres types, lobjet point e par une variable d eclar ee de type void * ne pourra pas etre utilis e tant que la variable naura pas et e convertie en un type pointeur pr ecis.

c H. Garreta, 1988-2013

65

6.2

Les pointeurs et les tableaux

POINTEURS

Le pointeur NULL. Une des principales applications des pointeurs est la mise en uvre de structures r ecursives de taille variable : listes cha n ees de longueur inconnue, arbres de profondeur quelconque, etc. Pour r ealiser ces structures il est n ecessaire de poss eder une valeur de pointeur, distincte de toute adresse valide, repr esentant la n de liste, la structure vide, etc. C garantit quaucun objet valide ne se trouve ` a ladresse 0 et lentier 0 est compatible avec tout type pointeur. Cette valeur peut donc etre utilis ee comme la valeur conventionnelle du pointeur qui ne pointe vers rien ; cest l equivalent en C de la constante nil de Pascal. En pratique on utilise plut ot la constante NULL qui est d enie dans le chier <stddef.h> : #define NULL ((void *) 0)

6.2
6.2.1

Les pointeurs et les tableaux


Arithm etique des adresses, indirection et indexation

tique. Certaines des op Arithme erations arithm etiques d enies sur les nombres gardent un sens lorsque lun des op erandes ou les deux sont des adresses : laddition et la soustraction dun nombre entier ` a une adresse ; la soustraction de deux adresses (de types rigoureusement identiques). Lid ee de base de ces extensions est la suivante : si exp exprime ladresse dun certain objet O1 alors exp +1 exprime ladresse de lobjet de m eme type que O1 qui se trouverait dans la m emoire imm ediatement apr` es O1 et exp 1 ladresse de celui qui se trouverait imm ediatement avant. De mani` ere analogue, si exp 1 et exp 2 sont les adresses respectives de deux objets O1 et O2 de m eme type et contigus (O2 apr` es O1 ), il semble naturel de donner ` a exp 2 exp 1 la valeur 1. Larithm etique des adresses d ecoule de ces remarques. Dun point de vue technique, ajouter 1 ` a un pointeur cest le consid erer momentan ement comme un entier et lui ajouter une fois la taille de lobjet point e. On peut aussi dire cela de la mani` ere suivante : exp 1 et exp 2 etant deux expressions de type adresse dun objet de type T et n une expression enti` ere, nous avons : exp 1 + n (T *) ((unsigned long) exp 1 + nsizeof(T )) et exp2 exp1 (unsigned long)exp2 (unsigned long)exp1 sizeof(T)

Lindexation. Il d ecoule des explications pr ec edentes que larithm etique des adresses ne se justie que si on peut consid erer un pointeur p comme ladresse dun el ement t [i0 ] dun tableau, eventuellement ctif. On peut alors interpr eter p + i comme ladresse de t[i0 + i] et q p comme l ecart entre les indices correspondant ` a ces deux el ements. Les pointeurs avec leur arithm etique et lacc` es aux el ements des tableaux sont donc des concepts tr` es voisins. En r ealit e il y a bien plus quun simple lien de voisinage entre ces deux concepts, car lun est la d enition de lautre : Soit exp une expression quelconque de type adresse et ind une expression enti` ere. Par d enition, lexpression : *(exp + ind ) se note : exp [ind ] On en d eduit que si on a lune ou lautre des d eclarations type t[100]; ou type *t; alors dans un cas comme dans lautre on peut ecrire indi eremment 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 des notations. Exemple. Le programme suivant parcourt un tableau en utilisant un pointeur : 66
c H. Garreta, 1988-2013

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 ex ecutable des fonctions. Mais il subsiste des di erences, surtout dans les d eclarations. Il ne faut pas oublier que les d eclarations int *p, t[100]; introduisent p comme une variable pointeur et t comme une constante pointeur. Ainsi, les expressions p = t et p++ sont l egitimes, mais t = p et t++ sont ill egales, car elles tentent de modier une non variable (un nom de tableau nest pas une lvalue ). Remarque 2. Dans ce m eme esprit, rappelons la di erence quil peut y avoir entre les deux d eclarations suivantes : char s[] = "Bonsoir"; et char *t = "Bonsoir"; s est (ladresse d) un tableau de caract` eres (gure 8)

s B o n s o i r\ 0
Figure 8 Cha ne de caract` eres

variable

tandis que t est ladresse dun (tableau de) caract` ere(s) (gure 9)

t o n s o i r \0 B o n s o i r \ 0A u revo

Figure 9 Constante cha ne de caract` eres

On pourra r ef erencer ces caract` eres indi eremment par s[i], *(s + i), t[i] ou *(t + i). Cependant, dans le premier cas le compilateur a allou e un vrai tableau de 8 caract` eres, et y a copi e la cha ne donn ee ; dans le second cas, la cha ne a et e rang ee avec les autres constantes litt erales et le compilateur na allou e quune variable de type pointeur dans laquelle il a rang e ladresse de cette constante. Par suite, s nest pas une variable mais *s, s[i] et *(s + i) en sont, tandis que t est une variable mais *t, t[i] et *(t + i) nen sont pas 43 . A ce propos voir aussi la section 5.1.3. Larithm etique des adresses permet dam eliorer lecacit e de certains traitements de tableaux 44 , en mettant ` a prot le fait que lindirection est plus rapide que lindexation. Autrement dit, lacc` es ` a une donn ee ` a travers une expression de la forme *p est r eput ee (l eg` erement) plus rapide que lacc` es ` a la m eme donn ee ` a travers lexpression *(q + i) (ou, ce qui est la m eme chose, q[i]) D eveloppons un exemple classique : la fonction strcpy(dest, srce) copie la cha ne de caract` eres srce dans lespace point e par dest. Premi` ere forme :
43. Cest uniquement dun point de vue technique que ces expressions ne sont pas des variables car du point de vue de la syntaxe elles sont des lvalue tout ` a fait l egitimes et le compilateur ne nous interdira pas de les faire appara tre ` a gauche dune aectation. 44. Bien s ur, il sagit de petites economies dont le b en ece ne se fait sentir que lorsquelles sappliquent ` a des parties des programmes qui sont vraiment critiques, cest-` a-dire ex ecut ees de mani` ere tr` es fr equente.

c H. Garreta, 1988-2013

67

6.2

Les pointeurs et les tableaux

POINTEURS

char *strcpy(char dest[], char srce[]) { register int i = 0; while ((dest[i] = srce[i]) != \0) i++; return dest; } Deuxi` eme forme : char *strcpy(char *dest, char *srce) { register char *d = dest, *s = srce; while ((*d++ = *s++) != \0) ; return dest; } La deuxi` eme forme est plus ecace, car on y a supprim e lindexation. Pour sen convaincre, il sut de consid erer que la boucle qui gure dans la premi` ere 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 r ealiser des tableaux dynamiques, cest-` a-dire des tableaux dont la taille nest pas connue au moment de la compilation mais uniquement lors de lex ecution, lorsque le tableau commence ` a exister. Pour cela il sut de remplacer la d eclaration du tableau par celle dun pointeur ; allouer lespace ` a lex ecution, avant toute utilisation du tableau, par un appel de malloc ; dans le cas dun tableau local, lib erer lespace ` a la n de lutilisation. Pour tout le reste, lutilisation de ces tableaux dynamiques est identique ` a celle des tableaux normaux (ce qui, pour le programmeur, est une tr` es bonne nouvelle !). Exemple : la fonction ci-dessous calcule dans un tableau la N eme ligne du triangle de Pascal 45 et sen sert dans des calculs auxquels nous ne nous int eressons pas ici. On notera quune seule ligne di` ere 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]; for (n = 0; n <= N; tab[0] = tab[n] for (p = n - 1; tab[p] += +

/* N_MAX est une constante connue ` a la compilation */

n++) { = 1; p > 0; p--) tab[p - 1]; /* exploitation de tab ; par exemple, achage : */ 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;


45. Rappelons que les coecients de la neme ligne du triangle de Pascal sont d enis r ecursivement ` a partir de ceux de la n 1eme p p1 p ligne par : Cn = Cn + C . 1 n1

68

c H. Garreta, 1988-2013

POINTEURS

6.2 Les pointeurs et les tableaux

for (n = 0; n <= N; tab[0] = tab[n] for (p = n - 1; tab[p] += +

n++) { = 1; p > 0; p--) tab[p - 1]; /* exploitation de tab ; par exemple, achage : */ for (i = 0; i <= n; i++) printf("%5d", tab[i]); printf("\n"); /* ` a ne pas oublier ( tab est une variable locale) */

} free(tab); }

Remarque. Lemploi de tableaux dynamiques ` a plusieurs indices est beaucoup plus compliqu e que celui des tableaux ` a un indice comme ci-dessus, car il faut g erer soi-m eme les calculs dindices, comme le montre la section suivante. 6.2.3 Tableaux multidimensionnels

flexions sur la double indexation. Supposons que nous ayons ` Re a ecrire un programme qui op` ere sur des matrices ` a N L lignes et N C colonnes. La d eclaration dune telle matrice est facile ` a construire (cf. section 5.4.1) : tableau de NL tableau de NC float tableau de NC float float m1 (m1)[NL] ou m1[NL] (m1[NL])[NC] ou m1[NL][NC]

Un el ement particulier de cette matrice sera not e m1[i][j]. Dapr` es ce que nous savons d ej` a, on peut linterpr eter de la mani` ere suivante 46 : m1[i][j] = *(m1[i] + j) = *(float *)((unsigned int) m1[i] + j sizeof(float)) Le probl` eme est : avec la d eclaration que nous avons donn ee, que signie m1[i] ? Il est int eressant de constater que cette expression nest pas une lvalue, puisquelle est de type tableau de... , mais que, ` a part cela, elle est trait ee comme un el ement dun tableau ordinaire. Autrement dit, si on supposait avoir la d eclaration typedef float LIGNE[NC]; /* LIGNE = tableau de NC float */ alors m1[i] serait 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 reste m1[i][j] = *(m1 + (i NC + j) sizeof(float)) Nous retrouvons lexpression classique i NC + j caract erisant lacc` es ` a un el ement mi,j dune matrice N LN C . Cette expression traduit la disposition par lignes des tableaux rectangulaires, comme le montre la gure 10.

m1 i x NC j

m1[i][j]
Figure 10 Tableau bidimensionnel statique Etudions une autre mani` ere dacc eder aux el ements de cette m eme matrice. Soient les d eclarations
46. Pour all eger les formules, nous supposons ici que la taille dun int, et donc celle dun unsigned, sont egales ` a celle dun pointeur.

c H. Garreta, 1988-2013

69

6.2

Les pointeurs et les tableaux

POINTEURS

float m1[NL][NC], *m2[NL]; La deuxi` eme se lit, compte tenu des priorit es : float *(m2[NL]). La variable m2 est donc d eclar ee comme un tableau de NL pointeurs vers des nombres ottants. Pour r ealiser la matrice dont nous avons besoin, il nous sut dinitialiser correctement ces pointeurs : chacun sera ladresse dune ligne de la matrice pr ec edente. Nous aurons la conguration de la gure 11.

m2 i m2[i]

m2[i][j]
Figure 11 Tableau bidimensionnel r ealis e avec un tableau de pointeurs Il est remarquable quun el ement de la nouvelle m2[i][j] mais maintenant, cette expression se traduira (en continuant ` a n egliger 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 r ealis ees de mani` ere tr` es rapide par les calculateurs (ce sont des multiplications par des puissances de 2), nous pouvons les ignorer. Il reste que nous avons remplac e m1 + i NC + j par *(m2 + i) + j . Nous avons donc bien elimin e une vraie multiplication : notre deuxi` eme mani` ere est plus ecace. En contrepartie nous occupons un peu plus despace (les NL pointeurs suppl ementaires). De plus, il nous faut initialiser le tableau de pointeurs m2. La matrice m1 existant par ailleurs, cela se fait ici par une expression dune etonnante simplicit e: for (i = 0; i < NL; i++) m2[i] = m1[i]; matrice ainsi d eclar ee se note encore

6.2.4

Tableaux multidimensionnels dynamiques

Les consid erations pr ec edentes montrent pourquoi dans la r ealisation dune matrice dynamique on devra renoncer au m ecanisme de double indexation ordinaire. En eet, NC doit etre connu ` a la compilation pour que lexpression i NC + j ait un sens. Or nous sommes maintenant parfaitement equip es pour r ealiser une telle matrice. Supposons que la fonction void unCalcul(int nl, int nc) impl emente un algorithme qui requiert une matrice 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 esp erant que nl NL et nc NC */ int i, j;

70

c H. Garreta, 1988-2013

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 : void unCalcul(int nl, int nc) { double **mat; int i, j; /* initialisation des pointeurs */ mat = malloc(nl * sizeof(double *)); if (mat == NULL) return 0; for (i = 0; i < nl; i++) { mat[i] = malloc(nc * sizeof(double)); if (mat[i] == NULL) { for (j = 0; j < i; j++) free(mat[j]); free(mat); } } /* utilisation de la matrice mat. Par exemple : */ for (i = 0; i < nl; i++) for (j = 0; j < nc; j++) mat[i][j] = 0; etc. /* lib eration (indispensable dans le cas dune variablelocale) */ for (j = 0; j < nl; j++) free(mat[j]); free(mat); } Dans cette mani` ere de proc eder, les lignes de la matrice sont allou ees ` a loccasion de nl appels distincts de malloc. La matrice est r ealis ee par des morceaux de m emoire eparpill ee, comme le montre la gure 12.

m2

Figure 12 Matrice dynamique (lignes allou ees s epar ement)

s. Une l Variante : lignes contigue eg` ere variante consiste ` a allouer dun seul coup toute la m emoire n ecessaire aux lignes de la matrice 47 . La structure de la matrice ressemble alors ` a celle de la gure 11 :
47. Notez que les expressions sizeof *mat et sizeof *mat[0] equivalent respectivement ` a sizeof(double) . Elles ont lavantage d etre ind ependantes du type eectif de mat.

sizeof(double *) et

c H. Garreta, 1988-2013

71

6.2

Les pointeurs et les tableaux

POINTEURS

int unCalcul(int nl, int nc) { double **mat; int i, j; /* initialisation des pointeurs */ mat = malloc(nl * sizeof *mat); if (mat == NULL) return 0; /* echec */ mat[0] = malloc(nl * nc * sizeof *mat[0]); if (mat[0] == NULL) { free(mat); return 0; /* echec */ } for (i = 1; i < nl; i++) mat[i] = mat[i - 1] + nc; /* utilisation de la matrice mat. Par exemple : */ for (i = 0; i < nl; i++) for (j = 0; j < nc; j++) mat[i][j] = 0; etc. /* lib eration (indispensable dans le cas dune variablelocale) */ free(mat[0]); free(mat); return 1; }

/* succ` es */

longueur maximum d'une chane B o n s o i r \0 \0 C a v a ? \0 Ce n' e st B y e \0

pa s

un

texte

g n i a l !\ 0

Figure 13 Matrice de caract` eres

6.2.5

Tableaux de cha nes de caract` eres

Le remplacement dun tableau ` a deux indices par un tableau de pointeurs se r ev` ele encore plus utile dans le traitement des tableaux de cha nes de caract` eres. Ici, cette technique entra ne une economie de place substantielle, puisquelle permet de remplacer un tableau rectangulaire dont la largeur est d etermin ee par la plus grande longueur possible dune cha ne (avec un espace inoccup e tr` es important) comme celui de la gure 13, par un espace lin eaire dans lequel chaque cha ne noccupe que la place qui lui est n ecessaire, comme le montre la gure 14.

B o n s o i r\ 0\0 C a

v a ?\ 0B y e\ 0C e

n ' e s ...

encombrement rel des caractres


Figure 14 Tableau de cha nes de caract` eres Nous allons illustrer cette m ethodologie par un exemple tr` es simple mais tout ` a fait typique. Notre programme 72
c H. Garreta, 1988-2013

POINTEURS

6.2 Les pointeurs et les tableaux

lit des lignes de caract` eres au terminal et les range dans une table, jusqu` a la rencontre de la n de chier ou la lecture dune ligne vide. Ensuite, il ordonne les lignes lues ; nalement il ache les lignes ordonn ees. Les lignes lues sont rang ees les unes ` a la suite des autres dans un tableau unique, nomm e espace, dont le pointeur libre indique la premi` ere place disponible. La table des mots, not ee mots, est une table de renvois dans espace. La gure 14 illustre ces structures de donn ees. Notez que le tri se fait en ordonnant la table des pointeurs ; les caract` eres eux-m emes ne sont nullement d eplac es. Les fonctions gets (lecture de cha ne), puts ( ecriture de cha ne), strcmp (comparaison de cha nes) et strlen (longueur dune cha ne) appartiennent ` a la biblioth` eque standard et sont expliqu ees dans les sections suivantes. #include <stdio.h> #include <string.h> #define MAXMOTS 100 #define MAXCARS 3000 char char char int espace[MAXCARS]; *libre = espace; *mots[MAXMOTS]; 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 quune structure tout ` a fait analogue ` a notre table mots est cr e ee par le compilateur lorsquon initialise un tableau de cha nes de caract` eres, comme dans : char *messages[] = { "Fichier inexistant", "Volume ou repertoire inexistant", "Nom de fichier incorrect", "Erreur physique dentree-sortie", "Autre erreur" }; La seule di erence entre notre tableau mots et le tableau messages que cr eerait le compilateur par suite de la d eclaration ci-dessus est la suivante : les el ements de mots sont des pointeurs vers un espace modiable faisant partie de la m emoire attribu ee pour les variables de notre programme, tandis que messages est fait de pointeurs vers des cellules de la zone de constantes , espace immodiable allou e et garni une fois pour toutes par le compilateur.
c H. Garreta, 1988-2013

73

6.2

Les pointeurs et les tableaux

POINTEURS

6.2.6

Tableaux multidimensionnels formels

Nous avons vu que si un argument formel dune fonction ou une variable externe est un tableau ` a un seul indice, alors lindication du nombre de ses el ements est sans utilit e puisquil ny a pas dallocation despace. Mais que se passe-t-il pour les tableaux ` a plusieurs indices ? Si un tableau a et e d eclar e T table[N1 ][N2 ] ... [Nk1 ][Nk ]; (N1 , N2 , etc. sont des constantes) alors un acc` es comme table[i1 ][i2 ] ... [ik1 ][ik ] se traduit par *( (T *)table + i1 N2 N3 Nk + + ik1 Nk + ik ) Quil y ait ou non allocation despace, il faut que ce calcul puisse etre eectu e. Do` u la r` egle g en erale : lorsquun argument formel dune fonction est un tableau ` a plusieurs indices, la connaissance de la premi` ere dimension d eclar ee est sans utilit e mais les autres dimensions d eclar ees doivent etre connues du compilateur. Pour un tableau ` a deux indices, cela donne : il est inutile dindiquer combien il y a de lignes, mais il faut absolument que, en compilant la fonction, le compilateur connaisse la taille d eclar ee pour chaque ligne. Par exemple, la fonction suivante eectue la multiplication par un scalaire k de la matrice m d eclar ee NL NC (deux constantes connues dans ce chier) : 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 consist e` a faire soi-m eme les calculs dindice : 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 quon y gagne est un programme bien plus dicile ` a lire !

6.2.7

Tableaux non n ecessairement index es ` a partir de z ero

En principe, les tableaux de C sont index es ` a partir de z ero : sans quil soit n ecessaire de le sp ecier, un tableau T d eclar e de taille N est fait des el ements T0 , T1 . . . TN 1 . Nous avons vu que, dans la grande majorit e des applications, cette convention est commode et able. Il existe cependant des situations dans lesquelles on souhaiterait pouvoir noter les el ements dun tableau T1 , T2 . . . TN ou, plus g en eralement, Ta , Ta+1 . . . Tb , a et b etant deux entiers quelconques v eriant a b. Nous allons voir que lallocation dynamique fournit un moyen simple et pratique 48 pour utiliser de tels tableaux. ` un indice. La fonction dTable 49 cr Cas des tableaux a ee un tableau d el ements de type double dont les indices sont les entiers iMin, iMin+1, . . . iMax :
c 48. Nous expliquons ici une technique massivement employ ee dans la biblioth` eque de calcul scientique Numerical Recipes ( Numerical Recipes Software) bas ee sur le livre Numerical Recipes in C (W. Press, S. Teukolsky, W. Vetterling et B. Flannery, Cambridge University Press, 1992). 49. Strictement parlant, la norme du langage C ne garantit pas que la technique expos ee ici fonctionne correctement sur tous les syst` emes. Dobscures contraintes techniques font que sur certains mat eriels ou syst` emes larithm etique des adresses subit des limitations drastiques et, notamment, rendent le r esultat de la soustraction dun entier ` a un pointeur peut etre incorrect ou inutilisable.

74

c H. Garreta, 1988-2013

POINTEURS

6.2 Les pointeurs et les tableaux

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 n ecessaire par le fait quun r esultat NULL ne sut pas ici ` a caract eriser l echec de lallocation ; en eet, res - iMin peut etre nul alors que lallocation a r eussi et res = NULL. La gure 15 illustre le r esultat rendu par la fonction pr ec edente lors dun appel comme dTable(5, 10).

adresse rendue comme rsultat par dTable

cellules effectivement alloues


Figure 15 Adresse rendue par la fonction dTable 0n acc` ede aux el ements dun tel tableau ` a laide dun indice compris entre les valeurs indiqu ees mais, ` a part cela, de mani` ere tout ` a fait habituelle ; en particulier, sans aucune perte decacit e. Exemple dutilisation : ... 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 cr e e par un appel de la fonction dTable est dynamiquement allou e, cependant sa lib eration est un peu plus complexe que pour les tableaux dynamiques ordinaires. En particulier, ` a la suite du programme ci-dessus, un appel comme free(t); g en erera certainement une erreur ` a lex ecution. Il aurait fallu d enir une structure plus complexe quun simple tableau, incluant soit la valeur de iMin, soit celle de res (actuellement des variables locales de la fonction dTable) pour g erer correctement la lib eration ult erieure dun tel tableau. ` plusieurs indices. Les remarques pr Cas des tableaux a ec edentes, avec celles faites ` a la section 6.2.4, sugg` erent une mani` ere de mettre en uvre des matrices dont les lignes et les colonnes ne sont pas index ees ` a partir de z ero. La fonction dMatrice cr ee une matrice dont les lignes sont index ees par les entiers lMin, lMin+1 . . . lMax et les colonnes sont index ees 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; }

c H. Garreta, 1988-2013

75

6.2

Les pointeurs et les tableaux

POINTEURS

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 pr ec edemment, lemploi de ces matrices ne saccompagne daucune perte decacit e. Voici un exemple dutilisation : ... 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.2.8

Matrices non dynamiques de taille inconnue

Un dernier cas qui se pr esente parfois est celui o` u on doit ecrire une fonction op erant sur des matrices ordinaires (c.-` a-d. non dynamiques) alors que leurs dimensions ne sont pas connues lors de la compilation. Par exemple, voici la fonction qui eectue le produit de deux matrices A et B en d eposant le r esultat dans une troisi` eme matrice C. Les arguments nla et nca repr esentent le nombre eectif de lignes et de colonnes de A, tandis que ncb est le nombre eectif de colonnes de B. Les arguments dNca, dNcb et dNcc repr esentent les nombres de colonnes avec lesquels les matrices A, B et C ont et e respectivement d eclar es . void produitMatrices(double *A, double *B, double *C, int nla, int nca, int ncb, int dNca, int dNcb, int dNcc) { int i, j, k; double s; for (i = 0; i < nla; i++) for (j = 0; j < ncb; j++) { s = 0; for (k = 0; k < nca; k++) s += A[i * dNca + k] * B[k * dNcb + j]; C[i * dNcc + j] = s; } } Exemple dappel (NLA, NCA, etc., sont des constantes introduites par des #define, tandis que nla, nca, etc. sont des variables) : 76
c H. Garreta, 1988-2013

/*1*/ /*1*/

POINTEURS

6.3

Les adresses des fonctions

double A[NLA][NCA], B[NLB][NCB], C[NLC][NCC]; ... obtention des valeurs des variables nla, nca, etc., et des matrices A et B ... produitMatrices(&A[0][0], &B[0][0], &C[0][0], nla, nca, ncb, NCA, NCB, NCC); ... On notera que la fonction produitMatrices peut etre optimis ee en supprimant des multiplications qui sont faites dans la boucle la plus profonde (la seule qui doit nous int eresser, du point de vue de lecacit e). Pour cela, il sut de remplacer les deux lignes marqu ees /*1*/ par ceci (ia et ib sont des variables locales nouvelles) : ... ia = i * dNca; ib = j; for (k = 0; k < nca; k++) { s += A[ia] * B[ib]; ia += 1; ib += dNcb; } ...

/*2*/ /*2*/ /*2*/ /*2*/ /*2*/ /*2*/ /*2*/

6.3
6.3.1

Les adresses des fonctions


Les fonctions et leurs adresses

En etudiant les d eclarateurs complexes (section 5.4) nous avons appris que la syntaxe de C permettait de manipuler le type fonction rendant un [objet de type] T (T repr esente un autre type), cette p eriphrase constituant elle-m eme un d eclarateur que le programmeur peut composer librement avec les autres d eclarateurs, adresse dun [objet de type] T et tableau de [objets de type] T . Tout cela sugg` ere quen C on aura le droit de travailler avec des variables qui repr esentent des fonctions, des tableaux de fonctions, des fonctions renvoyant comme r esultat dautres fonctions, etc. Quen est-il exactement ? Le concept le plus simple quon peut souhaiter dans ce domaine est celui de variable dont la valeur est une fonction. Il va falloir y renoncer, pour une raison facile ` a comprendre : la taille dune fonction ne peut pas se d eduire de son type 50 . Cest un inconv enient r edhibitoire, car toute d enition de variable doit entra ner la r eservation dun espace m emoire pour en contenir la valeur, ce qui demande de conna tre combien doctets sont n ecessaires. En revanche, le langage C permet dutiliser un concept ` a peine plus complexe que le pr ec edent : celui de variable dont la valeur est ladresse dune fonction. Il ny a plus de probl` eme de taille (la taille dune adresse ou pointeur est constante), et la syntaxe pour d eclarer une telle variable a d ej` a et e expliqu ee dans la section sur les d eclarateurs complexes (cf. section 5.4). Exemple : double (*uneFonction)(int n, double x); Cet enonc e d eclare uneFonction comme une variable (un pointeur) destin ee ` a contenir des adresses de fonctions ayant deux arguments, un int et un double, et rendant une valeur double. Cette d eclaration ninitialise pas uneFonction (sauf, si cest une variable globale, avec la valeur 0, peu utile ici) et, ` a part le type des arguments et du r esultat, ne laisse rien deviner sur les caract eristiques des fonctions dont uneFonction contiendra les adresses. Si, par la suite, uneFonction re coit ladresse dune fonction pour valeur, alors lexpression suivante est correcte et repr esente un appel de (en supposant k de type int et u et v de type double) : u = (*uneFonction)(k, v);
50. Notez que, malgr e les apparences, il nest pas d elirant de rapprocher les fonctions et les donn ees. Une fois compil ee (traduite en langage machine), une fonction est une suite doctets qui occupe une portion de m emoire exactement comme, par exemple, un tableau de nombres. La principale di erence est dans la possibilit e ou limpossibilit e de pr edire le nombre doctets occup es. Pour un tableau, par exemple, ce nombre d ecoule sans ambigu t e de la d eclaration. Pour une fonction il ne d epend pas de la d eclaration (qui ne pr ecise que le type du r esultat et le nombre et les types des arguments) mais du nombre dinstructions qui composent la fonction, cest-` a-dire de ce quelle fait et comment elle le fait.

c H. Garreta, 1988-2013

77

6.3

Les adresses des fonctions

POINTEURS

Comment le programmeur obtient-il des adresses de fonctions ? Il faut savoir que, de la m eme mani` ere que le nom dun tableau repr esente son adresse de base, le nom dune fonction repr esente ladresse de celle-ci. Ainsi, si on a la d enition de fonction double maFonction(int nbr, double val) { etc. } alors lexpression maFonction : poss` ede le type adresse dune fonction ayant pour arguments un int et un double et rendant un double ; a pour valeur ladresse o` u commence la fonction maFonction ; nest pas une lvalue. Par cons equent, laectation suivante est tout ` a fait l egitime : uneFonction = maFonction; A partir de cette aectation (et tant quon aura pas chang e la valeur de uneFonction) les appels de (*uneFonction) seront en fait des appels de maFonction. Examinons deux applications de cela parmi les plus importantes. 6.3.2 Fonctions formelles

Le probl` eme des fonctions formelles, cest-` a-dire des fonctions arguments dautres fonctions, est r esolu en C par lutilisation darguments de type pointeur vers une fonction . solution de f (x) = 0 par dichotomie. Si f est une fonction continue, d Exemple 1 : re enie sur un intervalle [ a . . . b ] telle que f (a) et f (b) ne sont pas de m eme signe, la fonction dicho recherche x [ a . . . b ] telle quil existe x0 [ a . . . b ] v eriant |x x0 | et f (x0 ) = 0. En clair : dicho d etermine x , qui est une solution de l equation f (x) = 0 si on tol` ere une erreur dau plus . Les arguments de la fonction dicho sont : les bornes a et b de lintervalle de recherche, la pr ecision voulue et surtout la fonction f dont on cherche un z ero. Voici cette fonction : double dicho(double a, double b, double eps, double (*f)(double)) { double x; /* hypoth` ese : 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; } Largument f est d eclar e comme un pointeur vers une fonction ; par cons equent *f est une fonction et (*f)(x) ec edemment d enie, changeant de signe un appel de cette fonction. Si une fonction est le nom dune fonction pr sur lintervalle [0, 1], un appel correct de dicho serait x = dicho(0.0, 1.0, 1E-8, une_fonction); Autre exemple du m eme tonneau y = dicho(0.0, 3.0, 1E-10, cos); Remarque. En syntaxe originale, ou sans proptotypes , la fonction dicho s ecrirait double dicho(a, b, eps, f) double a, b, eps, (*f)(); { etc. (la suite est la m eme) } Exemple 2 : utilisation de qsort. Reprenons notre programme de la section 6.2.5 (tri dun tableau de cha nes de caract` eres) en rempla cant la fonction de tri na f par la fonction qsort de la biblioth` eque standard. 78
c H. Garreta, 1988-2013

POINTEURS

6.3

Les adresses des fonctions

Cette fonction est expliqu ee ` a la section 8.4.2, elle trie un tableau par rapport ` a un crit` ere quelconque. Si elle nous int eresse ici cest parce quelle prend ce crit` ere, repr esent e par une 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 d eclaration 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 d eclar es "void *" (voyez la */ /* d eclaration de qsort). En r ealit e, dans les appels de cette fonction que */ /* nous faisons ici, a et b sont des adresses de "cha^ nes", cest-` a-dire des */ /* adresses dadresses 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]); } Remarque. La d enition de comparer peut para tre bien compliqu ee. Pour la comprendre, il faut consid erer les diverses contraintes auxquelles elle doit satisfaire : comparer gurant comme argument dans lappel de qsort, son prototype doit correspondre exactement 51 a celui indiqu ` e dans le prototype de qsort ; a et b, les arguments formels de comparer, etant de type void *, on ne peut acc eder aux objets quils pointent sans leur donner auparavant (` a laide de lop erateur de conversion de type) un type pointeur utilisable ; dapr` es la documentation de qsort, nous savons que comparer sera appel ee avec deux adresses de cha nes pour arguments eectifs ; une cha ne etant de type char *, le type adresse de cha ne est char **, ce qui explique la fa con dont a et b apparaissent dans lappel de strcmp. 6.3.3 Tableaux de fonctions

Le programme suivant montre lutilisation dun tableau dadresses de fonctions : chaque el ement du tableau table est form e de deux champs : ne de caract` eres ; le nom dune fonction standard, sous forme de cha ladresse de la fonction correspondante. Notre programme lit des lignes constitu ees dun nom de fonction, suivi dun ou plusieurs blancs, suivis dun nombre r eel ; il evalue et ache alors la valeur de la fonction mentionn ee appliqu ee au nombre donn e. La frappe dune ligne r eduite au texte n arr ete le programme. #include <stdio.h> double sin(double), cos(double), exp(double), log(double);
51. En syntaxe sans prototype , aucun contr ole nest fait sur le type des arguments de comparer, et toute cette question est bien plus simple. Mais attention : quelle que soit la syntaxe employ ee, on ne peut pas ignorer le fait que comparer sera appel ee (par qsort) avec pour arguments eectifs les adresses des cha nes ` a comparer.

c H. Garreta, 1988-2013

79

6.3

Les adresses des fonctions

POINTEURS

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 ex ecution de ce programme : sin 1 0.841471 cos 1 0.540302 atn 1 atn ??? fin Remarque. La d eclaration du tableau table pr esente une caract eristique peu fr equente : des fonctions sont r ef erenc ees mais elles ne sont pas en m eme temps appel ees. En eet, les noms des fonctions sin, cos, exp, etc. apparaissent dans cette d eclaration sans etre accompagn es dune paire de parenth` eses ; il ny a aucun signe indiquant au compilateur quil sagit de fonctions. Cest pourquoi ce programme sera trouv e erron e par le compilateur ` a moins quon lui donne les d eclarations externes des fonctions, soit explicitement : double sin(double), cos(double), exp(double), log(double); soit en incluant un chier dans lequel sont ecrites ces d eclarations : #include <math.h>

6.3.4

Flou artistique

A ce point de notre expos e vous etes en droit de trouver que les r oles respectifs des objets de type fonction... et des objets de type adresse dune fonction... sont confus. Examinons une fonction bien ordinaire, sinus. La d eclaration de cette fonction, extraite du chier <math.h>, est : double sin(double); Dautre part, la d enition dune variable de type adresse dune fonction double ` a un argument double est : double (*f)(double); Voici linitialisation de f par ladresse de la fonction sin et deux appels equivalents de cette fonction (x, y et z sont des variables double) : 80
c H. Garreta, 1988-2013

POINTEURS

6.4 Structures r ecursives

f = sin; y = (*f)(x); z = sin(x); Quelle que soit la valeur de x, y et z re coivent la m eme valeur. Pourtant, les trois lignes ci-dessus ne sont pas coh erentes. La premi` ere montre que f et sin sont de m eme type (` a savoir adresse dune fonction... ) ; or f doit etre d er ef erenc e pour appeler la fonction, tandis que sin na pas besoin de l etre. On peut comprendre la raison de cette incoh erence. Apr` es avoir constat e limpossibilit e de d eclarer des variables de type fonction... , car une fonction na pas de taille d enie, et donc lobligation de passer par des variables adresse de fonction... , on a voulu faire cohabiter la rigueur (un pointeur doit etre d er ef erenc e pour acc eder ` a lobjet quil pointe) et la tradition (le sinus de x sest toujours ecrit sin(x)). Cela a conduit ` a d enir lop erateur dappel de fonction aussi bien pour une expression de type fonction... que pour une expression de type adresse dune fonction... . Ainsi, les deux expressions (*f)(x) et sin(x) sont correctes ; lexp erience montre que cette tol erance na pas de cons equences n efastes. Il nen d ecoule pas moins que les deux expressions u = f(x); v = (*sin)(x); sont accept ees elles aussi (elles ont la m eme valeur que les pr ec edentes).

6.4
6.4.1

Structures r ecursives
D eclaration

Les structures r ecursives font r ef erence ` a elles-m emes. Elles sont principalement utilis ees pour implanter des structures de donn ees dont la d enition formelle est un enonc e r ecursif. Parmi les plus fr equemment rencontr ees : les listes et les arbres, comme les arbres binaires. Ainsi, par exemple, on peut donner la d enition r ecursive suivante : un arbre binaire est soit un symbole conventionnel signiant la structure vide ; soit un triplet ( valeur , ls g , ls d ) constitu e dune information dont la nature d epend du probl` eme etudi e et de deux descendants qui sont des arbres binaires. Bien entendu, du point de vue technique il nest pas possible de d eclarer un champ dune structure comme ayant le m eme type que la structure elle-m eme (une telle structure serait ipso facto de taille innie !). On contourne cette dicult e en utilisant les pointeurs. Un arbre binaire est repr esent e par une adresse, qui est : soit ladresse nulle ; soit ladresse dune structure constitu ee de trois champs : une information et deux descendants qui sont des adresses darbres binaires. Typiquement, la d eclaration dune telle structure sera donc calqu ee 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 point es par les champs fils gauche et fils droit.

6.4.2

Exemple

Le programme suivant eectue le m eme travail que celui du 6.2.5, mais ` a la place dune table on utilise un arbre binaire de recherche (voir, par exemple, R. Sedgewick, Algorithmes en langage C, chapitre 14) pour ranger les mots. La structure de donn ees mise en uvre est repr esent ee sur la gure 16. #include <stdlib.h> #include <stdio.h> #define MAXCARS 3000 char espace[MAXCARS], *libre = espace;
c H. Garreta, 1988-2013

81

6.4

Structures r ecursives

POINTEURS

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; }

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); else r->fils_droit = insertion(r->fils_droit, mot); return r; }

libre

j e a n \0 a n d r \0 p a u l \ 0
racine mot fils gauche fils droit

...

...

...

...

Figure 16 Arbre binaire de mots

void parcours(POINTEUR r) { if (r != NULL) { parcours(r->fils_gauche); printf("%s\n", r->mot); parcours(r->fils_droit); } } 82


c H. Garreta, 1988-2013

...

POINTEURS

6.4 Structures r ecursives

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 r ecursives

Donnons-nous le probl` eme suivant : repr esenter une liste de familles ; chaque famille est une liste dindividus , avec la particularit e que chaque individu fait r ef erence ` a sa famille. Les structures famille et individu se pointent mutuellement, chacune intervenant dans la d enition de lautre. Comment les d eclarer ?

... ...
Liste de familles Liste d'individus

...

...

Figure 17 Structures mutuellement r ecursives

Pour r esoudre ce probl` eme le compilateur C tol` ere quon d eclare un pointeur vers une structure non encore d enie. Plus pr ecis ement, si la structure ayant le nom indiqu e na pas encore et e d eclar ee, lexpression struct nom est l egitime. Elle identie un type incomplet et on peut lutiliser ` a tout endroit o` u sa taille nest pas requise, notamment dans la d eclaration dun pointeur vers une telle structure. Cela nous permet de d eclarer nos structures mutuellement r ecursives sous la forme : 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 et e:
c H. Garreta, 1988-2013

...

83

6.4

Structures r ecursives

POINTEURS

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; };

84

c H. Garreta, 1988-2013

ENTREES-SORTIES

Entr ees-sorties

Strictement parlant, les op erations dentr ee-sortie ne font pas partie du langage C ; elles sont eectu ees par des fonctions de la biblioth` eque que chaque syst` eme ore avec le compilateur. Or, C est n e et a v ecu longtemps en milieu UNIX avant d etre transport e ailleurs ; la biblioth` eque UNIX a donc souvent et e prise pour r ef erence, semant quelque confusion car la r ealisation de certaines fonctions dUNIX est inutile, voire impossible sur certains syst` emes. La norme ANSI y a mis bon ordre ; en principe la liste des fonctions standard est maintenant clairement etablie et un programme qui nutilise que ces fonctions doit pouvoir etre transport e dun syst` eme ` a un autre sans modication. Rien nemp eche que telle ou telle r ealisation particuli` ere du langage propose des fonctions additionnelles ; il faudra alors vous r ef erer ` a la documentation sp ecique.

7.1

Flots

Quelles que soient les particularit es du syst` eme dexploitation sous-jacent, les fonctions de la biblioth` eque standard des entr ees-sorties font en sorte que les chiers soient vus par le programmeur comme des ots, cest` a-dire des suites doctets qui repr esentent des donn ees selon une des deux modalit es suivantes : soit le chier contient les caract` eres qui constituent lexpression ecrite des donn ees en question dapr` es une certaine syntaxe. Ces caract` eres sont eux-m emes repr esent es selon une convention largement r epandue, presque toujours le code ASCII. De tels chiers sont appel es chiers de texte. Leur principale qualit e est d etre exploitables par un logiciel, un syst` eme ou un equipement di erents de celui qui les a produits ; en particulier, ils peuvent etre ach es, edit es, imprim es, etc. ; ees enregistr ees sous une forme qui est la copie exacte de leur codage dans soit le chier contient des donn la m emoire de lordinateur. On lappelle alors chier binaire. Les op erations de lecture ou d ecriture sur de tels chiers sont tr` es rapides, car elles ne requi` erent pas un travail danalyse ou de synth` ese de lexpression ecrite des donn ees. En revanche, les chiers binaires ne sont pas editables ou imprimables ; ils ne sont pas cens es etre transportables dun logiciel ` a un autre, encore moins dun syst` eme ` a un autre. Du point de vue du langage C (il nen est peut etre pas de m eme pour le syst` eme sous-jacent) etre de texte ou binaire nest pas une propri et e dun chier, mais de la mani` ere dont il est trait e par un programme. Ainsi la distinction entre ces deux types de chiers ne se fait pas lors de la d eclaration ou de louverture 52 du chier, mais par le choix des fonctions de lecture ou d ecriture qui sont utilis ees : certaines fonctions (fgets et fputs, lecture et ecriture dune ligne, et fscanf et fprintf, lecture et ecriture de donn ees format ees) nont de sens que sur un ot de texte. Dautres fonctions (fread, fwrite) eectuent la lecture et l ecriture de paquets doctets sans interpr etation aucune, et correspondent donc plut ot aux ots binaires 53 . Les ots binaires sont de simples suites doctets. Les ots de texte ont une structure l eg` erement plus riche, puisquils sont cens es etre organis es comme des suites de lignes. Chaque ligne est faite dun nombre quelconque de caract` eres distincts de \n et se termine par le caract` ere \n. M eme si sur un syst` eme particulier les chiers de texte ne sont pas ainsi organis es, ou si le caract` ere qui indique la n dune ligne est di erent, la biblioth` eque standard fera en sorte quils puissent etre vus de cette mani` ere par le programmeur 54 . Tampons. Un chier est dit tamponn e 55 lorsque les transferts physiques doctets entre le chier et un programme qui le lit ou l ecrit se font par paquets de taille xe, la taille du tampon, m eme si les echanges logiques sont faits par paquets de taille variable ou m eme octet par octet. Par d efaut un ot est toujours associ e ` a un tampon de taille pr ed etermin ee, mais le programmeur peut, ` a laide de la fonction setvbuf (cf. section 7.1.1), modier la taille de ce dernier ou m eme le supprimer compl` etement. Ce mode de gestion des chiers r eduit le nombre dop erations physiques dentr ee - sortie (beaucoup plus lentes, quelles que soient les performances des disques durs actuels, que les transferts dinformation purement internes) mais introduit un d ecalage dans leur chronologie. Ce que le programmeur croit etre une op eration d ecriture dans un chier nest en r ealit e quune op eration logique , cest-` a-dire une recopie dans un tampon situ e en m emoire, dont le contenu sera transf er e ult erieurement vers le chier. Par cons equent, il est dicile ou impossible de savoir dans quel etat se trouve le chier ` a un moment donn e ; de plus, si le programme vient ` a subir une terminaison anormale, une partie des informations logiquement ecrites dans le chier seront perdues,
52. Il y a quand-m eme un point de d etail quil fait xer lors de louverture (voyez la fonction fopen) : y a-t-il lieu de guetter les marques de ns-de-ligne pour les transformer dans le caract` ere \n et r eciproquement ? 53. Cons equence : sauf si le syst` eme sous-jacent linterdit, un chier ecrit comme un ot de texte peut etre lu comme un ot binaire. Cela revient ` a ignorer le fait que les octets dont il se compose repr esentent des donn ees dun ordre sup erieur. La d emarche inverse, lire comme un ot de texte un chier binaire, na pas de signication et peut provoquer des erreurs fatales (les ns de ligne seront absentes ou incomprises, le tampon d ebordera, etc.). 54. En particulier, le couple (CR,LF ) utilis e par certains syst` emes est traduit en lecture par lunique caract` ere \n. Inversement, sur de tels syst` emes, l ecriture logique du caract` ere \n se traduit par l ecriture eective du couple (CR,LF ). 55. En bon franglais, on dit plut ot bueris e .

c H. Garreta, 1988-2013

85

7.1

Flots

7 ENTREES-SORTIES

parce quelles nont jamais et e physiquement transf er ees 56 . 7.1.1 Fonctions g en erales sur les ots

Les ots sont repr esent es dans les programmes par des variables de type FILE *. La structure FILE est d enie dans le chier <stdio.h>. Pour utiliser un ou plusieurs ots il faut donc ecrire en t ete de son programme la directive #include <stdio.h> puis une d eclaration de la forme FILE *chier ; Un chier est donc repr esent e en C par une variable dun type pointeur. Par cons equent, sa d eclaration nautorise aucune des op erations propres aux chiers, aussi longtemps quon naura pas fait le n ecessaire (voyez la fonction fopen ci-apr` es) pour quil pointe sur une structure l egitimement allou ee et qui constitue la description dun chier ouvert. Parmi les principales fonctions qui eectuent les op erations g en erales (ouverture, fermeture, etc.) sur les ots nous avons : FILE *fopen(const char *nom, const char *mode) Cette fonction ouvre le chier dont le nom est indiqu e par la cha ne nom et rend un pointeur sur le ot correspondant, ou NULL si lop eration a echou e (chier absent, etc.). Le nom doit etre correct pour le syst` eme dexploitation sous-jacent ; cela ne regarde pas le langage C. Les valeurs permises pour mode sont : etruit. Le descripteur du "r" (read ) ouverture dun chier. Le chier doit exister ; son contenu nest pas d ot cr e e est positionn e en lecture et au d ebut du chier. En principe, seules les op erations de lecture sont permises sur ce ot. "r+" comme "r", mais les op erations d ecriture sont permises aussi. "w" (write) cr eation dun chier. Le chier peut exister ou non ; sil existe, son contenu est enti` erement eac e. Le descripteur du ot cr e e est positionn e en ecriture et au d ebut du chier (qui est vide). En principe, seules les op erations d ecriture sont permises sur ce ot. "w+" comme "w", mais les op erations de lecture sont permises aussi. "a" (append) allongement dun chier. Le chier existe ou non ; sil existe, son contenu nest pas eac e. Le descripteur du ot cr e e est positionn e en ecriture et ` a la n du chier. Seules les op erations d ecriture sont permises. "a+" comme "a", mais les op erations de lecture sont permises aussi. "rb", "r+b", "wb", "w+b", "ab", "a+b" : si on envisage dutiliser le chier en mode binaire il faut ajouter la lettre b au mode ("r+b", "w+b" et "a+b" peuvent se noter aussi "rb+", "wb+" et "ab+") . Remarque 1. La seule di erence que la lettre b dans le mode introduit dans le fonctionnement de toutes les fonctions dentr ee-sortie est la suivante : si le chier est de texte (i.e. si la lettre b ne gure pas dans le mode) alors : en lecture, chaque occurrence dune marque de n de ligne est d etect ee et, si cette marque nest pas le caract` ere \n lui-m eme, remplac ee par lunique caract` ere \n, toute demande d ecriture du caract` ere \n produit en fait lenvoi eectif au chier de la marque de n de ligne requise par le syst` eme sous-jacent. Il nest donc pas exclu que sur un syst` eme dexploitation donn e lajout de la lettre b au mode soit sans eet. Cest notamment le cas dUNIX, o` u les ns de ligne sont indiqu ees par le caract` ere \n. Remarque 2. Sur lint er et quil peut y avoir ` a eectuer des lectures et des ecritures sur le m eme chier, voir les remarques faites ` a la section 7.4.3 ` a propos des chiers en acc` es relatif. int fflush(FILE *flot) Dans le cas dun ot de sortie, cette fonction provoque l ecriture physique imm ediate du tampon en cours de remplissage. Elle rend EOF en cas derreur, z ero dans les autres cas. Dapr` es la norme ocielle du langage C, leet de fflush sur un ot qui nest pas un ot de sortie est ind eni. Mais pour la plupart des biblioth` eques actuelles, lappel de cette fonction sur un ot dentr ee
56. Sil existe une probabilit e non n egligeable pour quun programme ait une n anormale (par exemple ` a cause de conditions dexploitation diciles) il est prudent de programmer des appels r eguliers de la fonction fflush, cf. section 7.1.1.

86

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.1

Flots

supprime les caract` eres disponibles dans le tampon. Par exemple, dans le cas fr equent o` u lentr ee standard correspond au clavier, lappel fflush(stdin) fait dispara tre tous les caract` eres d ej` a tap es mais pas encore lus par le programme. Remarque. Si le chier physique qui correspond au ot indiqu e est un organe interactif, par exemple l ecran dun poste de travail, alors la fonction fflush est implicitement appel ee dans deux circonstances tr` es fr equentes : ecriture du caract` ere \n qui produit l emission dune marque de n de ligne et la vidange eective l du tampon, le d ebut dune op eration de lecture sur lunit e dentr ee associ ee (les organes dentr ee-sortie interactifs forment g en eralement des couples) ; ainsi, par exemple, une lecture au clavier provoque la vidange du tampon d ecriture ` a l ecran. Cela permet quune question soit eectivement ach ee avant que lutilisateur ne doive taper la r eponse correspondante. int fclose(FILE *flot) Cette fonction ferme le ot indiqu e, produisant l ecriture physique des tampons, la restitution de lespace allou e pour le tampon et les descripteurs du ot, etc. Elle rend z ero en cas de succ` es, une autre valeur en cas derreur. A la suite dun appel comme fclose(flot) lemploi de la variable flot est ill egitime. Oublier de fermer, par un appel de fclose, un chier qui a et e ouvert en lecture nest en g en eral pas une erreur importante. Mais oublier de fermer un chier qui a et e ouvert en ecriture (cest-` a-dire un chier qui vient d etre cr e e) peut etre fatal pour linformation qui y a et e ecrite, car le contenu du tampon d ecriture en cours de formation lors de la terminaison du programme ne sera probablement pas sauvegard e dans le chier ; dans certains syst` emes, les chiers nouvellement cr e es ne subissent les op erations qui les rendent connus du syst` eme dexploitation (comme linclusion de leur nom dans un r epertoire) quau moment de leur fermeture. Un chier qui na jamais et e ferm e a donc une existence tr` es pr ecaire. int feof(FILE *flot) Cette fonction na dint er et que si flot d esigne un chier en lecture. Elle renvoie une valeur non nulle si et seulement si lindicateur de n de chier du flot indiqu e est vrai. Cela se produit non pas lorsque le chier est positionn e sur sa n mais lorsquil est plac e au-del` a de sa n. Autrement dit, feof(fic) devient vrai imm ediatement apr` es quune lecture sur fic a echou e parce quil ny avait plus [assez] dinformations dans fic pour la satisfaire. En particulier, feof nest jamais vrai apr` es louverture (en lecture) du chier, m eme lorsque ce dernier est vide. Il faut au moins une [tentative de] lecture pour, eventuellement, le rendre vrai. Cette sp ecication de la fonction feof fonde le sch ema suivant, de lecture s equentielle dun chier : FILE *fichier; ... fichier = fopen(nom , "rb"); v erication du succ` es de louverture de fichier ... lecture de donn ees sur fichier while ( ! feof(fichier)) { traitement des donn ees lues lecture de donn ees sur fichier } ... int ferror(FILE *flot) Cette fonction renvoie une valeur non nulle si et seulement si lindicateur derreur du flot indiqu e est vrai . Elle doit etre appel ee imm ediatement apr` es une entr ee-sortie sur ce ot pour savoir si lop eration en question a echou e. La variable globale enti` ere errno (d eclar ee dans <errno.h>) contient alors un code renseignant sur la nature de lerreur. FILE *tmpfile(void) Cette fonction cr ee un chier temporaire en mode "w+b", sans nom externe, qui sera automatiquement d etruit ` a la terminaison du programme. Elle renvoie le ot associ e ou NULL en cas derreur. int setvbuf(FILE *flot, char *tampon, int mode, size t taille)
c H. Garreta, 1988-2013

87

7.2

Lecture et ecriture textuelles

7 ENTREES-SORTIES

Cette fonction red enit le tampon associ e` a un flot ouvert. Elle doit etre appel ee apr` es louverture du ot mais avant toute lecture ou ecriture. Largument mode doit etre une des trois constantes suivantes, d enies dans <stdio.h> : IOFBF Tampon de taille xe. L ecriture ou la lecture physique a lieu lorsque le tampon contient taille caract` eres. IOLBF Tampon egal ` a une ligne. L ecriture ou la lecture physique a lieu lors de la rencontre du caract` ere \n qui d etermine la n des lignes. IONBF Pas de tampon. Une ecriture ou une lecture physique a lieu ` a chaque lecture ou ecriture logique. Largument taille indique la taille voulue pour le tampon. Largument tampon, sil nest pas NULL, est cens e pointer un espace (de taille au moins egale ` a taille) qui sera utilis e comme tampon. Si tampon est NULL, alors la fonction alloue un espace propre.

7.1.2

Les unit es standard dentr ee-sortie

Sans que le programmeur nait ` a prendre aucune disposition particuli` ere, lex ecution dun programme commence avec trois ots de texte automatiquement ouverts par le syst` eme. Ils sont connect es (on dit aect es ) aux organes dentr ee-sortie les plus naturels par rapport ` a la mani` ere dont on utilise lordinateur. Ces chiers sont d eclar es dans <stdio.h>, de la mani` ere suivante : FILE *stdin, *stdout, *stderr; e standard dentr ee. Elle est habituellement aect ee au clavier du poste de travail. stdin est lunit stdout est lunit e standard de sortie. Elle est habituellement aect ee ` a l ecran du poste de travail. stderr est lunit e standard dachage des erreurs. Elle est aussi aect ee ` a l ecran du poste de travail. Les fonctions de lecture (resp. d ecriture) qui ne sp ecient pas un autre ot utilisent stdin (resp. stdout). Remarque. Lutilisation des unit es standard appara t encore plus int eressante lorsque lon sait que dans UNIX et plusieurs autres syst` emes dexploitation il est permis de r eaecter les unit es standard lors dune ex ecution du programme, sans quil faille recompiler ce dernier. En UNIX et MS-DOS cela fonctionne de la mani` ere suivante : supposons que prog1 soit un programme qui nutilise que les unit es standard stdin et stdout. Activ e par la commande prog1, il lira ses donn ees au clavier et achera ses r esultats ` a l ecran. Mais sil est lanc e par la commande prog1 < fichier1 (resp. : prog1 > fichier2) alors le chier fichier1 (resp. fichier2) remplacera le clavier (resp. l ecran). De mani` ere similaire, supposons que prog2 soit un autre programme qui fait ses entr ees-sorties de la m eme mani` ere. Alors la commande prog1 | prog2 active en parall` ele ces deux programmes avec la sortie standard de prog1 connect ee ` a lentr ee standard de prog2 (les sorties de prog1 sont lues et exploit ees par prog2 au fur et ` a mesure quelles sont produites par prog1). Bien entendu, tout cela se combine. Par exemple, la commande prog1 < fichier1 | prog2 > fichier2 active prog1 et prog2. Les donn ees de prog1 sont lues dans fichier1, ses r esultats sont fournis ` a titre de donn ees ` a prog2, dont les r esultats sont ecrits dans fichier2.

7.2
7.2.1

Lecture et ecriture textuelles


Lecture et ecriture de caract` eres et de cha nes

Les fonctions de lecture- ecriture les plus simples eectuent la lecture ou l ecriture dun caract` ere isol e ou bien la lecture ou l ecriture dune ligne. Pour la lecture nous avons : int fgetc(FILE *flot) Renvoie le caract` ere suivant sur le ot indiqu e, ou EOF si la n du chier est atteinte ou si une erreur survient. Cest une vraie fonction.

88

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.2 Lecture et ecriture textuelles

int getc(FILE *flot) Fait la m eme chose que fgetc mais cest peut etre macro. Lint er et dutiliser une macro r eside dans lecacit e sup erieure des (petites) macros par rapport aux (petites) fonctions 57 . Il ne faut pas oublier cependant quil y a deux cas o` u les macros ne peuvent pas etre utilis ees : lorsque largument a un eet de bord. Une expression comme *s++ = fgetc(table_de_fichiers[i++]) est correcte (mais peu utilis ee), tandis que *s++ = getc(table_de_fichiers[i++]) est certainement erron ee, car ayant un eet ind eni (on ne peut pas dire combien de fois i sera incr ement e) ; lorsquil faut obtenir ladresse de lop eration en question, pour la passer comme argument dune autre fonction. Par exemple, si getc est une macro, un appel comme appliquer(getc, fichier) sera certainement trouv e incorrect par le compilateur. int getchar(void) getchar() equivaut ` a getc(stdin) ; char *fgets(char *s, int n, FILE *flot) Lecture dune ligne en veillant ` a ne pas d eborder. Plus pr ecis ement, cette fonction lit des caract` eres sur le flot indiqu e et les place dans lespace point e par s. Elle sarr ete lorsquelle a lu n1 caract` eres ou lorsquelle a rencontr e un caract` ere \n de n de ligne (ce caract` ere est copi e dans le tableau). Un caract` ere \0 est ajout e` a la n du tableau. La fonction rend s, ou NULL si une erreur ou la n du chier a et e rencontr ee. Lisez aussi la remarque faite ci-apr` es ` a propos de la fonction gets. char *gets(char *s) Lecture dune ligne sur le ot stdin. Les caract` eres lus sont rang es dans lespace point e par s. Elle renvoie le m eme r esultat que fgets mais, contrairement ` a cette derni` ere ebordement 58 ; il ny a pas de test de d le caract` ere de n de ligne est remplac e par \0. Atention. Largument de gets doit etre ladresse dun espace disponible (accessible en ecriture) et de taille susante, dans lequel gets puisse ranger les caract` eres qui seront lus. Exemples. Pour illustrer ces fonctions, voici deux exercices d ecole. Dune part, l ecriture dune fonction analogue ` a gets en utilisant getchar : char *myGets(char *s) { int c, i = 0; c = getchar(); while (c != \n) { if (c == EOF) return NULL; s[i++] = c; c = getchar(); } s[i] = \0; return s; } Dautre part, l ecriture dune fonction analogue ` a getchar (version fonction) en utilisant gets :
57. Il sagit dentr ees-sorties tamponn ees, cest-` a-dire de simples transferts entre m emoires. Sil s etait agi dop erations physiques, le gain de temps obtenu en utilisant getc ` a la place de fgetc aurait et e n egligeable devant le temps requis par lentr ee-sortie elle-m eme. 58. Ainsi, gets lit des caract` eres jusqu` a la rencontre de \n, sans se pr eoccuper de savoir si lespace dans lequel ces caract` eres sont rang es est de taille susante pour les recevoir ; autrement dit, tout appel de gets peut entra ner un d ebordement de m emoire. Cest la raison pour laquelle on dit que tout programme comportant ne serait-ce quun appel de gets est bogu e.

c H. Garreta, 1988-2013

89

7.2

Lecture et ecriture textuelles

7 ENTREES-SORTIES

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 indiqu e sur le flot indiqu e. Renvoie le caract` ere ecrit ou EOF si une erreur survient. Cest une vraie fonction. int putc(int caractere, FILE *flot) Fait la m eme chose que fputc mais peut etre une macro. Voir ` a ce propos les remarques faites ` a loccasion de lintroduction 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 indiqu e. La fonction renvoie EOF en cas derreur, une valeur 0 dans les autres cas. int puts(const char *s) Cette fonction ecrit la cha ne s sur le ot stdout. Elle renvoie le m eme r esultat que fputs. Contrairement a fputs un caract` ` ere \n est ajout e` a la suite de la cha ne. int ungetc(int c, FILE *flot) D electure du caract` ere c. Cette fonction remet le caract` ere c dans le flot indiqu e, o` u il sera trouv e lors de la prochaine lecture. Sur chaque ot, seule la place pour un caract` ere d elu est garantie. On ne peut pas d elire le caract` ere EOF. Cette op eration incarne donc lid ee de restitution (` a lunit e dentr ee, qui en est le fournisseur) dun caract` ere lu ` a tort. Cest une mani` ere de r esoudre un probl` eme que pose l ecriture danalyseurs lexicaux, o` u la n de certaines unit es lexicales est indiqu ee par un caract` ere qui les suit sans en faire partie. Exemple : la lecture dun nombre entier non n egatif : int lirentier(FILE *flot) { /* lecture dun 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 caract` ere suivant le nombre */ ungetc(c, flot); return x; } 7.2.2 Ecriture avec format printf

La fonction d ecriture sur lunit e de sortie standard (en principe l ecran du poste de travail) avec conversion et mise en forme des donn ees est : printf( format , expr 1 , expr 2 , ... expr n ) 90
c H. Garreta, 1988-2013

ENTREES-SORTIES

7.2 Lecture et ecriture textuelles

format est une cha ne qui joue un r ole particulier. Elle est form ee de caract` eres qui seront ecrits normalement, m elang es ` a des indications sur les types des expressions expr 1 , expr 2 , ... expr n et sur laspect sous lequel il faut ecrire les valeurs de ces expressions. Cela fonctionne de la mani` ere suivante : la fonction printf parcourt la cha ne format et reconna t certains groupes de caract` eres comme etant des sp ecications de format. Cest le caract` ere % qui d eclenche cette reconnaissance. La fonction printf recopie sur le ot de sortie tout caract` ere qui ne fait pas partie dune telle sp ecication. La ieme sp ecication d etermine la mani` ere dont la valeur de expr i sera ecrite. Les sp ecications de format reconnues par la fonction printf sont expliqu ees dans la table 4 (page 92). Notez que seuls les el ements 1 (le caract` ere %) et 6 (la lettre qui indique le type de la conversion) sont obligatoires dans une sp ecication de format. Exemple 1. Supposons quon ait donn e la d eclaration-initialisation float x = 123.456; Voici une liste dappels de printf, avec lachage produit par chacun : Appel printf(">%f<", x); printf(">%12f<", x); printf(">%12.2f<", x); printf(">%.2f<", x); printf(">%-12.2f<", x); printf(">%+-12.2f<", x); printf(">% -12.2f<", x); printf(">%12.2f<", x); printf(">%012.2f<", x); printf(">%.0f<", x); printf(">%#.0f<", x); Achage obtenu >123.456001< > 123.456001< > 123.46< >123.46< >123.46 < >+123.46 < > 123.46 < > 123.46< >000000123.46< >123< >123.<

Exemple 2. La liste suivante illustre lutilisation de la largeur et de la pr ecision pour les cha nes. Supposons avoir donn e les d eclarations : char *s = "ABCDEFG"; int l = -10, n = 5; Voici une liste dappels de printf, avec lachage produit par chacun : Appel printf("printf(">%5s<", s); printf("printf(">%10s<", s); printf("printf(">%-10s<", s); printf("printf(">%10.5s<", s); printf("printf(">%-10.5s<", s); printf("printf(">%.5s<", s); printf("printf(">%*.*s<", l, n, s); Achage obtenu >ABCDEFG< > ABCDEFG< >ABCDEFG < > ABCDE< >ABCDE < >ABCDE< >ABCDE <

La largeur du champ (cf. tableau 4, n 3) peut etre indiqu ee par le caract` ere * ` a la place dun nombre. Elle est alors d enie par la valeur de largument dont cest le tour, qui doit etre de type entier. Il en est de m eme pour la sp ecication de la pr ecision (cf. tableau 4, n 4). Par exemple, i et j etant des variables enti` eres et x une variable ottante, lexpression printf("%*.*f", i, j, x); ecrit la valeur de x sur un nombre de caract` eres egal ` a la valeur de i, avec un nombre de chires apr` es la virgule egal ` a la valeur de j.

c H. Garreta, 1988-2013

91

7.2

Lecture et ecriture textuelles

7 ENTREES-SORTIES

Chaque sp ecication de format se compose, dans lordre indiqu e, des el ements suivants : 1. Obligatoirement, le caract` ere %. 2. Facultativement, un ou plusieurs modieurs, dans un ordre quelconque, parmi : - : cadrer ` a gauche du champ (par d efaut le cadrage se fait ` a droite) ; + : imprimer le signe du nombre m eme lorsque celui-ci est positif ; espace : si le premier caract` ere nest pas un signe, placer un espace au d ebut dun nombre ; 0 : compl eter les nombres par des z eros (` a la place de blancs) ; # : utiliser le format alternatif, lorsquil existe (voir ci-dessous). 3. Facultativement, un nombre qui indique la largeur du champ. Il sagit dune largeur minimum, automatiquement augment ee si elle se r ev` ele insusante. 4. Facultativement, un point suivi dun nombre qui indique la pr ecision : dans les conversions e, E ou f : le nombre de chires ` a ecrire apr` es la virgule d ecimale ; dans les conversions g ou G : le nombre de chires signicatifs ; pour une cha ne : le nombre maximum de caract` eres ` a acher ; pour un entier (conversions d et u) : le nombre minimum de chires. Le cas ech eant, des z eros seront ajout es ` a gauche. 5. Facultativement, une lettre qui compl` ete linformation sur la nature de largument et, le cas ech eant, modie en cons equence la largeur du champ : h (associ ee ` a d ou u) : largument est un short. Cette indication est sans eet si un int et un short sont la m eme chose ; l (associ ee ` a d, u, x ou X) : largument est un long ou un unsigned long. Cette indication est sans eet si un int et un long sont la m eme chose ; L (associ ee ` a f, e, E, g ou G) : largument est un long double. 6. Obligatoirement, un caract` ere de conversion, parmi d : argument : int ; impression : notation d ecimale sign ee. o : argument : int ; impression : notation octale non sign ee. Format alternatif (voir # au point 2) : un 0 est imprim e au d ebut du nombre. x : argument : int ; impression : notation hexad ecimale non sign ee avec les chires 0...9abcdef. Format alternatif : le nombre est pr ec ed e de 0x. X : comme x mais avec ABCDEF et 0X ` a la place de abcdef et 0x. u : argument : int ; impression : notation d ecimale non sign ee. c : argument : unsigned char ; impression : un seul caract` ere. s : argument : char * ; impression : cha ne de caract` eres. f : argument : double ; impression : notation en virgule xe [-]zzz.f. Le nombre de f est donn e par la pr ecision. Pr ecision par d efaut : 6. Si la pr ecision vaut z ero, le point est supprim e. Format alternatif : le point est toujours imprim e. e : argument : double. Impression : notation en virgule ottante [-]z. enn. Le nombre de f est donn e par la pr ecision. Pr ecision par d efaut : 6. Si la pr ecision vaut z ero, le point est supprim e. Format alternatif : le point est toujours imprim e. E : comme %e avec E ` a la place de e. g : argument : double ; impression : comme %e si lexposant est inf erieur ` a -4 ou sup erieur ` a la pr ecision, comme %f sinon. Format alternatif : le point est toujours imprim e. G : comme %g avec E ` a la place de e. p : argument : void * ; impression : d epend de limplantation (par exemple %X). n : argument : int *. Aucune impression. Eet : largument correspondant doit etre ladresse dune variable enti` ere, qui recevra pour valeur le nombre de caract` eres eectivement ecrits jusqu` a pr esent par lappel en cours de la fonction printf. % : pas dargument correspondant ; impression : le caract` ere %. Table 4 Sp ecications de format pour printf, fprintf et sprintf

92

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.2 Lecture et ecriture textuelles

7.2.3

Lecture avec format scanf

La fonction de lecture avec format ` a lunit e dentr ee standard (en principe le clavier du poste de travail) est scanf( format , adresse 1 , adresse 2 , ... adresse n ) Largument format est une cha ne qui indique la mani` ere de convertir les caract` eres qui seront lus ; adresse 1 , adresse 2 , ... adresse n indiquent les variables devant recevoir les donn ees lues. Ce sont des sorties de la fonction scanf, par cons equent il est essentiel que ces arguments soient des adresses de variables ` a lire. La fonction scanf constituant un vrai analyseur lexical, les donn ees lues ont de nombreuses occasions d etre incorrectes et on peut sattendre ` a ce que scanf soit dun maniement dicile. Lexp erience ne d e coit pas cette attente ! Voici un bon conseil : si un programme ne fonctionne pas comme il le devrait, commencez par v erier les valeurs lues par la fonction scanf (par exemple avec des appels de printf suivant imm ediatement les appels de scanf). Les sp ecications de format reconnues par la fonction scanf sont expliqu ees dans la table 5 (page 94). Fonctionnement : scanf parcourt le format. Elle rend la main lorsque la n du format est atteinte ou sur une erreur. Jusque-l` a: Tout caract` ere ordinaire du format, cest-` a-dire qui nest ni un caract` ere despacement (blanc, tabulation) ni un caract` ere faisant partie dune sp ecication de format (commen cant par %), doit sidentier au caract` ere courant du ot dentr ee. Si cette identication a lieu, le caract` ere courant est lu, sans etre rang e dans aucune variable, et le parcours du format se poursuit. Si lidentication na pas lieu, lactivation de scanf se termine. Les sp ecications de format commencent par %. Elles indiquent la mani` ere danalyser les caract` eres lus sur le ot dentr ee et de ranger les valeurs ainsi obtenues. Voir le tableau 5. Dans le format, les caract` eres ordinaires et les sp ecications peuvent etre s epar es entre eux par des caract` eres despacement. Le nombre de ces espacements est sans importance, mais leur pr esence indique que les donn ees correspondantes peuvent etre s epar ees dans le ot dentr ee par un nombre quelconque de caract` eres despacement ou de n de ligne. Sil ny a pas despacement, dans le format, entre les caract` eres ordinaires ou les sp ecications, alors les donn ees correspondantes dans le ot dentr ee doivent etre adjacentes. Cependant, les espacements au d ebut des nombres sont toujours saut es. On peut m elanger des appels de scanf et dautres fonctions de lecture. Chaque appel dune telle fonction commence par lire le premier caract` ere que lappel pr ec edent na pas consomm e . Exemple 1. Lecture dun entier : scanf("%d", &x); Exemple 2. Lecture de deux entiers s epar es par une virgule et encadr es par une paire de parenth` eses : scanf("(%d,%d)", &x, &y); Donn ees correctes : (22,33) ( 22 , ou 33 ) ( 22, 33) (des blancs, mais uniquement devant les nombres). Donn ees incorrectes : (trop de blancs). Exemple 3. Comme ci-dessus mais avec une syntaxe plus souple (les blancs sont tol er es partout) : scanf(" ( 22 ; (%d 33 ,%d ) )", &x, &y); Toutes les donn ees de lexemple pr ec edent sont correctes. Exemple de donn ee incorrecte : Exemple 4. Lecture dun caract` ere 59 : char calu; ... scanf("%c", &calu); Remarque. Nous avons indiqu e par ailleurs quen C on range souvent les caract` eres dans des variables de type int an de permettre la m emorisation dune valeur intruse , EOF. Cependant, notez bien quici nous sommes oblig es de d eclarer la variable calu de type char ; le programme
59. Rappelons que fgetc, getc et getchar font ce travail bien plus simplement.

c H. Garreta, 1988-2013

93

7.2

Lecture et ecriture textuelles

7 ENTREES-SORTIES

Chaque sp ecication de format se compose, dans lordre indiqu e, des el ements suivants : 1. Obligatoirement, le caract` ere %. 2. Facultativement, le caract` ere * qui indique une suppression daectation : lire une donn ee comme indiqu e et l oublier (cest-` 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 de nombres coll es les uns aux autres). 4. Facultativement, une lettre qui apporte des informations compl ementaires sur la nature de largument correspondant : h devant d, i, o, u, x : largument est ladresse dun short (et non pas ladresse dun int) ; l devant d, i, o, u, x : largument est ladresse dun long (et non pas ladresse dun int). Devant e, f, g : largument est ladresse dun double (et non pas dun float) ; L devant e, f, g : largument est ladresse dun long double (et non pas dun float). 5. Obligatoirement, un caract` ere de conversion parmi d Argument : int *. Donn ee : nombre entier en notation d ecimale. i Argument : int *. Donn ee : entier en notation d ecimale, octale (pr ec ed e de 0) ou hexad ecimale (pr ec ed e de 0x ou 0X). o Argument : int *. Donn ee : entier en octal (pr ec ed e ou non de 0). u Argument : unsigned int *. Donn ee : entier en notation d ecimale. x Argument : int *. Donn ee : entier en notation hexad ecimale (pr ec ed e ou non de 0x ou 0X). c Argument : char *. Donn ee : autant de caract` eres que la largeur du champ lindique (par d efaut : 1). Ces caract` eres sont copi es dans lespace dont ladresse est donn ee en argument. Les blancs et tabulations ne sont pas pris pour des s eparateurs (ils sont copi es comme les autres caract` eres) et il ny a pas de \0 automatiquement ajout e` a la n de la cha ne. s Argument : char *. Donn ee : cha ne de caract` eres termin ee par un caract` ere despacement (blanc, tabulation, n de ligne) qui nen fait pas partie. Un \0 sera automatiquement ajout e apr` es les caract` eres lus. e Argument : float *. Donn ee : nombre ottant, cest-` a-dire dans lordre : signe facultatif, suite de chires, point d ecimal et suite de chires facultatifs, exposant (e ou E, signe facultatif et suite de chires) facultatif. f Comme e. g Comme e. p Argument : void ** (ou void *, pour ce que ca change...). Donn ee : nombre exprimant un pointeur comme il serait imprim e par printf avec le format %p. n Argument : int *. Aucune lecture. Eet : largument correspondant doit etre ladresse dune variable enti` ere ; celle-ci re coit pour valeur le nombre de caract` eres eectivement lus jusqu` a ce point par le pr esent appel de la fonction scanf. [caract` ere ...caract` ere ] Argument : char *. Donn ee : la plus longue suite faite de caract` eres appartenant a lensemble a indiqu ` e entre crochets. Un caract` ere au moins sera lu. Un \0 est ajout e` a la n de la cha ne lue. [^caract` ere ...caract` ere ] Argument : char *. Donn ee : comme ci-dessus mais les caract` eres permis sont maintenant ceux qui nappartiennent pas ` a lensemble indiqu e. % Pas dargument. Le caract` ere % doit se pr esenter sur le ot dentr ee.
a. Pour sp ecier le caract` ere ] l ecrire imm ediatement apr` es le [ initial.

Table 5 Sp ecications de format pour scanf, fscanf et sscanf int calu; ... scanf("%c", &calu); aurait et e incorrect (bien quil puisse fonctionner correctement sur certains syst` emes), car on y fournit ` a scanf ladresse dun entier (fait de plusieurs octets) pour y ranger un caract` ere, alors que scanf, dapr` es lanalyse du format, croit recevoir ladresse dun char (un octet). Or on ne doit pas faire dhypoth` ese sur la mani` ere dont sont organis es les octets qui composent un entier, le langage C ne pr ecisant rien ` a ce sujet. Une autre 94
c H. Garreta, 1988-2013

ENTREES-SORTIES

7.2 Lecture et ecriture textuelles

solution correcte aurait et e la suivante : int calu; char tmp; ... scanf("%c", &tmp); calu = tmp; Exemple 5. Lecture dun nombre entier et du caract` ere le suivant imm ediatement, quel quil soit : int nblu; char calu; ... scanf("%d%c", &nblu, &calu); Exemple 6. Lecture dun nombre entier et du premier caract` ere 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 sp ecications qui constituent le format peuvent etre s epar ees entre elles par un ou plusieurs blancs, ce qui indique que les donn ees ` a lire correspondantes peuvent alors etre s epar ees par un nombre quelconque de blancs. Pour la fonction scanf, les blancs dans le format se traduisent donc par lordre ` a pr esent, sautez tous les caract` eres blancs qui se pr esentent . Or la seule mani` ere pour lordinateur de sassurer quil a bien saut e tous les caract` eres blancs consiste ` a lire (sans le consommer) le premier caract` ere non blanc qui les suit. Si la lecture se fait dans un chier, cela ne pose pas de probl` eme, les caract` eres ` a lire existant tous d` es le d epart. Mais cela peut cr eer des situations confuses dans le cas dune lecture interactive mettant en uvre le clavier et un op erateur 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 quon a ecrit, peut- etre sans faire attention, des blancs apr` es le format %d, le premier appel de scanf ci-dessus signie lisez un entier et tous les blancs qui le suivent . Lop erateur constatera avec etonnement que pour que son premier nombre sont pris en compte il est oblig e de composer le deuxi` eme ! Exemple de dialogue (le signe repr esentant la frappe de la touche Entr ee ) : donne x : 11 la machine ne r eagit pas... ! ? 22 donne y : x = 11, y = 22 Notez que faire suivre le premier nombre dun caract` ere non blanc conventionnel nest pas une bonne solution car, ce caract` ere n etant pas consomm e, il fait echouer la lecture du second nombre : donne x : 11 donne y : x = 11, y = 33 Le con ` a retenir : dans le format de scanf, les blancs ne sont pas de la d ecoration, ils jouent un r ole bien pr ecis. `re. Une autre situation g A propos de la lecture dun caracte en eratrice de confusion est celle o` u des lectures de nombres sont suivies par des lectures de caract` eres ou de cha nes. Par exemple, le programme na f suivant lit des nombres les uns ` a la suite des autres et ache le carr e de chacun :
c H. Garreta, 1988-2013

95

7.2

Lecture et ecriture textuelles

7 ENTREES-SORTIES

int x, c; ... do { printf("donne un nombre : "); scanf("%d", &x); lecture dun nombre printf("%d ^ 2 = %d\n", x, x * x); printf("un autre calcul (O/N) ? "); c = getchar(); lecture dun caract` ere } while (c == O); printf("au revoir...");\end{verbatim} Voici un dialogue (la r eplique de lop erateur est soulign ee, le signe repr esente la frappe de la touche Entr ee ) : donne un nombre : 14 14 ^ 2 = 196 un autre calcul (O/N) ? au revoir... La machine na pas attendu la r eponse (O ou N) de lop erateur... ! Que sest-il pass e ? Lop erateur a compos e un nombre suivi imm ediatement dun caract` ere n-de-ligne (traduction de la frappe de la touche Entr ee ). Cette n de ligne est in evitable mais na pas et e consomm ee par scanf et est rest ee disponible pour des lectures ult erieures. Lappel de getchar (qui, dans lesprit du programmeur, aurait d u provoquer une nouvelle lecture physique) trouve cette n-de-ligne et laecte ` a c comme r eponse, ce qui fait quitter la boucle. Ainsi, pour que ce programme se comporte comme son programmeur attend il faudrait qu` a la suite de linvite donne un nombre: , lutilisateur saisisse un nombre imm ediatement suivi dun caract` ere O ou N qui est la r eponse ` a une question qui ne lui a pas encore et e pos ee ! Cela nest pas raisonnable, bien s ur. La bonne mani` ere de faire consiste ` a vider le tampon dentr ee avant de proc eder ` a une lecture de caract` ere. Dans beaucoup denvironnements on peut faire cela avec la fonction fflush : int x, c; char s[BUFSIZ]; ... do { printf("donne un nombre : "); scanf("%d", &x); printf("%d ^ 2 = %d\n", x, x * x); printf("un autre calcul (O/N) ? "); ici, le tampon de stdin contient (au moins) un \n fflush(stdin); maintenant le tampon est vide c = getchar(); } while (c == O); printf("au revoir..."); ... Si la fonction fflush est inop erante sur le ot stdin (un tel comportement est permis par la norme du langage C) alors on peut r esoudre le m eme probl` eme en faisant des appels r ep et es de la fonction getchar, de mani` ere ` a consommer tous les caract` eres restant dans le tampon jusqu` a la n-de-ligne, ce caract` ere devant etre consomm e lui aussi. Lexemple ci-dessus devient ... scanf("%d", &x); printf("%d ^ 2 = %d\n", x, x * x); printf("un autre calcul (O/N) ? "); ici, le tampon de stdin contient (au moins) un \n while (getchar() != \n) ; maintenant le tampon est vide c = getchar(); ...

96

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.3

Op erations en mode binaire

7.2.5

Les variantes de printf et scanf

Voici la famille au complet, comme elle est d eclar ee (en syntaxe ANSI) dans le chier <stdio.h> : int printf(const char *format, ... ) Nous avons d ecrit cette fonction. Ajoutons seulement quelle renvoie le nombre de caract` eres eectivement ecrits (on utilise rarement cette information) ou un nombre n egatif en cas derreur. int fprintf(FILE *flot, const char *format, ... ) Comme printf mais sur le ot indiqu e (` a la place de stdout). Ainsi printf( format , exp 1 , ... , exp k ) equivaut ` a fprintf(stdout, format , exp 1 , ... , exp k ) int sprintf(char *destination, const char *format, ... ) Cette fonction eectue les m emes mises en forme que ses deux surs, avec la di erence que les caract` eres produits ne sont pas ajout es ` a un ot, mais sont concat en es ensemble dans la cha ne destination. Elle permet donc de dissocier les deux fonctionnalit es de printf, transcodage et ecriture des donn ees, an dutiliser la premi` ere sans la deuxi` eme. int scanf(const char *format, ... ) Nous avons d ecrit cette fonction. Ajoutons quelle renvoie EOF si une n de chier ou une erreur a emp ech e toute lecture ; autrement elle rend le nombre de variables lues avec succ` es. Cette indication fournit une mani` ere bien pratique pour d etecter, dans les programmes simples, la n dune s erie de donn ees. Exemple : for(;;) { printf("donne un nombre : "); if (scanf("%d", &x) < 1) break; exploitation du nombre lu } printf("au revoir..."); La boucle pr ec edente sera arr et ee par nimporte quel caract` ere non blanc ne pouvant pas faire partie dun nombre. int fscanf(FILE *flot, const char *format, ... ) Comme scanf mais sur le ot indiqu e (` a la place de stdin). Ainsi scanf( format , exp 1 , ... , exp k ) equivaut ` a fscanf(stdin, format , exp 1 , ... , exp k ) int sscanf(const char *source, const char *format, ... ) Cette fonction eectue la m eme analyse lexicale que ses surs, avec la particularit e que le texte analys e nest pas pris dans un ot, mais dans la cha ne source.

7.3
7.3.1

Op erations en mode binaire


Lecture- ecriture

size t fread(void *destination, size t taille, size t nombre, FILE *flot) Cette fonction essaye de lire sur le flot indiqu e nombre objets, chacun ayant la taille indiqu ee, et les copie les uns ` a la suite des autres dans lespace point e par destination. Elle renvoie le nombre dobjets eectivement lus, qui peut etre inf erieur au nombre demand e, ` a cause de la rencontre de la n du chier 60 , dune 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 indiqu ee, qui se trouvent les uns ` a la suite des autres ` a ladresse indiqu ee par source. Elle renvoie le nombre dobjets ecrits, qui peut etre inf erieur au nombre demand e (en cas derreur).
60. Lorsquun chier est lu par des appels de la fonction fread, comparer avec z ero le nombre dobjets eectivement lus est la mani` ere la plus pratique de d etecter la n du chier.

c H. Garreta, 1988-2013

97

7.3

Op erations en mode binaire

7 ENTREES-SORTIES

7.3.2

Positionnement dans les chiers

int fseek(FILE *flot, long deplacement, int origine) Cette fonction positionne le pointeur du chier associ e au flot indiqu e. La premi` ere lecture ou ecriture ult erieure se fera ` a partir de la nouvelle position. Celle-ci est obtenue en ajoutant la valeur de d eplacement a une valeur de base qui peut ` etre la position courante, la n ou le d ebut du chier. Cest largument origine qui indique de quelle base il sagit ; la valeur de cet argument doit etre une des constantes (d enies dans <stdio.h>) : ebut du chier SEEK SET : base = le d SEEK CUR : base = la position courante SEEK END : base = la n du chier La fonction renvoie z ero en cas de succ` es, une valeur non nulle en cas derreur. Sur un chier de texte, il vaut mieux utiliser getpos et setpos. Un appel de cette fonction sur un chier en ecriture produit la vidange du tampon (fseek implique fflush). Lire ci-apr` es les explications sur lacc` es relatif aux chiers. void rewind(FILE *flot) Positionnement au d ebut du chier du pointeur de chier associ e au ot indiqu e. Un appel de cette fonction equivaut ` a fseek(flot, 0L, SEEK SET) suivi de la mise ` a z ero de tous les indicateurs derreur. void fgetpos(FILE *flot, fpos t *ptr) Place dans ptr la position courante dans le flot indiqu e en vue de son utilisation par setpos. Le type fpos t (d eni dans <stdio.h>) a une taille susante pour contenir une telle information. void fsetpos(FILE *flot, const fpos t *ptr) La valeur de ptr provenant dun appel de getpos, cette fonction positionne le ot indiqu e` a lemplacement correspondant. `s relatif aux e le ments des fichiers. La principale application des fonctions de positionnement Lacce est la r ealisation de ce quon appelle lacc` es relatif 61 ou parfois acc` es direct aux composantes dun chier : lacc` es ` a une composante (on dit un enregistrement ) que ce soit pour le lire ou pour l ecrire, ` a partir de la donn ee de son rang dans le chier sans etre oblig e dacc eder au pr ealable aux enregistrements qui le pr ec` edent. Pour donner une signication utilisable ` a la notion de neme article , il faut que le chier puisse etre vu comme une suite darticles de m eme taille (mais la biblioth` eque C ignorera ce fait ; le chier restera une suite doctets). Cela sobtient g en eralement en d eclarant une structure qui repr esente un article, selon le mod` ele : typedef struct { ... champs dont se composent tous les articles ... } ARTICLE;... Un chier destin e` a etre utilis e en acc` es relatif sera d eclar e normalement, et ouvert de fa con ` a autoriser les lectures et les ecritures : FILE *fichier; ... fichier = fopen(nom , "rb+"); Si les articles sont num erot es ` a partir de z ero, lop eration programme alors selon le sch ema suivant : ARTICLE article; ... fseek(fichier, n * sizeof(ARTICLE), SEEK_SET); fread( & article, sizeof(ARTICLE), 1, fichier); ... exploitation des valeurs des champs de article ... Lop eration

lecture de lenregistrement de rang n

se

ecriture de la composante de rang n

ob eit au sch ema

61. Lacc` es relatif soppose ` a lacc` es s equentiel, dans lequel une composante ne peut etre lue ou ecrite autrement qu` a la suite de la lecture ou de l ecriture de la composante qui la pr ec` ede.

98

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.4

Exemples

ARTICLE article; ... aectation des champs de article ... fseek(fichier, n * sizeof(ARTICLE), SEEK_SET); fwrite( & article, sizeof(ARTICLE), 1, fichier); ... Attention. La possibilit e deectuer des lectures et des ecritures sur le m eme chier pose des probl` emes dans la gestion des tampons associ es au chier que les fonctions de la biblioth` eque standard ne r esolvent pas. Ainsi chaque fois quune s erie de lectures sur un chier va etre suivie dune s erie d ecritures sur le m eme chier, ou r eciproquement, il appartient au programmeur de pr evoir l ecriture eective du tampon, soit par un appel explicite de la fonction fflush, soit par un appel dune fonction de positionnement comme fseek. Notez que cette contrainte est satisfaite si on pratique des acc` es relatifs selon les sch emas indiqu es ci-dessus, puisquil y a toujours un appel de fseek entre deux acc` es au chier, quel que soit leur mode.

7.4

Exemples

Les programmes suivants sont assez na fs ; ils se proposent dillustrer les fonctions de traitement de chiers, aussi bien pour les chiers de texte que pour les chiers binaires.

7.4.1

Fichiers en vrac

Le programme suivant fait une copie identique dun chier (ce programme nest pas bien utile, g en eralement une fonction du syst` eme dexploitation fait ce travail) : #include <stdio.h> #define PAS_D_ERREUR #define ERREUR_OUVERTURE #define ERREUR_CREATION 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; } Lessentiel de ce programme est sa boucle while. Elle se compose dun appel de fread demandant la lecture de 512 octets, suivi dun appel de fwrite pour ecrire les nombre octets eectivement lus. Supposons que le
c H. Garreta, 1988-2013

0 1 2

/* codes conventionnels */ /* ` a lusage du syst` eme */ /* dexploitation */

99

7.4

Exemples

7 ENTREES-SORTIES

N chier ` a copier comporte N octets ; ces deux op erations seront ex ecut ees 512 fois avec nombre egal ` a 512 puis (sauf si N est multiple de 512) une derni` ere fois avec nombre egal au reste de la division de N par 512.

7.4.2

Fichiers binaires et chiers de texte

Lobjet du programme suivant est la constitution dun chier darticles ` a partir dun chier de texte qui se pr esente comme une r ep etition du groupe suivant : un nom et un pr enom sur la m eme ligne ; une adresse sur la ligne suivante ; un entier seul sur une ligne, exprimant un certain nombre de passages (` a un p eage autoroutier). Exemple TOMBAL Pierre Rue de la Gaiete de Vivre 10

MENSOIF Gerard Allee Les verts 20 etc. Le programme est ind ependant de la nature du chier de texte, qui peut etre aussi bien le clavier dun terminal quun chier existant par ailleurs. /* constitution dun fichier darticles */ /* ` a partir dun fichier de texte */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define #define #define #define #define PAS_D_ERREUR ERREUR_OUVERTURE ERREUR_CREATION ERREUR_ECRITURE ERREUR_FERMETURE 0 1 2 3 4 /* un article du fichier */

struct { char nom[32], prenom[32]; char adresse[80]; int nombre_de_passages; } art; FILE *srce; FILE *dest; main() { char nom[256];

/* fichier de texte */ /* fichier binaire */

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); 100
c H. Garreta, 1988-2013

ENTREES-SORTIES

7.4

Exemples

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 et e utilis ees 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 pr enom sont lus par scanf aussi, car cela nous arrange bien que les blancs soient pris pour des s eparateurs et enlev es. Mais ladresse est lue par gets, car elle peut contenir des blancs qui ne doivent pas etre supprim es.

7.4.3

Fichiers en acc` es relatif

Dans le programme pr ec edent nous avons vu un chier binaire organis e en articles et trait e s equentiellement (chaque article etant ecrit ` a la suite du pr ec edent). Voyons un exemple o` u ce m eme chier est trait e en acc` es relatif. Imaginons que le chier cr e e ` a lexemple pr ec edent soit le chier des abonn es ` a un certain p eage, et que nous disposions par ailleurs dun chier binaire contenant les num eros des abonn es qui ont emprunt e ce p eage durant une p eriode donn ee. On nous demande d ecrire un programme pour incr ementer la valeur du champ nbr passages de chaque abonn e concern e.

#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);
c H. Garreta, 1988-2013

101

7.5

Les chiers de bas niveau dUNIX

7 ENTREES-SORTIES

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 chiers de bas niveau dUNIX

Les chiers de bas niveau du syst` eme UNIX ne sont pas fondamentalement distincts des ots que lon vient de d ecrire. En fait, les ots et les chiers de bas niveau sont deux mani` eres de voir depuis un programme les m emes entit es de base (les chiers). Les chiers de bas niveau sont la mani` ere habituelle de r ealiser les chiers binaires lorsquon ne poss` ede pas un C ANSI. Dans le syst` eme UNIX chaque processus dispose, pour g erer ses chiers, dune table de descripteurs de chiers. Lorsque les chiers sont g er es au plus bas niveau, ils sont repr esent es tout simplement par un indice dans cette table. Par exemple, les trois unit es standard stdin, stdout et stderr sont respectivement repr esent ees en tant que chiers par les descripteurs dindices 0, 1 et 2. Les fonctions disponibles sont : int open(char *nom, int mode, int permissions) Ouvre un chier existant ayant le nom externe indiqu e. Largument mode doit avoir pour valeur une des constantes symboliques (d enies dans <ioctl.h> ou dans <sys/file.h>) : O RDONLY : ouverture en lecture seulement O WRONLY : ouverture en ecriture seulement O RDWR : ouverture en lecture / ecriture Cette fonction renvoie le num ero du descripteur ouvert pour le chier, ou un nombre n egatif en cas derreur. Pour largument permissions voir ci-dessous. int creat(char *nom, int permissions) Cr ee un chier nouveau, ayant le nom indiqu e, et louvre en ecriture. Comme pour la fonction open, largument permissions indique que le droit de lecture, le droit d ecriture ou le droit dex ecution est accord e ou non au propri etaire du chier, aux membres de son groupe de travail ou ` a tous les utilisateurs du syst` eme. Cela fait trois groupes de trois bits, soit un nombre qui s ecrit particuli` erement bien en octal. Par exemple : fic = creat("tmp/monfic", 0751); cr ee un chier nouveau et lui accorde les permissions : propri etaire groupe autres lecture 1 1 0 ecriture 1 0 0 ex ecution 1 1 1 en octal 7 5 1

int read(int fichier, char *adresse, int nombre) Lit nombre octets depuis le fichier indiqu e et les range ` a partir de ladresse indiqu ee. Renvoie le nombre doctets eectivement lus, qui peut etre inf erieur ` a nombre (n de chier, erreur de lecture, etc.). int write(int fichier, void *adresse, int nombre) Ecrit les nombre octets qui se trouvent ` a ladresse indiqu ee dans le fichier indiqu e. Rend le nombre doctets eectivement ecrits, qui peut etre inf erieur ` a nombre (erreur d ecriture, etc.). int close(int descfic) Ferme le chier indiqu e.

102

c H. Garreta, 1988-2013

ENTREES-SORTIES

7.5

Les chiers de bas niveau dUNIX

long lseek(int fichier, long rang, int origine) Positionne le chier indiqu e sur loctet ayant le rang indiqu e. Ce rang exprime une position... ...relative au d ebut du chier (si origine = 0) ...relative ` a la position courante (si origine = 1) ...relative ` a la n du chier (si origine = 2) Exemple. Voici la version UNIX (et, de plus, non ANSI) du programme, donn e plus haut, qui eectue une copie identique dun chier quelconque. Au sujet de lutilisation qui est faite des arguments de main voir la section 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, 1988-2013

103

EMENTS AUTRES EL DU LANGAGE C

Autres el ements du langage C

Cette section regroupe un ensemble de notions ayant en commun que leur int er et appara t surtout ` a loccasion de l ecriture de gros programmes. Ce sont des el ements importants du langage mais, sauf quelques exceptions, on peut sen passer aussi longtemps quon limite la pratique de C ` a des exercices de petite taille.

8.1

Le pr eprocesseur

Le pr eprocesseur transforme le texte source dun programme avant que la compilation ne commence. G en eralement associ ees au processus de la compilation, ses actions semblent en faire partie, mais il faut savoir que le pr eprocesseur est un programme s epar e, largement ind ependant du compilateur. En particulier le pr eprocesseur ne conna t pas la syntaxe du langage C. Les transformations quil eectue sur un programme sont simplement des ajouts, des suppressions et des remplacements de morceaux de texte nullement astreints ` a correspondre aux entit es syntaxiques du langage. Les directives destin ees au pr eprocesseur comportent, dans lordre : un signe # (qui, en syntaxe originale, doit occuper la premi` ere position de la ligne) ; un mot-cl e parmi include, define, if, ifdef, ifndef, else et endif qui identie la directive. Nous passerons sous silence quelques autres directives mineures comme pragma, line ou error ; le corps de la directive, dont la structure d epend de la nature de celle-ci. 8.1.1 Inclusion de chiers

La possibilit e dinclure des chiers sources dans dautres chiers sources sav` ere tr` es utile, par exemple pour ins erer dans chacun des chiers s epar es qui constituent un programme lensemble de leurs d eclarations communes (types, structures, etc.). Ainsi ces d enitions sont ecrites dans un seul chier, ce qui en facilite la maintenance. Un cas particulier tr` es important de ce partage des d eclarations est celui des biblioth` eques livr ees avec le compilateur. Elles sont essentiellement compos ees de fonctions utilitaires d ej` a compil ees dont seul le chier objet est fourni. Pour que les appels de ces fonctions puissent etre correctement compil es 62 dans des programmes qui les utilisent, un certain nombre de chiers en-t ete sont livr es avec les biblioth` eques, comprenant principalement, les prototypes des fonctions qui forment la biblioth` eque ou qui incarnent une fonctionnalit e particuli` ere (les entr ees - sorties, la gestion des cha nes de caract` eres, la biblioth` eque math ematique, etc.) ; souvent, un ensemble de directives #define et de d eclarations struct, union et typedef qui d enissent les constantes et les types n ecessaires pour utiliser la biblioth` eque en question ; parfois, quelques directives #include concernant dautres chiers en-t ete lorsque les el ements d enis dans le chier en-t ete en question ne peuvent eux-m emes etre compris sans ces autres chiers ; plus rarement, quelques d eclarations de variables externes. Cette directive poss` ede deux formes : #include "nom-de-chier " Cest la forme g en erale. Le chier sp eci e est ins er e` a lendroit o` u la directive gure avant que la compilation ne commence ; ce doit etre un chier source ecrit en C. La cha ne de caract` eres "nom-de-chier " doit etre un nom complet et correct pour le syst` eme dexploitation utilis e. La deuxi` eme forme est : #include <nom-de-chier > Ici, le nom est incomplet ; il ne mentionne pas le chemin dacc` es au chier, car il est convenu quil sagit dun chier appartenant au syst` eme, faisant partie de la biblioth` eque standard : le pr eprocesseur sait dans quels r epertoires ces chiers sont rang es. Ce sont des chiers dont le nom se termine par .h (pour header, en-t ete) et qui contiennent les d eclarations n ecessaires pour quun programme puisse utiliser correctement les fonctions de la biblioth` eque standard. Exemples (avec des noms de chier en syntaxe UNIX) : #include <stdio.h> #include "/users/henri/projet_grandiose/gadgets.h"
62. Notez bien quil ne sagit pas de fournir les fonctions de la biblioth` eque (cela est fait par lapport du chier objet au moment de l edition de liens) mais uniquement de donner linformation requise pour que les appels de ces fonctions puissent etre correctement traduits par le compilateur. Pour cette raison on ne trouve jamais dans les chiers en-t ete ni des variables non externes, ni le corps des fonctions.

104

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.1 Le pr eprocesseur

Attention. Il ne faut pas confondre inclusion de chiers et compilation s epar ee. Les chiers que lon inclut au moyen de la directive #include contiennent du texte source C qui sera compil e chaque fois que linclusion sera faite (cest pourquoi ces chiers ne contiennent jamais de fonctions). Le service rendu par la directive #include est de permettre davoir un texte en un seul exemplaire, non de permettre de le compiler une seule fois. 8.1.2 D enition et appel des macros

Les macros sans argument 63 se d enissent par la directive : #define nom corps Le nom de la macro est un identicateur. Le corps de la macro s etend jusqu` a la n de la ligne 64 . Partout dans le texte o` u le nom de la macro appara t en qualit e didenticateur (ce qui exclut les commentaires, les cha nes de caract` eres et les occurrences o` u le nom de la macro est coll e` a un autre identicateur), il sera remplac e par le corps de la macro, lequel est un texte quelconque nayant ` a ob eir ` a aucune syntaxe. Par exemple, entre lendroit o` u gurent les deux directives suivantes : #define NBLIGNES 15 #define NBCOLONNES (2 * NBLIGNES) et la n du chier o` u elles apparaissent, chaque occurrence de lidenticateur NBLIGNES sera remplac ee par le texte 15 et chaque occurrence de lidenticateur NBCOLONNES par le texte (2 * 15). Au moins dun point de vue 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 imm ediatement 2 * 15 par 30). Attention. Voici une erreur que lon peut faire : #define NBLIGNES 15; #define NBCOLONNES (2 * NBLIGNES); Cest une erreur dicile ` a d eceler, car le compilateur ne signalera ici rien de particulier. Mais plus loin il donnera pour erron ee une d eclaration comme : double matrice[NBLIGNES][NBCOLONNES]; En eet, cette expression qui para t sans d efaut aura et e transform ee par le pr eprocesseur en 65 double matrice[15;][(2 * 15;);]; Macros avec arguments. Elles se d enissent par des expressions de la forme : #define nom (ident 1 , ... ident k ) corps Il ne doit pas y avoir de blanc entre le nom de la macro et la parenth` ese ouvrante. Comme pr ec edemment, le corps de la macro s etend jusqu` a la n de la ligne. Les identicateurs entre parenth` eses sont les arguments de la macro ; ils apparaissent aussi dans le corps de celle-ci. Les utilisations (on dit les appels ) de la macro se font sous la forme nom (texte 1 , ... texte k ) Cette expression sera remplac ee par le corps de la macro, dans lequel ident 1 aura et e remplac e par texte 1 , ident 2 par texte 2 , etc. Exemple 66 : #define permuter(a, b, type) { type w_; w_ = a; a = b; b = w_; } Exemple dappel : permuter(t[i], t[j], short *) R esultat de la substitution (on dit aussi d eveloppement ) de la macro : { short * w_; w_ = t[i]; t[i] = t[j]; t[j] = w_; }
63. Le mot macro est une abr eviation de lexpression macro-instruction d esignant un m ecanisme qui existe depuis longtemps dans beaucoup dassembleurs. Les macros avec arguments sont appel ees aussi pseudo-fonctions. 64. Mais il d ecoule de la section 1.2.1 que le corps dune macro peut occuper en fait plusieurs lignes : il sut que chacune delles, sauf la derni` ere, se termine par le caract` ere \. 65. Pour permettre de trouver ce genre derreurs, certains compilateurs C poss` edent une option (sous UNIX loption -E) qui produit lachage du programme source uniquement transform e par le pr eprocesseur. 66. Dans cet exemple, les accolades { et } donnent au corps de la macro une structure de bloc qui sera comprise et exploit ee par le compilateur, mais cette structure est parfaitement indi erente au pr eprocesseur.

c H. Garreta, 1988-2013

105

8.1

Le pr eprocesseur

EMENTS AUTRES EL DU LANGAGE C

Les appels de macros ressemblent beaucoup aux appels de fonctions. La m eme facilit e, ne pas r ecrire un certain code source, peut etre obtenue avec une macro et avec une fonction. Mais il faut comprendre que ce nest pas du tout la m eme chose. Alors que le corps dune fonction gure en un seul exemplaire dans le programme ex ecutable dont elle fait partie, le corps dune macro est recopi e puis compil e` a chaque endroit o` u gure lappel de la macro. Il faut donc que le corps dune macro soit r eduit, sans quoi son utilisation peut nir par etre tr` es on ereuse en termes despace occup e. Un autre inconv enient des macros est que leur d enition hors syntaxe les rend tr` es diciles ` a ma triser au-del` a dun certain degr e de complexit e (pas de variables locales, etc.). En faveur des macros : leur ecacit e. Le texte de la macro etant recopi e` a la place de lappel, on gagne ` a chaque fois le co ut dun appel de fonction. Il e ut et e bien peu ecace dappeler une fonction pour ne faire que la permutation de deux variables ! Autre int er et des macros, elles sont ind ependantes des types de leurs arguments ou m eme, comme ci-dessus, elles peuvent avoir un type pour argument. En d enitive, la principale utilit e des macros est dam eliorer lexpressivit e et la portabilit e des programmes en centralisant la d enition dop erations compliqu ees, proches du mat eriel ou sujettes ` a modication. Voici un exemple el ementaire : en C original (dans lequel on ne dispose pas du type void *) la fonction malloc doit etre g en eralement utilis ee en association avec un changement de type : p = (machin *) malloc(sizeof(machin)); Des telles expressions peuvent gurer ` a de nombreux endroits dun programme. Si nous d enissons la macro #define NOUVEAU(type) ((type *) malloc(sizeof(type))) alors les allocations despace m emoire s ecriront de mani` ere bien plus simple et expressive p = NOUVEAU(machin); De plus, un eventuel remplacement ult erieur de malloc par un autre proc ed e dobtention de m emoire sera facile et able, puisquil ny aura quun point du programme ` a modier. Attention. 1. Il est quasiment obligatoire d ecrire les arguments et le corps des macros entre parenth` eses, pour eviter des probl` emes li es aux priorit es des op erateurs. Par exemple, la macro #define TVA(p) 0.196 * p est tr` es imprudente, car une expression comme (int) TVA(x) sera d evelopp ee en (int) 0.196 * x, ce qui vaut toujours 0. Premi` ere correction : #define TVA(p) (0.186 * p) Le probl` eme pr ec edent est corrig e, mais il y en a un autre : lexpression TVA(p1 + p2) est d evelopp ee en (0.186 * p1 + p2), ce qui est probablement erron e. Do` u la forme habituelle : #define TVA(p) (0.186 * (p)) 2. Il ne faut jamais appeler une macro inconnue avec des arguments qui ont un eet de bord, car ceux-ci peuvent etre evalu es plus dune fois. Exemple classique : on dispose dune macro nomm ee toupper qui transforme toute lettre minuscule en la majuscule correspondante, et ne fait rien aux autres caract` eres. Elle pourrait etre ainsi d enie (mais on nest pas cens e 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 tr` es mauvais appel de cette macro : calu = toupper(getchar()) En eet, cette expression se d eveloppe en calu = (a <= (getchar()) && (getchar()) <= z ? (getchar()) + (A - a) : (getchar())); A chaque appel, au moins deux caract` eres sont lus, ce qui nest s urement pas leet recherch e. On notera que si toupper avait et e une fonction, lexpression toupper(getchar()) aurait et e tout ` a fait correcte. Mais puisque ce nen est pas une, il faut lappeler avec un argument sans eet de bord. Par exemple : { calu = getchar(); calu = toupper(calu); }

8.1.3

Compilation conditionnelle

Les directives de conditionnelle (compilation) se pr esentent sous les formes suivantes : 106
c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C #if expression #ifdef identicateur #ifndef identicateur ... texte compil e si la condition est vraie, ignor e si elle est fausse ... #else ... texte compil e si la condition est fausse, ignor e si elle est vraie ... #endif La partie else est facultative ; on a donc droit aussi aux formes : #if expression #ifdef identicateur #ifndef identicateur ... texte compil e si la condition est vraie, ignor e si elle est fausse ... #endif

8.1 Le pr eprocesseur

Lorsque le premier el ement de cette construction est #if expression, expression doit pouvoir etre evalu ee par le pr eprocesseur. Ce doit donc etre une expression constante ne contenant pas lop erateur de changement de type ni les op erateurs sizeof ou &. Elle sera evalu ee et si elle est vraie, cest-` a-dire non nulle, alors le texte qui se trouve entre #if et #else sera trait e par le compilateur, tandis que celui qui gure entre #else et #endif sera purement et simplement tenu pour inexistant ; si elle est fausse, cest-` a-dire nulle, cest le premier texte qui sera ignor e tandis que le second sera lu par le compilateur. Dans la forme sans #else, si la condition est fausse aucun texte ne sera compil e. Les directives #ifdef identicateur #ifndef identicateur equivalent respectivement ` a #if #if

identicateur est le nom dune macro actuellement d enie identicateur nest pas le nom dune macro actuellement d enie

La compilation conditionnelle se r ev` ele etre un outil pr ecieux pour contr oler les parties des programmes qui d ependent de la machine ou du syst` eme sous-jacent. On obtient de la sorte des textes sources qui restent portables malgr e le fait quils contiennent des el ements non portables. Voici un exemple : les fonctions de lecture ecriture sur des chiers binaires ne sont pas les m emes dans la biblioth` eque ANSI et dans la biblioth` eque UNIX classique. Une mani` ere d ecrire un programme ind ependant de ce fait consiste ` a enfermer les appels de ces fonctions et les d eclarations qui sy rapportent dans des s equences conditionnelles. D eclaration : ... #ifdef BINAIRES_ANSI FILE *fic; #else int fic; #endif ... Ouverture : ... #ifdef BINAIRES_ANSI fic = fopen(nom, "r"); ok = (fic != NULL); #else fic = open(nom, 0); ok = (fic >= 0); #endif ...
c H. Garreta, 1988-2013

107

8.2

La modularit e de C

EMENTS AUTRES EL DU LANGAGE C

Lecture : ... #ifndef BINAIRES_ANSI ok = (fread(&art, sizeof(art), 1, fic) == 1); #else ok = (read(fic, &art, sizeof(art)) == sizeof(art)); #endif ... Lemploi de la compilation conditionnelle associ ee ` a la d enition de noms de macros est rendu encore plus commode par le fait quon peut utiliser des noms de macros normalement d enis (#define...) ; des noms de macros d enis dans la commande qui lance la compilation, ce qui permet de changer le texte qui sera compil e sans toucher au chier source. De plus, puisque ces d enitions se font au niveau de la commande de compilation, elles pourront etre faites par des proc edures de commandes (scripts) donc automatis ees ; un ensemble de noms de macros pr ed enis dans chaque syst` eme, pour le caract eriser. Par exemple dans le compilateur des syst` emes UNIX la macro UNIX est d enie et vaut 1. Remarque. Le pr eprocesseur du C ANSI ore egalement lop erateur defined qui permet d ecrire les directives #ifdef ident (resp. #ifndef ident ) sous la forme #if defined ident (resp. #if !defined ident ). Dautre part, la construction #if expression1 ... #else #if expression2 ... #endif #endif peut en C ANSI sabr eger en #if expression1 ... #elif expression2 ... #endif

8.2

La modularit e de C

A ce point de notre expos e nous pouvons nous poser une question importante : le langage C est-il modulaire ? La modularit e est une qualit e que les bonnes m ethodes de conception doivent poss eder. Elle nest pas facile ` a d enir ; on peut toutefois la cerner ` a travers quelques crit` eres 67 . Une m ethode de conception doit aider ` a diviser chaque nouveau probl` eme en sous-probl` emes quon peut r esoudre s epar ement (crit` ere de d ecomposabilit e) ; favoriser la production de composants logiciels qui peuvent se combiner librement pour produire de nouveaux syst` emes (crit` ere de composabilit e) ; ecrire des modules dont chacun peut etre compris isol ement par un lecteur permettre au concepteur d humain (crit` ere de compr ehensibilit e) ; permettre quune petite modication des sp ecications du probl` eme entra ne uniquement la modication dun petit nombre des modules de la solution (crit` ere de continuit e) ; e` a ce module assurer que leet dune condition anormale se produisant dans un module restera localis ou, au pire, natteindra quun petit nombre de modules voisins ( crit` ere de protection ). Th eoriquement, tout langage de programmation peut servir ` a mettre en uvre une m ethode de conception modulaire, mais dans certains cas il faut une telle dose de ruse et dautodiscipline, pour un r esultat si peu able, que le jeu nen vaut pas la chandelle. En pratique un langage est dit modulaire lorsquon peut, sans douleur et sans artice, en faire loutil dune m ethode de conception modulaire. Lexp erience montre que les crit` eres pr ec edents induisent sur le langage en question quelques contraintes assez pr ecises, comme celles-ci : a des entit es syntaxiques du langage ; les modules doivent correspondre `
67. Suivant lanalyse de Bertrand Meyer (Conception et programmation par objets, InterEditions, 1990) ` a laquelle il semble dicile dajouter ou de retrancher quelque chose.

108

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.2

La modularit e de C

chaque module doit partager des informations avec aussi peu dautres modules que possible, et quand un tel partage existe il doit concerner aussi peu d el ements que possible ; quand deux modules partagent des informations, cela doit etre clairement indiqu e dans leurs deux textes. A la lumi` ere de ces principes la modularit e de C appara t fort rudimentaire, pour ne pas dire inexistante. Si lon prend pour modules les chiers sources qui composent un programme, on constate quaucune structure syntaxique ne signale les modules ni nen d elimite la port ee, dautant plus que le caract` ere non syntaxique de la directive #include brouille lorganisation du programme en chiers distincts. Aucune d eclaration particuli` ere nest requise pour indiquer que des objets sont partag es entre plusieurs modules. Chaque module communique avec tous les autres et, sauf sp ecication contraire, tous les objets de chaque module sont partag es. Bien s ur, la compilation s epar ee est une des id ees-cl es du langage, et il est possible de rendre inaccessibles les noms qui peuvent etre priv es. Mais le langage ore peu doutils pour rendre able le partage des noms qui doivent etre publics, et ces outils restent dun emploi facultatif, subordonn e` a lautodiscipline du programmeur. Par exemple, si dans un module B on doit r ef erencer une variable ou une fonction d enie dans un module A, il sut d ecrire dans B une d eclaration comme extern int x;. Cet enonc e postule lexistence dun objet nomm e x, ce qui sera contr ol e par l editeur de liens. Mais il donne ` a x des attributs (la classe variable, le type int) que lobjet d esign e par x ne poss` ede pas forc ement ; aucune v erication cependant ne sera faite. Ainsi la compilation de B se fait dans la plus totale ins ecurit e.

8.2.1

Fichiers en-t ete

Le seul moyen dont dispose lauteur dun module A pour sassurer que les autres modules qui forment un programme utilisent correctement les variables et fonctions quil rend publiques consiste ` a ecrire un chier ent ete (chier A.h) contenant toutes les d eclarations publiques. Ce chier doit etre inclus par la directive #include dans le module qui implante les objets publics (chier A.c) et dans chacun des modules qui les utilisent. De cette mani` ere tous ces chiers voient les m emes d enitions de types, les m emes d eclarations de variables et les m emes prototypes de fonctions ; ces d eclarations sont ecrites en un seul endroit, et toute modication de lune dentre elles se r epercute sur tous les chiers qui en d ependent. La n ecessit e de ces chiers en-t ete appara t encore plus grande quand on consid` ere le cas des biblioth` eques, cest-` a-dire des modules que leurs fonctionnalit es placent en position de prestataires de services vis-` a-vis des autres modules qui composent un programme ; on parle alors de module serveur et de modules clients. En fait, on peut presque toujours voir la modularit e en termes de serveurs et clients, car il y a toujours une hi erarchie parmi les modules. Le propre des biblioth` eques est d etre con cues de mani` ere ind ependante des clients, an de pouvoir etre utilis ees dans un programme pr esent et un nombre quelconque de programmes futurs. Lint er et de leur associer le meilleur dispositif pour minimiser le risque de mauvaise utilisation est evident. Typiquement, un chier en-t ete comportera les el ements suivants : Des directives #include concernant les autres chiers en-t ete n ecessaires pour la compr ehension (par le compilateur) des el ements qui apparaissent dans le chier en-t ete en question. Des d enitions de constantes, soit sous forme de directives #define soit sous forme de type enum er e, qui sont des informations symboliques echang ees entre le serveur et ses clients. Exemple : dans une biblioth` eque graphique, les noms conventionnels des couleurs. Des d enitions de structures (struct, union) et de types (typedef) qui d enissent la nature des objets manipul es par la biblioth` eque. Typiquement, ces types permettent aux clients de d eclarer les objets qui sont les arguments et les r esultats des fonctions de la biblioth` eque. Exemple : dans une biblioth` eque graphique, la d enition de types point, segment, etc. eclarations extern des variables publiques du serveur. Les d enitions correspondantes (sans le quali Les d eur extern) gureront dans le module serveur. Remarque. Lemploi de variables publiques est d econseill e ; un module ne devrait orir que des fonctions 68 . eclarations des fonctions publiques du serveur. Les d enitions correspondantes gureront dans le Les d module serveur. En syntaxe originale seules les d eclarations des fonctions qui ne rendent pas un entier sont n ecessaires, mais m eme dans ce cas cest une bonne habitude que dy mettre les d eclarations de toutes les fonctions, cela constitue un germe de documentation. Bien entendu, tous les noms de variables et fonctions du module serveur qui ne gurent pas dans le chier en-t ete doivent etre rendus priv es (en les qualiant static).
68. Sil est utile quun client puisse consulter ou modier une variable du serveur, ecrivez une fonction qui ne fait que cela (mais en contr olant la validit e de la consultation ou de la modication). Cest bien plus s ur que de donner libre acc` es ` a la variable.

c H. Garreta, 1988-2013

109

8.2

La modularit e de C

EMENTS AUTRES EL DU LANGAGE C

8.2.2

Exemple : stdio.h

A titre dexemple visitons rapidement le plus utilis e des chiers en-t ete de la biblioth` eque standard : stdio.h. Notre but nest pas de prolonger ici l etude des entr ees-sorties, mais dillustrer les indications du paragraphe pr ec edent sur l ecriture des chiers en-t ete. Le texte ci-apr` es est fait de morceaux de la version MPW (Macintosh Programmer Workshop ) du chier stdio.h auquel nous avons enlev e 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_SET 0 #define SEEK_CUR 1 #define SEEK_END 2

/* * 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 *); /* * The */ #define #define #define standard predefined streams stdin stdout stderr (&_iob[0]) (&_iob[1]) (&_iob[2])

/* * File access functions */ int fclose (FILE *); int fflush (FILE *); FILE *fopen (const char *, const char *); int setvbuf (FILE *, char *, int, size_t); 110

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.2

La modularit e de C

/* * */ int int int int int int

Formatted input/output functions printf (const char *, ...); fprintf (FILE *, const char *, ...); sprintf (char *, const char *, ...); scanf (const char *, ...); fscanf (FILE *, const char *, ...); 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 chiers en-t ete sont dun int er et g en eral ils nissent par etre inclus dans de nombreux autres chiers, eux-m emes inclus les uns dans les autres. Le compilateur risque alors de lire plusieurs fois un m eme chier, ce qui entra ne un travail inutile et parfois des erreurs li ees ` a des red enitions. Par exemple, imaginons que MonBazar.h soit un chier comportant la directive #include <stdio.h>. Dans la compilation dun chier commen cant par les deux directives #include <stdio.h> #include "MonBazar.h" le compilateur risquerait de compiler deux fois le chier stdio.h. Les deux premi` eres directives de ce chier r esolvent ce probl` eme : lorsque ce chier est pris en compte une premi` ere fois, le nom __STDIO__ (nom improbable arbitrairement associ e ` a stdio.h) devient d eni. Durant la pr esente compilation, la directive #ifndef __STDIO__ fera que lint erieur du chier ne sera plus jamais lu par le compilateur. Tous les chiers en-t ete peuvent etre prot eg es de cette mani` ere contre les inclusions multiples. On peut noter ici que la vue du type FILE quont les modules clients est tr` es di erente de celle quils auraient en utilisant un langage tr` es modulaire comme Modula II ou Ada. Dans ces langages les noms et les types des champs dune structure comme FILE resteraient priv es, ainsi que la structure elle-m eme ; seul le type adresse dune telle structure serait rendu public. Cela sappelle un type opaque et augmente la abilit e des programmes. En C nous navons pas doutils pour proc eder de la sorte : ou bien un type est priv e, connu uniquement du module serveur, ou bien il faut tout dire ` a son sujet. Lutilisation dun pointeur g en erique, dans le style de typedef void *FILE_ADDRESS; dissimulerait bien linformation sur la structure FILE, mais aurait la cons equence de rendre impossibles ` a d eceler les mauvaises utilisations du type en question par le client. Notez dautre part que la connaissance
c H. Garreta, 1988-2013

111

8.3

Deux ou trois choses bien pratiques...

EMENTS AUTRES EL DU LANGAGE C

dans les modules clients des noms des champs de la structure FILE est indispensable pour lutilisation des macros comme getc ou putc. Tous les noms qui apparaissent dans le chier en-t ete deviennent de ce fait publics, m eme ceux quon aurait aim e garder priv es alors quon ne le peut pas, par exemple parce quils apparaissent dans le corps dune macro, comme ici le nom de la table des descripteurs de chiers iob. Le langage C nayant rien pr evu ` a ce eet, la privacit e nest assur ee que par une convention entre le syst` eme et les utilisateurs : eme et lutilisateur doit feindre den tout nom commen cant par un blanc soulign e appartient au syst` ignorer lexistence. Ceci nest pas la meilleure mani` ere de donner les prototypes des fonctions dans un chier en-t ete. Dans une d eclaration qui nest pas une d enition, la syntaxe nexige pas les noms des arguments mais uniquement leurs types. Cependant, sils sont bien choisis, ces noms apportent une information suppl ementaire qui augmente la s ecurit e dutilisation de la biblioth` eque. Par exemple, les prototypes FILE *fopen(const char *, const char *); size_t fread(void *, size_t, size_t, FILE *); ne seraient daucune utilit e` a un programmeur qui h esiterait ` a propos du r ole ou de lordre des arguments de 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 b en ecient pas des v erications syntaxiques que le C ANSI eectue lorsque la fonction a fait lobjet dune d enition de prototype. Sauf pour les arguments nomm es (le premier ou les deux premiers), les arguments que lon passe ` a lune de ces six fonctions echappent donc ` a tout contr ole du compilateur. Linformation concernant la nature de ces arguments est port ee par le format ; elle ne pourra etre exploit ee qu` a lex ecution.

Autres remarques. Le premier client de ce dispositif doit etre le fournisseur lui-m eme. Pour que la protection contre lerreur recherch ee soit eective, il faut que chacun des chiers qui r ealisent limplantation des variables et fonctions promises dans stdio.h comporte la directive #include <stdio.h> De cette mani` ere on garantit que lauteur et lutilisateur de chaque variable ou fonction sont bien daccord sur la d enition de lentit e en question. Une autre r` egle ` a respecter par lauteur du ou des modules serveurs : qualier static tous les noms qui ne sont pas d eclar es dans le chier en-t ete, pour eviter les collisions de noms. Le chier en-t ete joue ainsi, pour ce qui concerne les variables et les fonctions, le r ole de liste ocielle des seuls noms publics.

8.3
8.3.1

Deux ou trois choses bien pratiques...


Les arguments du programme principal

Cette section ne concerne que les environnements, comme UNIX ou MS-DOS, dans lesquels les programmes sont activ es en composant une commande de la forme nom-du-programme argument 1 ... argument k . Lex ecution dun programme commence par la fonction main. Tout se passe comme si le syst` eme dexploitation avait appel e cette fonction comme une fonction ordinaire. Il faut savoir que lors de cet appel, des arguments sont fournis au programme. Voici len-t ete complet de main, en syntaxe ANSI : int main(int argc, char *argv[]) avec : argc : nombre darguments du programme argv : tableau de cha nes de caract` eres, qui sont les arguments du programme. Par convention, le premier argument est le nom du programme lui-m eme. Imaginons avoir ecrit un programme qui, une fois compil e, se nomme echo ; supposons que ce programme soit lanc e par la commande : echo Pierre Paul alors, main re coit les arguments que montre la gure 18. Supposons que tout ce que lon demande ` a echo soit de recopier la liste de ses arguments. Voici comment on pourrait ecrire ce programme : 112
c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.3

Deux ou trois choses bien pratiques...

e c h o\ 0 argc 3 argv

P i e r r e\ 0

P a u l\ 0

NULL
Figure 18 Arguments de main

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 La principale application de ce m ecanisme est la fourniture ` a un programme des param` etres dont il peut d ependre, comme des noms de chiers, des tailles de tableaux, etc. Par exemple, voici une nouvelle version du programme de copie de chiers donn e` a la section 7.4.1, qui prend les noms des chiers source et destination comme arguments : #include <stdio.h> #define #define #define #define PAS_D_ERREUR ERREUR_OUVERTURE ERREUR_CREATION PAS_ASSEZ_D_ARGUMENTS 0 1 2 3

echo Pierre Paul produit lachage de

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; while ((nombre = fread(tampon, 1, 512, srce)) > 0) fwrite(tampon, 1, nombre, dest); fclose(dest); return PAS_D_ERREUR; } Si nous appelons copier le chier ex ecutable produit en compilant le texte ci-dessus, alors nous pourrons lex ecuter en composant la commande copier chier-source chier-destination
c H. Garreta, 1988-2013

113

8.3

Deux ou trois choses bien pratiques...

EMENTS AUTRES EL DU LANGAGE C

8.3.2

Branchements hors fonction : setjmp.h

Le m ecanisme des longs branchements permet dobtenir la terminaison imm ediate de toutes les fonctions qui ont et e appel ees (et ne sont pas encore termin ees) depuis que le contr ole est pass e par un certain point du programme, quel que soit le nombre de ces fonctions. Il est r ealis e` a laide des deux fonctions : int setjmp(jmp_buf contexte); void longjmp(jmp_buf contexte, int code); Cela fonctionne de la mani` ere suivante : tout dabord il faut d eclarer une variable, g en eralement globale, de type jmp buf (type d eni dans le chier en-t ete setjmp.h) : #include <setjmp.h> ... jmp_buf contexte; Lappel setjmp(contexte); enregistre dans contexte certaines informations traduisant l etat du syst` eme, puis renvoie z ero. Ensuite, lappel longjmp(contexte, valeur); remet le syst` eme dans l etat qui a et e enregistr e dans la variable contexte. Plus pr ecis ement, le syst` eme se trouve comme si lappel de setjmp(contexte) venait tout juste de se terminer, en rendant cette fois non pas z ero mais la valeur indiqu ee dans lappel de longjmp. Le principal service rendu par ce dispositif est de permettre de programmer simplement, et en ma trisant le point de chute , labandon dune famille de fonctions qui se sont mutuellement appel ees. Examinons un exemple classique : supposons quune certaine fonction expression soit un analyseur syntaxique pr esentant deux caract eristiques fr equentes : la fonction expression est ` a lorigine dune imbrication dynamique fort complexe (expression appelle une fonction terme, qui appelle une fonction facteur qui ` a son tour appelle une fonction primaire laquelle rappelle expression, etc.) ; chacune des fonctions expression, terme, facteur, etc., peut ` a tout moment rencontrer une erreur dans ses donn ees, qui rend la poursuite du traitement inutile ou impossible.

main analyse()

analyse

setjmp

setjmp(c) expression expression() longjmp(c,v) terme

terme()

Figure 19 Branchement hors fonction

Voici comment lappel de expression pourrait etre emball e dans une fonction analyse (voir la gure 19) : 114

enveloppe

nomm ee

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.3

Deux ou trois choses bien pratiques...

#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 activ ee, setjmp est appel ee et rend 0 ; elle est donc imm ediatement suivie par lappel de expression. Si le travail de expression se termine normalement, alors analyse rendra SUCCES et elle aura et e transparente. Dautre part, dans la fonction expression et toutes celles appel ees au-dessus delle (terme, facteur, etc.), il est possible deectuer lappel longjmp(contexte, 1); qui ram` ene le contr ole dans la fonction analyse, exactement sur la partie condition de linstruction if (setjmp(contexte) == 0) mais cette fois la valeur rendue par setjmp sera 1 et analyse rendra donc la valeur ERREUR. Remarques. 1. Lappel de longjmp se pr esente comme une remise du syst` eme dans lun des etats par lesquels il est pass e. Cela est vrai, sauf pour ce qui concerne la valeur des variables, notamment les variables globales : elles ne reprennent pas la valeur quelles avaient lorsque longjmp a et e appel ee. 2. On ne peut eectuer un appel de longjmp que pour ramener le syst` eme dans lune des fonctions appel ees et non encore termin ees, pour laquelle lespace local est encore allou e dans la pile. Appeler longjmp avec un contexte qui a et e m emoris e dans une fonction dont lactivation est termin ee est une erreur aux cons equences ind enies, m eme si cette fonction a et e rappel ee depuis. Par exemple, un appel longjmp(c,v) qui serait plac e dans la fonction main apr` es lappel analyse() (voir la gure 19) serait erron e, car il utiliserait le contexte dune activation de fonction (la fonction analyse) qui nexiste plus. 8.3.3 Interruptions : signal.h

Lobjet de la fonction signal est la d etection d ev enements asynchrones qui peuvent se produire pendant lex ecution dun programme. Asynchrones signie quon ne peut pas pr evoir le moment o` u ils se produiront, car ils ne r esultent pas dinstructions normalement ins er ees dans la s equence qui forme le programme. Un certain ensemble de types d ev enements, d ependant de chaque syst` eme, est r ecup erable ` a travers le m ecanisme d ecrit ici. En g en eral on traite de la m eme mani` ere le vrai et le faux asynchronisme. Une coupure de courant ou une interruption provoqu ee par lutilisateur sont des ev enements vraiment impr evisibles. Une r ef erence ` a travers un pointeur invalide ou une division par z ero ne sont pas r eellement asynchrones (si on avait connu parfaitement le programme et les donn ees on aurait pu pr edire tr` es exactement de tels ev enements), mais il est commode de les consid erer comme tels et de les r ecup erer de la m eme mani` ere. La fonction signal est d eclar ee dans le chier signal.h de la fa con suivante : void (*signal(int numero, void (*manip)(int)))(int); Ce prototype nest pas tr` es facile ` a lire. D enissons le type PROCEDURE comme celui dune fonction sans r esultat d eni ayant un argument de type int : typedef void PROCEDURE(int); Avec cela, la d eclaration pr ec edente se r ecrit plus simplement : PROCEDURE *signal(int numero, PROCEDURE *manip); et elle nous apprend que la fonction signal prend deux arguments, ` a savoir un int et ladresse dune PROCEDURE, et renvoie ladresse dune PROCEDURE. Lors dun appel de signal, largument numero doit d esigner un des ev enements quil est possible de r ecup erer. Leet de signal est denregistrer la PROCEDURE donn ee en argument, de telle mani` ere que si l ev enement en question se produit, alors elle sera automatiquement appel ee par le syst` eme avec pour argument le num ero de l ev enement. La fonction signal rend ladresse de la PROCEDURE qui etait jusqualors enregistr ee pour ce m eme ev enement.
c H. Garreta, 1988-2013

115

8.4

La biblioth` eque standard

EMENTS AUTRES EL DU LANGAGE C

Six ev enements sont pr evus par le standard ANSI (ils ne sont pas tous implant es dans tous les syst` emes ; en outre, chaque syst` eme peut ajouter ses propres particularit es) : SIGABRT : n anormale du programme (appel de la fonction abort) SIGINT : interruption provoqu ee par lutilisateur (touche Ctrl-C, etc.) SIGTERM : demande darr et de lutilisateur (interruption forte ) SIGFPE : erreur arithm etique (division par z ero, etc.) SIGSEGV : acc` es m emoire ill egal (souvent : mauvais pointeur dans lacc` es ` a une donn ee, d ebordement de tableau) SIGILL : instruction ill egale (souvent : mauvais pointeur dans lappel dune fonction) La biblioth` eque fournit en outre deux m ecanismes de r ecup eration dune interruption pr ed enis : SIG DFL : le m ecanisme par d efaut utilis e par le syst` eme pour l ev enement en question lorsque la fonction signal na pas et e appel ee SIG IGN : le m ecanisme trivial qui consiste ` a ignorer l ev enement Exemple. Supposons quune section dun certain programme eectue des calculs tr` es complexes dont la dur ee risque dinqui eter lutilisateur nal. On souhaite donc orir ` a ce dernier la possibilit e dinterrompre le programme lorsquil estime que celui-ci devient excessivement long, en appuyant sur une touche particuli` ere 69 . Lutilisateur apprendra alors l etat davancement de son calcul (traduit par la valeur de la variable iteration numero) et aura ` 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("Jen suis a literation: %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 op erations ... /* entr ee dans la section on ereuse */ maniprec = signal(SIGINT, voirSkissPass); ... calculs terriblement complexes ... /* sortie de la section on ereuse */ signal(SIGINT, maniprec); ... autres op erations ... }

8.4

La biblioth` eque standard

La biblioth` eque standard ANSI se compose de deux sortes d el ements : un ensemble de chiers objets 70 contenant le code compil e des fonctions de la biblioth` eque et participant,
69. Sur plusieurs syst` emes, comme UNIX et VMS, il sagit de la combinaison des deux touches Ctrl et C . 70. Il sagit en r ealit e de chiers biblioth` eques (extensions .a, .lib, .dll, etc.), mais de tels chiers ne sont que des chiers objets un peu arrang es pour en faciliter lemploi par les editeurs de liens.

116

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.4 La biblioth` eque standard

pour la plupart sans quil y soit n ecessaire de lindiquer explicitement, 71 ` a l edition de liens de votre programme, un ensemble de chiers en-t ete contenant les d eclarations n ecessaires pour que les appels de ces fonctions puissent etre correctement compil es. Ces chiers en-t ete sont organis es par th` emes ; nous reprenons cette organisation pour expliquer les principaux el ements de la biblioth` eque. Des parties importantes de la biblioth` eque standard ont d ej` a et e expliqu ees ; notamment : la biblioth` eque des entr ees - sorties (associ ee au chier stdio.h), ` a la section 7 ; les listes variables darguments (introduites dans le chier stdarg.h) ` a la section 4.3.4 ; les branchements hors-fonction (d eclar es dans le chier setjmp.h) ` a la section 8.3.2 ; la r ecup eration des interruptions (avec le chier signal.h) ` a la section 8.3.3. Nous passerons sous silence certains modules mineurs (comme la gestion de la date et lheure). Au besoin, reportez-vous ` a la documentation de votre syst` eme.

8.4.1

Aide ` a la mise au point : assert.h

Cette

biblioth` eque ne contient aucune fonction. Elle se compose dune macro unique :

void assert(int expression ) qui fonctionne de la mani` ere suivante : si lexpression indiqu ee est vraie (cest-` a-dire non nulle) au moment o` u la macro est evalu ee, il ne se passe rien. Si lexpression est fausse (c.-` a-d. si elle vaut z ero), un message est imprim e sur stderr, de la forme Assertion failed: expression , file chier , line numero ensuite lex ecution du programme est avort ee. Exemple na f : #include <assert.h> ... #define TAILLE 100 int table[TAILLE]; ... for (i = 0; j < TAILLE; i++) { ... assert(0 <= i && i < TAILLE); table[i] = 0; ... } lex ecution de ce programme donnera (puisquune faute de frappe a rendu innie la boucle for ) : Assertion failed: 0 <= i && i < TAILLE, file ex.c, line 241 La macro assert est un outil pr ecieux pour la mise au point des programmes. Sans elle, il arrive seuvent quune situation anormale produite en un point dun programme ne se manifeste quen un autre point sans rapport avec avec le premier ; il est alors tr` es dicile de remonter depuis la manifestation du d efaut jusqu` a sa cause. Beaucoup de temps peut etre gagn e en postant de telles assertions aux endroits d elicats , o` u les variables du programme doivent satisfaire des contraintes cruciales. Il est clair, cependant, que cet outil sadresse au programmeur et nest utile que pendant le d eveloppement du programme. Les appels de assert ne doivent pas gurer dans lex ecutable livr e` a lutilisateur nal, pour deux raisons : dune part, ce dernier na pas ` a conna tre des d etails du programme source (en principe il ne conna t m eme pas le langage de programmation employ e), dautre part parce que les appels de assert ralentissent les programmes. Lorsque la mise au point dun programme est termin ee, on doit donc neutraliser tous les appels de assert qui y gurent. Cela peut sobtenir en une seule op eration, simplement en d enissant en t ete du programme 72 lidenticateur NDEBUG : #define NDEBUG peu importe la valeur
71. Notez que, sous UNIX, lorsque des chiers de la biblioth` eque standard ne sont pas implicites, il y a un moyen simple de les indiquer explicitement. Par exemple, loption -lm dans la commande gcc sp ecie lapport de la biblioth` eque /lib/libm.a (ce chier contient le code des fonctions math ematiques). 72. Sous UNIX il nest m eme pas n ecessaire dajouter une ligne au programme : il sut de composer la commande gcc en sp eciant une option -D , comme ceci : gcc -DNEBUG -o monprog monprog.c etc.

c H. Garreta, 1988-2013

117

8.4

La biblioth` eque standard

EMENTS AUTRES EL DU LANGAGE C

juste avant deectuer la compilation nale du programme. Celle-ci se fera alors comme si tous les appels de assert avaient et e gomm es. N.B. Le fait quil soit possible et utile de faire dispara tre les appels de assert du programme ex ecutable nal montre bien que assert nest pas la bonne mani` ere de d etecter des erreurs que le programmeur ne peut pas eviter, comme les erreurs dans les donn ees saisies. Ainsi sauf pour un programme eph em` ere ` a usage strictement personnel le code suivant nest pas acceptable : ... scanf("%d", &x); assert(0 <= x && x <= N); ...

/* NON ! */

La v erication que 0 <= x && x <= N porte sur une valeur lue ` a lex ecution et garde tout son int er et dans le programme nal, lorsque les assert sont d esarm es. Cest pourquoi elle doit plut ot etre programm ee avec du vrai code : ... scanf("%d", &x); if( ! (0 <= x && x <= N)) { fprintf(stderr, "Erreur. La valeur de x doit ^ etre comprise entre 0 et %d\n", N); exit(-1); } ... 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 lint (resp. le long, le double) dont la cha ne s est lexpression ecrite. Exemple int i; char *s; ... aectation de s ... i = atoi(s); maintenant i a la valeur num erique dont la cha ne s est lexpression textuelle Note. Pour faire le travail inverse de atoi ou une autre des fonctions pr ec edentes on peut employer sprintf selon le sch ema suivant : int i; char s[80]; ... aectation de i ... sprintf(s, "%d", i); maintenant la cha ne s est lexpression textuelle de la valeur de i int rand(void) Le ieme appel de cette fonction rend le ieme terme dune suite dentiers pseudo-al eatoires compris entre 0 epend que de la valeur de et la valeur de la constante RAND MAX, qui vaut au moins 32767. Cette suite ne d la semence donn ee lors de lappel de srand, voir ci-dessous. Si srand na pas et e appel ee, la suite obtenue est celle qui correspond ` a srand(1). Voyez srand ci apr` es. void srand(unsigned int semence) Initialise une nouvelle suite pseudo-al eatoire, voir rand ci-dessus. Deux valeurs di erentes de la semence eloign ees ou proches, peu importe donnent lieu ` a des suites tout ` a fait di erentes, du moins ` a partir du deuxi` eme terme. Application. Pour que la suite al eatoire fournie par les appels de rand que fait un programme soit di erente ` a chaque ex ecution de ce dernier il fait donc commencer par faire un appel de srand en veillant 118
c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.4 La biblioth` eque standard

` lui passer un argument di a erent chaque fois. Une mani` ere dobtenir cela consiste ` a utiliser lheure courante (elle change tout le temps !), par exemple ` a travers la fonction time 73 . Par exemple, le programme suivant initialise et ache une suite pseudo-al eatoire de dix nombres ottants xi v eriant 0 xi 1 : #include <stdio.h> #include <stdlib.h> #include <time.h> main() { int i; float x; srand(time(NULL)); /* version C de randomize */ rand(); /* le premier tirage est tr` es li e ` a la semence */ for(i = 0; i < 10; i++) { x = rand() / (float) RAND_MAX; printf("%f\n", x); } } void *malloc(size t taille) Alloue un espace pouvant m emoriser un objet ayant la taille indiqu ee, ou NULL en cas d echec. Cet espace nest pas initialis e. void *calloc(size t nombre, size t taille) Alloue un espace susant pour loger un tableau de nombre objets, chacun ayant la taille indiqu ee, ou NULL en cas d echec. Lespace allou e est initialis e par des z eros. void free(void *adresse) Indique au syst` eme que lespace m emoire ayant ladresse indiqu ee nest plus utile au programme. Cette adresse doit n ecessairement provenir dun appel de malloc, calloc ou realloc. void *realloc(void *adr, size t taille) R e-allocation despace. La valeur de adr doit provenir dun appel de malloc, calloc ou realloc. Cette fonction essaie dagrandir ou de r etr ecir (usage rare) lespace point e par adr an quil ait la la taille demand ee. Si cela peut se faire sur place alors la fonction renvoie la m eme adresse adr. Sinon, elle obtient un nouvel espace ayant la taille voulue, y copie le contenu de lespace point e par adr, lib` ere cet espace, enn renvoie ladresse de lespace nouvellement allou e. void exit(int code) Produit larr et normal (fermeture des chiers ouverts, etc.) du programme. La valeur du code indiqu e est transmise au syst` eme dexploitation, cette valeur est utilis ee lorsque le programme a et e appel e dans le cadre dun proc edure de commandes (ou script ). La signication de ce code d epend du syst` eme ; on peut utiliser les constantes : eme sous-jacent, que le EXIT SUCCESS : la valeur conventionnelle qui indique, pour le syst` programme a r eussi ` a accomplir sa mission ; EXIT FAILURE : le programme na pas r eussi. void abort(void) Provoque un arr et anormal du programme (cest un ev enement d etect e par la fonction signal). int system(const char *commande) Suspend momentan ement lex ecution du programme en cours, demande ` a linterpr` ete des commandes du syst` eme dexploitation dex ecuter la commande indiqu ee et rend le code donn e par cette commande (cette
73. La fonction time renvoie le nombre de secondes ecoul ees depuis le premier janvier 1970. Elle renvoie donc un nombre entier qui change toutes les secondes. Si on doit ecrire un programme devant initialiser plusieurs suites al eatoires distinctes en moins dune seconde ce qui est quand m eme rarissime on aura un probl` eme, quon peut r egler en faisant intervenir dautres fonctions de mesure du temps. Par exemple clock compte le temps (mais cest un temps relatif au lancement du programme) en fractions de seconde, par exemple des millisecondes.

c H. Garreta, 1988-2013

119

8.4

La biblioth` eque standard

EMENTS AUTRES EL DU LANGAGE C

valeur d epend du syst` eme). La cha ne commande doit etre lexpression compl` ete, avec options et arguments, dune commande l egale pour le syst` eme utilis e. Toutes les versions de C norent pas ce service, mais on peut savoir ce quil en est : lappel system(NULL) rend une valeur non nulle si linterpr` ete de commandes peut eectivement etre appel e depuis un programme, z ero sinon. Exemple (bien connu des utilisateurs de Dev-C++ ; pause est une commande MS-DOS/Windows) : system("pause"); char *getenv(char *nom) Rend la cha ne qui est la valeur de la variable denvironnement ayant le nom indiqu e, ou NULL si une telle variable nest pas d enie. La notion de variable denvironnement est d enie au niveau du syst` eme sous-jacent. void qsort(void *tab, size t nbr, size t taille, int (*comp)(const void *, const void *)) Quick sort, ou tri rapide. Cette fonction trie sur place (cest-` a-dire sans utiliser un tableaux auxiliaire), par ordre croissant, un tableau dont tab donne ladresse de base, form e de nbr objets chacun ayant la taille indiqu ee. Pour comparer les el ements du tableau, qsort utilise la fonction qui est la valeur de largument comp. Cette fonction doit prendre les adresses de deux objets comme ceux dont le tableau est fait, et rendre une valeur n egative, nulle ou positive selon que le premier objet est respectivement inf erieur, egal ou sup erieur au second. Voir ` a la section 6.3.2, exemple 2, un mod` ele dutilisation de cette fonction. int bsearch(const void *ptr, const void *tab, size t nbr, size t taille, int (*comp)(const void *, const void *)) Binary search, recherche dichotomique. Cette fonction recherche dans un tableau qui doit etre tri e, ayant ladresse de base donn ee par tab, form e de nbr objets chacun ayant la taille indiqu ee, un objet egal ` a celui ayant ptr pour adresse. Pour cela, elle utilise la fonction de comparaison donn ee par comp, qui est d enie comme pour qsort, voir ci-dessus. int abs(int x), long labs(long x) Valeur absolue, avec un argument int (resp. long) 74 . 8.4.3 Traitement de cha nes : string.h

char *strcpy(char *destin, const char *source) Copie la cha ne source ` a ladresse 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 lordre lexicographique (cest-` a-dire lordre par lequel on range les mots dans un dictionnaire) et rend une valeur n egative, nulle ou positive selon que a est, respectivement, inf erieur, egal ou sup erieur ` a b. size t strlen(const char *s) Rend le nombre de caract` eres de la cha ne s. Il sagit du nombre de caract` eres utiles : le caract` ere \0 qui se trouve ` a la n de toutes les cha nes nest pas compt e. Ainsi, strlen("ABC") vaut 3. void *memcpy(char *destin, const char *source, size t nombre) Copie la zone m emoire dadresse source de de taille nombre dans la zone de m eme taille et dadresse destin . Ces deux zones ne doivent pas se rencontrer. void *memmove(char *destin, const char *source, size t nombre) Copie la zone m emoire dadresse source de de taille nombre dans la zone de m eme taille et dadresse destin . Fonctionne correctement m eme si ces deux zones se rencontrent ou se chevauchent.
74. La fonction correspondante pour les r eels sappelle fabs (cf. section 8.4.5).

120

c H. Garreta, 1988-2013

EMENTS AUTRES EL DU LANGAGE C

8.4 La biblioth` eque standard

8.4.4

Classication des caract` eres : ctype.h

Les el ements de cette biblioth` eque peuvent etre implant es soit par des fonctions, soit par des macros. Les pr edicats rendent, lorsquils sont vrais, une valeur non nulle qui nest pas forc ement egale ` a1: 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 chire d ecimal. int isalnum(int c) c est une lettre ou un chire. int isspace(int c) c est un caract` ere despacement : , \t, \n, \r ou \f. int iscntrl(int c) c est un caract` ere de contr ole (cest-` a-dire un caract` ere dont le code ASCII est compris entre 0 et 31). ere imprimable, cest-` a-dire quil nest pas un caract` ere de contr ole. int isprint(int c) c est un caract` int isgraph(int c) c est un caract` ere imprimable autre quun caract` ere despacement. int ispunct(int c) c est une ponctuation, cest-` a-dire un caract` ere imprimable qui nest ni un caract` ere despacement, ni une lettre ni un chire. int tolower(int c), int toupper(int c) si c est une lettre majuscule (resp. minuscule) rend la lettre minuscule (resp. majuscule) correspondante, sinon rend le caract` ere c lui-m eme. 8.4.5 Fonctions math ematiques : math.h

double sin(double x) rend la valeur de sin x. double cos(double x) rend la valeur de cos x. double tan(double x) rend la valeur de tan x. [ ] double asin(double x) rend la valeur de arcsin x, 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 . [ ] v + double atan2(double y, double x) rend la limite de la valeur de arctan u dans 2 , 2 lorsque u x y et v y . Pour x = 0 cest la m eme chose que arctan x double sinh(double x) rend la valeur du sinus hyperbolique de x, soit sh x = double cosh(double x) rend la valeur du cosinus hyperbolique de x, ch x = double tanh(double x) rend la valeur de la tangente hyperbolique de x, th double exp(double x) rend la valeur de lexponentielle des, ex . double log(double x) rend la valeur du logarithme n ep erien de x, log x. On doit avoir x > 0. double log10(double x) rend la valeur du logarithme d ecimal 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 nest 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 sup erieur ou egal ` a x, transform e en double. double floor(double x) rend la valeur du plus grand entier inf erieur ou egal ` a x, transform e en double. double fabs(double x) la valeur absolue de x. 8.4.6 Limites propres ` a limpl ementation : limits.h, float.h
ex ex 2 ex + e x 2 x x = ch shx

Ces chiers en-t ete d enissent les tailles et les domaines de variation pour les types num eriques. On y trouve : CHAR BIT nombre de bits par caract` ere. CHAR MAX, CHAR MIN valeurs extr emes dun char. SCHAR MAX, SCHAR MIN valeurs extr emes dun signed char. UCHAR MAX valeur maximum dun unsigned char.
c H. Garreta, 1988-2013

121

8.4

La biblioth` eque standard

EMENTS AUTRES EL DU LANGAGE C

SHRT MAX, SHRT MIN valeurs extr emes dun short. USHRT MAX valeur maximum dun unsigned short. INT MAX, INT MIN valeurs extr emes dun int. UINT MAX valeur maximum dun unsigned int. LONG MAX, LONG MIN valeurs extr emes dun long. ULONG MAX valeur maximum dun unsigned long. Le chier float.h d enit : FLT DIG pr ecision (nombre de chires d ecimaux de la mantisse). FLT EPSILON plus petit nombre e tel que 1.0 + e = 1.0. FLT MANT DIG nombre de chires de la mantisse. FLT MAX plus grand nombre repr esentable. FLT MAX 10 EXP plus grand n tel que 10n soit repr esentable. FLT MAX EXP plus grand exposant n tel que FLT RADIXn 1 soit repr esentable. FLT MIN plus petit nombre positif repr esentable (sous forme normalis ee). FLT MIN 10 EXP plus petit exposant n tel que 10n soit repr esentable (sous forme normalis ee). FLT MIN EXP plus petit exposant n tel que FLT RADIXn soit repr esentable (sous forme normalis ee). esentation exponentielle. FLT RADIX base de la repr FLT ROUNDS type de larrondi pour laddition. Ces chiers d enissent egalement les constantes analogues pour les double (noms en de FLT ... ) et les long double (noms en LDBL ... ).

DBL ... ` a la place

122

c H. Garreta, 1988-2013

Index
() (op erateur), 19 * (op erateur), 23 *= (op erateur), 32 ++ (op erateur), 22 += (op erateur), 32 , (op erateur), 33 - (op erateur), 23 -= (op erateur), 32 ->(op erateur), 21 - - (op erateur), 22 . (op erateur), 21 /= (op erateur), 32 = (op erateur), 31 == (op erateur), 28 ? : (op erateur), 31 [ ] (op erateur), 20 %= (op erateur), 32 & (op erateur), 24, 29 &= (op erateur), 32 && (op erateur), 30 ^(op erateur), 29 ^= (op erateur), 32 erateur), 22 ~(op |(op erateur), 29 |= (op erateur), 32 ||(op erateur), 30 >(op erateur), 28 >= (op erateur), 28 >>(op erateur), 28, 32 <(op erateur), 28 <= (op erateur), 28 <<(op erateur), 28 <<= (op erateur), 32 abort, 119 abs, 120 acc` es ` a un champ (op erateur), 21 acc` es relatif, 98 acos, 121 adresse (op erateur), 24 adresse dun objet, 24, 64 adresse dune fonction, 77 aectation (op erateur), 31 appel de fonction, 19, 45, 47 argc, 112 argument formel, 12 arguments des fonctions (passage), 48 arguments en nombre variable, 50 arguments par adresse, 49 argv, 112 arithm etique des adresses, 66 arithm etiques (op erateurs), 27 asin, 121 assert, 117 assert.h, 117 atan, 121 atan2, 121 atof, 118 atoi, 118 atol, 118 automatique (variable), 13 bits (champs de bits), 56 bloc (instruction), 36, 37 break, 36, 42 bsearch, 120 calloc, 119 caract` ere, 7 case, 36, 41 cast (op erateur), 25, 62 ceil, 121 cha ne de caract` eres, 7, 53 cha ne de caract` eres (initialisation), 54 champs de bits, 56 char, 10 CHAR BIT, 121 CHAR MAX, 121 CHAR MIN, 121 close, 102 commentaire, 6 comparaison (op erateur), 28 compl ement ` a 1 (op erateur), 22 conditionnelle (compilation), 106 conjonction (op erateur), 30 conjonction bit-` a-bit (op erateur), 29 connecteurs logiques (op erateurs), 30 const, 15, 60 continue, 36, 42 conversion de type (op erateur), 25, 62 conversions arithm etiques usuelles, 34 cos, 121 cosh, 121 creat, 102 ctype.h, 121 d ecalage de bits (op erateur), 28 d eclarateur complexe, 58 default, 36, 41 define, 105 disjonction (op erateur), 30 disjonction bit-` a-bit (op erateur), 29 do, 36, 39 dur ee de vie (dune variable), 13 eet de bord, 18 else, 38, 107 en-t ete (chier), 109 endif, 107 enum eration, 57 etiquette, 38 exit, 119 EXIT FAILURE, 119 EXIT SUCCESS, 119 exp, 121 expression conditionnelle (op erateur), 31 extern, 16

INDEX

INDEX

externe (identicateur), 16 F, f (suxe dune constante), 7 fabs, 121 fclose, 87 feof, 87 ferror, 87 fflush, 86 fgetc, 88 fgetpos, 98 fgets, 89 chier binaire, 85 chier de texte, 85 FILE, 86 float.h, 121, 122 floor, 121 ots, 85 ottante (constante), 7 FLT DIG, 122 FLT EPSILON, 122 FLT MANT DIG, 122 FLT MAX, 122 FLT MAX 10 EXP, 122 FLT MAX EXP, 122 FLT MIN, 122 FLT MIN 10 EXP, 122 FLT MIN EXP, 122 FLT RADIX, 122 FLT ROUNDS, 122 fonction formelle, 78 fopen, 86 for, 36, 40 fprintf, 97 fputc, 90 fputs, 90 fread, 97 free, 119 fscanf, 97 fseek, 98 fsetpos, 98 fwrite, 97 getc, 89 getchar, 89 getenv, 120 gets, 89 globale (variable), 12 goto, 36, 38 hexad ecimale ( ecriture dun nombre), 7 identicateur, 6 if, 36, 38, 107 ifdef, 107 ifndef, 107 include, 104 indexation (op erateur), 20, 66 indirection (op erateur), 23 initialisation dun tableau, 52 initialisation dune cha ne de caract` eres, 54 initialisation dune structure, 56 initialisation dune variable, 13 124

INT MAX, 122 INT MIN, 122 isalnum, 121 isalpha, 121 iscntrl, 121 isdigit, 121 isgraph, 121 islower, 121 isprint, 121 ispunct, 121 isspace, 121 isupper, 121 jmp buf, 114 L, l (suxe dune constante), 7 labs, 120 limits.h, 121 locale (variable), 12 log, 121 log10, 121 logiques (op erateurs), 30 LONG MAX, 122 LONG MIN, 122 longjmp, 114 lseek, 103 lvalue, 18 macros, 105 main, 112 malloc, 119 math.h, 121 memcpy, 120 memmove, 120 moins unaire (op erateur), 23 mot-cl e, 6 n egation (op erateur), 22 NDEBUG, 117 nombre entier, 7, 9 nombre ottant, 7, 11 NULL, 66 O RDONLY, 102 O RDWR, 102 O WRONLY, 102 octale ( ecriture dun nombre), 7 op erateur, 6 open, 102 ordre d evaluation des expressions, 34 ou exclusif bit-` a-bit (op erateur), 29 passage des arguments des fonctions, 48 pointeur, 64 post-decr ementation (op erateur), 22 post-incr ementation (op erateur), 22 pow, 121 pr e-decr ementation (op erateur), 22 pr e-incr ementation (op erateur), 22 pr eprocesseur, 104 printf, 90, 97 priorit e des op erateurs, 19
c H. Garreta, 1988-2013

INDEX

INDEX

priv e (denticateur), 15 prototype dune fonction, 44 public (denticateur), 15 putc, 90 putchar, 90 puts, 90 qsort, 78, 120 rand, 118 read, 102 realloc, 119 register, 14 return, 36, 43 rewind, 98 rvalue, 18 s equence d echappement, 7 scanf, 93, 97 SCHAR MAX, 121 SCHAR MIN, 121 SEEK CUR, 98 SEEK END, 98 SEEK SET, 98 setjmp, 114 setjmp.h, 114 setvbuf, 87 SHRT MAX, 122 SHRT MIN, 122 signal, 115 signal.h, 115 sin, 121 sinh, 121 sizeof, 24 sprintf, 97 sqrt, 121 srand, 118 sscanf, 97 static, 14 statique (variable), 14 stderr, 88 stdin, 88 stdio.h, 86 stdlib.h, 118 stdout, 88 strcat, 120 strcmp, 120 strcpy, 120 string.h, 120 strlen, 120 struct, 54 structure (d eclaration), 54 structure (initialisation), 56 structure (signication dune variable structure), 55 structures r ecursives, 81, 83 switch, 36, 41 system, 119 tableau tableau tableau tableau (d eclaration), 52 (initialisation), 52 (signication dune variable tableau), 52 [dynamique] multidimensionnel, 69

tableau de cha nes de caract` eres, 72 tableau de fonctions, 79 tableau dynamique, 68 taille dun objet (op erateur), 24 tampon (dentr ee ou de sortie), 85 tan, 121 tanh, 121 tmpfile, 87 tolower, 121 typ e enum er e, 57 type enum er e, 11 typedef, 61 types, 9 types d esincarn es, 62 U, u (suxe dune constante), 7 UCHAR MAX, 121 UINT MAX, 122 ULONG MAX, 122 ungetc, 90 union, 56 USHRT MAX, 122 variable, 11 virgule (op erateur), 33 void, 45 void *, 65 volatile, 15, 60 while, 36, 39 write, 102

c H. Garreta, 1988-2013

125

You might also like