Vous êtes sur la page 1sur 115

Universit de la Mditerrane e e e

Facult des Sciences de Luminy e

Le langage C
Henri Garreta Table des mati`res e
e 1 Elments de base 1.1 Structure gnrale dun programme . . . . . . . e e 1.2 Considrations lexicales . . . . . . . . . . . . . e 1.2.1 Prsentation du texte du programme . . e 1.2.2 Mots-cls . . . . . . . . . . . . . . . . . e 1.2.3 Identicateurs . . . . . . . . . . . . . . 1.2.4 Oprateurs . . . . . . . . . . . . . . . . e 1.3 Constantes littrales . . . . . . . . . . . . . . . e 1.3.1 Nombres entiers . . . . . . . . . . . . . 1.3.2 Nombres ottants . . . . . . . . . . . . 1.3.3 Caract`res et cha e nes de caract`res . . . e 1.3.4 Expressions constantes . . . . . . . . . . 1.4 Types fondamentaux . . . . . . . . . . . . . . . 1.4.1 Nombres entiers et caract`res . . . . . . e 1.4.2 Types numrs . . . . . . . . . . . . . e ee 1.4.3 Nombres ottants . . . . . . . . . . . . 1.5 Variables . . . . . . . . . . . . . . . . . . . . . 1.5.1 Syntaxe des dclarations . . . . . . . . . e 1.5.2 Visibilit des variables . . . . . . . . . . e 1.5.3 Allocation et dure de vie des variables e 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 spare . . . e e 1.6.1 Identicateurs publics et privs . . . . . e 1.6.2 Dclaration dobjets externes . . . . . . e 2 Oprateurs et expressions e 2.1 Gnralits . . . . . . . . . . . . . . . . . . e e e 2.1.1 Lvalue et rvalue . . . . . . . . . . . 2.1.2 Priorit des oprateurs . . . . . . . . e e 2.2 Prsentation dtaille des oprateurs . . . . e e e e 2.2.1 Appel de fonction () . . . . . . . . . 2.2.2 Indexation [] . . . . . . . . . . . . . 2.2.3 Slection . . . . . . . . . . . . . . . e 2.2.4 Slection dans un objet point -> . . e e 2.2.5 Ngation ! . . . . . . . . . . . . . . . e 2.2.6 Complment ` 1 ~ . . . . . . . . . . e a 2.2.7 Les cl`bres ++ et -- . . . . . . . . . ee 2.2.8 Moins unaire - . . . . . . . . . . . . 2.2.9 Indirection * . . . . . . . . . . . . . 2.2.10 Obtention de ladresse & . . . . . . . 2.2.11 Oprateur sizeof . . . . . . . . . . e 2.2.12 Conversion de type (cast operator) 2.2.13 Oprateurs arithmtiques . . . . . . e e 2.2.14 Dcalages << >> . . . . . . . . . . . e 2.2.15 Comparaisons == != < <= > >= . . 2.2.16 Oprateurs de bits & | ^ . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 4 5 5 5 6 6 6 6 7 8 8 10 10 10 10 11 12 12 13 14 14 15 15 15 17 17 17 18 18 18 19 20 20 20 21 21 22 22 22 23 23 25 25 26 27

` TABLE DES MATIERES

` TABLE DES MATIERES

2.3

2.2.17 Connecteurs logiques && et || . . . . . . 2.2.18 Expression conditionnelle ? : . . . . . . . 2.2.19 Aectation = . . . . . . . . . . . . . . . . 2.2.20 Autres oprateurs daectation += *= etc. e 2.2.21 Loprateur virgule , . . . . . . . . . . . e Autres remarques . . . . . . . . . . . . . . . . . . 2.3.1 Les conversions usuelles . . . . . . . . . . 2.3.2 Lordre dvaluation des expressions . . . e 2.3.3 Les oprations non abstraites . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

28 28 29 30 30 31 31 32 32 33 33 34 34 34 35 35 36 37 38 39 40 41 41 41 41 42 43 43 43 44 45 45 45 45 46 47 49 49 49 49 50 51 51 53 53 54 55 55 57 58 59 60 60 60 61 62 62

3 Instructions 3.1 Syntaxe . . . . . . . . . . . . . . . . . . . 3.2 Prsentation dtaille des instructions . . e e e 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 . . . . . . . .

4 Fonctions 4.1 Syntaxe ANSI ou avec prototype . . . . . . 4.1.1 Dnition . . . . . . . . . . . . . . . . e 4.1.2 Type de la fonction et des arguments . 4.1.3 Appel des fonctions . . . . . . . . . . 4.1.4 Dclaration externe dune fonction . e 4.2 Syntaxe originale ou sans prototype . . . . 4.2.1 Dclaration et dnition . . . . . . . . e e 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 structurs e 5.1 Tableaux . . . . . . . . . . . . . . . . 5.1.1 Cas gnral . . . . . . . . . . . e e 5.1.2 Initialisation des tableaux . . . 5.1.3 Cha nes de caract`res . . . . . e 5.2 Structures et unions . . . . . . . . . . 5.2.1 Structures . . . . . . . . . . . . 5.2.2 Unions . . . . . . . . . . . . . . 5.2.3 Champs de bits . . . . . . . . . 5.3 Enumrations . . . . . . . . . . . . . . e 5.4 Dclarateurs complexes . . . . . . . . e 5.4.1 Cas des dclarations . . . . . . e 5.4.2 Pointeurs et tableaux constants 5.4.3 La dclaration typedef . . . . e 5.4.4 Cas des types dsincarns . . . e e

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

6 Pointeurs 6.1 Gnralits . . . . . . . . . . . . . . . . . . . . . . . . . . . e e e 6.1.1 Dclaration et initialisation des pointeurs . . . . . . e 6.1.2 Les pointeurs gnriques et le pointeur NULL . . . . . e e 6.2 Les pointeurs et les tableaux . . . . . . . . . . . . . . . . . 6.2.1 Arithmtique des adresses, indirection et indexation e 2

c H. Garreta, 2003

` TABLE DES MATIERES

` TABLE DES MATIERES

6.3

6.4

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`res . . . . . . e 6.2.6 Tableaux multidimensionnels formels . . . . . 6.2.7 Tableaux non ncessairement indexs ` partir e e a Les adresses des fonctions . . . . . . . . . . . . . . . 6.3.1 Les fonctions et leurs adresses . . . . . . . . . 6.3.2 Fonctions formelles . . . . . . . . . . . . . . . 6.3.3 Tableaux de fonctions . . . . . . . . . . . . . 6.3.4 Flou artistique . . . . . . . . . . . . . . . . . Structures rcursives . . . . . . . . . . . . . . . . . . e 6.4.1 Dclaration . . . . . . . . . . . . . . . . . . . e 6.4.2 Exemple . . . . . . . . . . . . . . . . . . . . . 6.4.3 Structures mutuellement rcursives . . . . . . e

. . . . . . . . . . . . . . . . . . . . . . . . . de zro e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

64 65 66 68 69 70 72 72 72 74 75 75 75 76 77 79 79 80 81 82 82 84 86 88 89 90 90 90 92 92 92 94 94 96 96 96 97 99 100 101 102 104 104 106 107 109 109 109 111 111 112 112

7 Entres-sorties e 7.1 Flots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Fonctions gnrales sur les ots . . . . . . . . . . . . . . . e e 7.1.2 Les units standard dentre-sortie . . . . . . . . . . . . . e e 7.2 Lecture et criture textuelles . . . . . . . . . . . . . . . . . . . . e 7.2.1 Lecture et criture de caract`res et de cha e e 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 Oprations en mode binaire . . . . . . . . . . . . . . . . . . . . . e 7.3.1 Lecture-criture . . . . . . . . . . . . . . . . . . . . . . . . e 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`s relatif . . . . . . . . . . . . . . . . . . . e 7.5 Les chiers de bas niveau dUNIX . . . . . . . . . . . . . . . . . 8 Gnie logiciel e 8.1 Le prprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . e 8.1.1 Inclusion de chiers . . . . . . . . . . . . . . . . . . . 8.1.2 Dnition et appel des macros . . . . . . . . . . . . e 8.1.3 Compilation conditionnelle . . . . . . . . . . . . . . . 8.2 La modularit de C . . . . . . . . . . . . . . . . . . . . . . . . e 8.2.1 Fichiers en-tte . . . . . . . . . . . . . . . . . . . . . . e 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`que standard . . . . . . . . . . . . . . . . . . . . e 8.4.1 Aide ` la mise au point : assert.h . . . . . . . . . . . a 8.4.2 Fonctions utilitaires : stdlib.h . . . . . . . . . . . . . 8.4.3 Traitement de cha nes : string.h . . . . . . . . . . . 8.4.4 Classication des caract`res : ctype.h . . . . . . . . . e 8.4.5 Fonctions mathmatiques : math.h . . . . . . . . . . . e 8.4.6 Limites propres ` limplantation : limits.h, float.h a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28 fvrier 2003 e Henri.Garreta@luminy.univ-mrs.fr

c H. Garreta, 2003

ELEMENTS DE BASE

1
1.1

e Elments de base
Structure gnrale dun programme e e

La transformation dun texte crit en langage C en un programme excutable par lordinateur se fait en e e deux tapes : la compilation et ldition de liens. La compilation est la traduction des fonctions crites en C en e e e des procdures quivalentes crites en langage machine. Le compilateur lit toujours un chier source1 et produit e e e un chier objet. Chaque chier objet est incomplet, insusant pour tre excut, car il contient des appels de fonctions e e e ou des rfrences ` des variables qui ne sont pas dnies dans le mme chier (pensez ` la fonction printf). ee a e e a Ldition de liens est lopration par laquelle plusieurs chiers objets sont mis ensemble pour se complter e e e mutuellement : un chier apporte des dnitions de fonctions et de variables auxquelles un autre chier fait e rfrence, et rciproquement. Lditeur de liens (ou linker ) prend en entre plusieurs chiers objets et produit ee e e e un unique chier excutable. Lditeur de liens est indpendant du langage de programmation utilis pour crire e e e e e les chiers sources, qui peuvent mme avoir t crits dans des langages dirents. e eee e Chaque chier source entrant dans la composition dun programme excutable est fait dune succession dun e nombre quelconque dlments indpendants, qui sont : ee e des des des des des directives pour le prprocesseur (lignes commenant par #), e c constructions de types (struct, union, enum, typedef), dclarations de variables et de fonctions externes, e dnitions de variables, e dnitions de fonctions. e

Seules les expressions des deux derni`res catgories font grossir le chier objet : les dnitions de fonce e e tions laissent leur traduction en langage machine, tandis que les dnitions de variables se traduisent par des e rservations despace, ventuellement garni de valeurs initiales. Les autres directives et dclarations sadressent e e e 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` la question : par o` lexcution doit-elle commencer ? La convention est la suivante : parmi les fonctions u u e donnes il doit en exister une dont le nom est main. Cest par elle que lexcution commencera ; le lancement du e e programme quivaut ` lappel de cette fonction par le syst`me dexploitation. Notez bien que, ` part cela, main e a e a est une fonction comme les autres, sans aucune autre proprit spcique. En particulier, les variables internes ee e a ` main sont locales, tout comme celles des autres fonctions. Pour nir cette entre en mati`re, voici la version C du cl`bre programme-qui-dit-bonjour, sans lequel on e e ee ne saurait commencer un cours de programmation : #include <stdio.h> int main() { printf("Bonjour\n"); return 0; }

1.2
1.2.1

Considrations lexicales e
Prsentation du texte du programme e

Le programmeur est ma tre de la disposition du texte du programme. Des blancs, des tabulations et des sauts ` la ligne peuvent tre placs ` tout endroit o` cela ne coupe pas un identicateur, un nombre ou un a e e a u symbole compos2 . e Les commentaires commencent par /* et se terminent par */ :
1 Comme nous le verrons, si la directive #include est utilise, alors le chier lu par le compilateur est la runion du chier initial e e et du chier inclus. 2 Nanmoins, les directives pour le prprocesseur (cf. section 8.1) doivent comporter un # dans la premi`re position de la ligne. e e e Cela ne constitue pas une exception ` la r`gle donne ici, car le prprocesseur nest pas le compilateur C et ne travaille pas sur la a e e e syntaxe du langage.

c H. Garreta, 2003

ELEMENTS DE BASE

1.2

Considrations lexicales e

/* Ce texte est un commentaire et sera donc ignor par le e compilateur */ Les commentaires ne peuvent pas tre imbriqus : crit dans un programme, le texte  /* voici un grand e e e /* et un petit */ commentaire */  est erron, car seul  /* voici un grand /* et un petit */  sera e vu comme un commentaire par le compilateur. Les langages C et C++ cohabitant dans la plupart des compilateurs actuels, le compilateur accepte galement e comme un commentaire tout texte compris entre le signe // et la n de la ligne o` ce signe appara : u t // Ceci est un commentaire ` la mode C++. a Le caract`re anti-slash \ prcdant immdiatement un saut ` la ligne masque ce dernier : la ligne suivante e e e e a est considre comme devant tre concatne ` la ligne courante. Cela est vrai en toute circonstance, y compris ee e e e a a ` lintrieur dune cha de caract`res. Par exemple, le texte e ne e message = "anti\ constitutionnellement"; est compris comme ceci :  message = "anti 1.2.2 Mots-cls e constitutionnellement" ; 

Les mots suivants sont rservs. Leur fonction est prvue par la syntaxe de C et ils ne peuvent pas tre e e e e utiliss dans un autre but : e 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, cest-`-dire lorsquil sagit didenticateurs dont la porte est incluse dans un seul e a e chier (nous dirons de tels identicateurs quils sont privs) : e en toute circonstance une lettre majuscule est tenue pour dirente de la lettre minuscule correspondante ; e dans les identicateurs, le nombre de caract`res discriminants est au moins de 31. e Attention, lorsquil sagit didenticateurs externes, cest-`-dire partags par plusieurs chiers sources, il est a e possible que sur un syst`me particulier lditeur de liens sous-jacent soit trop rustique pour permettre le respect e e de ces deux prescriptions. Le caract`re (appel  blanc soulign ) est considr comme une lettre ; il peut donc gurer ` nimporte e e e ee a quelle place dans un identicateur. Cependant, par convention un programmeur ne doit pas utiliser des identicateurs qui commencent par ce caract`re. Cela assure quil ny aura jamais de conit avec les noms introduits e (` travers les chiers  .h ) pour les besoins des biblioth`ques, car ces noms commencent par un tel blanc a e soulign. e 1.2.4 Oprateurs e

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

Symboles composs : e -> += ++ -= -*= <= /= >= %= == <<= != >>= && |= || &= << ^= >>

Tous ces symboles sont reconnus par le compilateur comme des oprateurs. Il est interdit dinsrer des e e caract`res blancs ` lintrieur dun symbole compos. En outre, il est conseill dencadrer par des blancs toute e a e e e utilisation dun oprateur. Dans certaines circonstances cette r`gle est plus quun conseil, car sa non-observance e e cre une expression ambigu. e e
c H. Garreta, 2003

1.3 Constantes littrales e

1 ELEMENTS DE BASE

1.3
1.3.1

Constantes littrales e
Nombres entiers

Les constantes littrales numriques enti`res ou relles suivent les conventions habituelles, avec quelques e e e e particularits. e Les constantes littrales sont sans signe : lexpression 123 est comprise comme lapplication de loprateur e e unaire ` la constante 123 ; mais puisque le calcul est fait pendant la compilation, cette subtilit na aucune a e consquence pour le programmeur. Notez aussi quen C original, comme il nexiste pas doprateur + unaire, la e e notation +123 est interdite. Les constantes littrales enti`res peuvent aussi scrire en octal et en hexadcimal : e e e e une constante crite en octal (base 8) commence par 0 (zro) ; e e une constante crite en hexadcimal (base 16) commence par 0x ou 0X. e e Voici par exemple trois mani`res dcrire le mme nombre : e e e 27 033 0x1B Dtail ` retenir : on ne doit pas crire de zro non signicatif ` gauche dun nombre : 0123 ne reprsente e a e e a e pas la mme valeur que 123. e Le type dune constante enti`re est le plus petit type dans lequel sa valeur peut tre reprsente. Ou, plus e e e e exactement : si elle est dcimale : si possible int, sinon long, sinon unsigned long ; e si elle est octale ou hexadcimale : si possible int, sinon unsigned int, sinon unsigned long. e 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 littrale est lexpression dun nombre ottant si elle prsente, dans lordre : e e une suite de chires dcimaux (la partie enti`re), e e un point, qui joue le rle de virgule dcimale, o e une suite de chires dcimaux (la partie fractionnaire), e une des deux lettres E ou e, ventuellement un signe + ou -, e une suite de chires dcimaux. e Les trois derniers lments forment lexposant. Exemple : 123.456E-78. ee On peut omettre : la partie enti`re ou la partie fractionnaire, mais pas les deux, e le point ou lexposant, mais pas les deux. Exemples : .5e7, 5.e6, 5000000., 5e6 Une constante ottante est suppose de type double, ` moins de comporter un suxe explicite : e a 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`res et cha e nes de caract`res e

Une constante de type caract`re se note en crivant le caract`re entre apostrophes. Une constante de type e e e cha de caract`res se note en crivant ses caract`res entre guillemets. Exemples, trois caract`res : ne e e e e A 2 "

Quatre cha nes de caract`res : e "A" 6 "Bonjour ` tous !" a "" ""
c H. Garreta, 2003

ELEMENTS DE BASE

1.3

Constantes littrales e

On peut faire gurer nimporte quel caract`re, mme non imprimable, dans une constante caract`re ou e e e cha de caract`res en utilisant les combinaisons suivantes, appeles squences dchappement : ne e e e e \n \t \b \r \f \a \\ " \d3 d2 d1 nouvelle ligne (LF) tabulation (HT) espace-arri`re (BS) e retour-chariot (CR) saut de page (FF) signal sonore (BELL) \ " le caract`re qui a pour code le nombre octal d3 d2 d1 . Sil commence par un ou deux zros e e et si cela ne cre pas une ambigu e, on peut aussi le noter \d2 d1 ou \d1 e t

Par exemple, la cha suivante dnit la suite des 9 caract`res3 A, escape (de code ASCII 27), B, , C, saut ne e e de page, D, \ et E : "A\033B\"C\fD\\E" Une constante de type caract`re appartient au type char, cest-`-dire entier reprsent sur un octet. La e a e e valeur dune constante caract`re est le nombre qui reprsente le caract`re de mani`re interne ; de nos jours il e e e e sagit presque toujours du code ASCII4 . Une constante de type cha de caract`res reprsente une suite nie de caract`res, de longueur quelconque. ne e e e Le codage interne dune cha de caract`res est le suivant (voyez la gure 1) : ne e les caract`res constituant la cha sont rangs en mmoire, de mani`re contigu, dans lordre o` ils e ne e e e e u gurent dans la cha ; ne un caract`re nul est ajout immdiatement apr`s le dernier caract`re de la cha pour en indiquer la n ; e e e e e ne, la constante cha reprsente alors, ` lendroit o` elle est crite, ladresse de la cellule o` a t rang le ne e a u e u ee e premier caract`re de la cha e ne.

"Bonjour" B o n j o u r\ 0
Fig. 1 Reprsentation de la cha "Bonjour" e ne Par consquent, une constante cha de caract`res a pour type celui dun tableau de caract`res (cest-`-dire e ne e e a  char[] ) et pour valeur ladresse dune cellule de la mmoire. e Par caract`re nul on entend le caract`re dont le code interne est 0 ; on peut le noter indiremment 0, \000 e e e ou \0 (mais certainement pas 0) ; il est utilis tr`s frquemment en C. Notez que, dans une expression, e e e \0 est toujours interchangeable avec 0. 1.3.4 Expressions constantes

Une expression constante est une expression de lun des types suivants : toute constante littrale ; exemples : 1, A, "HELLO", 1.5e-2 ; e e e e a une expression correcte forme par lapplication dun oprateur courant (arithmtique, logique, etc.) ` une ou deux expressions constantes ; exemples : -1, A - a, 2 * 3.14159265, "HELLO" + 6 ; e e e lexpression constitue par lapplication de loprateur & (oprateur de calcul de ladresse, voyez la section 2.2.10) ` une variable statique, ` un champ dune variable statique de type structure ou ` un lment a a a ee dun tableau statique dont le rang est donn par une expression constante ; exemples : &x, &fiche.nom, e &table[50] ; e e a lexpression constitue par lapplication de loprateur sizeof ` un descripteur de type. Exemples : sizeof(int), sizeof(char *) ; e e a lexpression constitue par lapplication de loprateur sizeof ` une expression quelconque, qui ne sera pas value ; exemples : sizeof x, sizeof(2 * x + 3). e e
3 Nous 4 En

verrons quen fait cette cha comporte un caract`re de plus qui en marque la n. ne e standard le langage C ne prvoit pas le codage Unicode des caract`res. e e

c H. Garreta, 2003

1.4 Types fondamentaux

1 ELEMENTS DE BASE

Les expressions constantes peuvent tre values pendant la compilation. Cela est fait ` titre facultatif par e e e a 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 value avant que e e lexcution ne commence. En termes de temps dexcution, lvaluation des expressions constantes est donc e e e enti`rement  gratuite . e

1.4

Types fondamentaux
Types de base Nombres entiers Anonymes Petite taille signs e non signs e Taille moyenne signs e non signs e Grande taille signs e non signs e Nomms e Nombres ottants Simples Grande prcision e Prcision encore plus grande e Types derives Tableaux Fonctions Pointeurs Structures Unions

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

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

Le tableau 1 prsente lensemble des types connus du compilateur C. Lorganisation gnrale de cet ensemble e e e est vidente : on dispose de deux sortes de types de base, les nombres entiers et les nombres ottants, et dune e famille innie de types drivs obtenus en appliquant quelques procds rcursifs de construction soit ` des e e e e e a types fondamentaux soit ` des types drivs dnis de la mme mani`re. a e e e e e Cette organisation rv`le un trait de lesprit de C : le pragmatisme lemporte sur lesthtisme, parfois mme e e e e sur la rigueur. Dans dautres langages, les caract`res, les boolens, les constantes symboliques, etc., sont cods e e e de mani`re interne par des nombres, mais ce fait est ociellement ignor par le programmeur, qui reste oblig e e e de considrer ces donnes comme appartenant ` des ensembles disjoints. En C on a fait le choix oppos, laissant e e a e au programmeur le soin de raliser lui-mme, ` laide des seuls types numriques, limplantation des types de e e a e niveau suprieur. e

1.4.1

Nombres entiers et caract`res e

La classication des types numriques obit ` deux crit`res : e e a e Si on cherche ` reprsenter un ensemble de nombres tous positifs on pourra adopter un type non sign ; a e e au contraire si on doit reprsenter un ensemble contenant des nombres positifs et des nombres ngatifs on e e devra utiliser un type sign 5 . e
5 On dit parfois quune donne  est un entier sign  ou  est un entier non sign . Cest un abus de langage : le caract`re sign e e e e e ou non sign nest pas un attribut dun nombre (un nombre donn est positif ou ngatif, cest tout) mais de lensemble de nombres e e e quon a choisi de considrer et, par extension, de toute variable cense pouvoir reprsenter nimporte quelle valeur de cet ensemble. e e e

c H. Garreta, 2003

ELEMENTS DE BASE

1.4

Types fondamentaux

Le deuxi`me crit`re de classication des donnes numriques est la taille requise par leur reprsentation. e e e e e Comme prcdemment, cest un attribut dun ensemble, et donc dune variable devant reprsenter tout e e e lment de lensemble, non dune valeur particuli`re. Par exemple, le nombre 123 considr comme un ee e ee lment de lensemble {0 ... 65535} est plus encombrant que le mme nombre 123 quand il est considr ee e ee comme un lment de lensemble {0 ... 255}. ee Avec N chires binaires (ou bits) on peut reprsenter : e soit les 2N nombres positifs 0, 1, ... 2N 1 (cas non sign) ; e soit les 2N nombres positifs et ngatifs 2N 1 , ... 2N 1 1 (cas sign). e e De plus, la reprsentation signe et la reprsentation non signe des lments communs aux deux domaines e e e e ee (les nombres 0, 1, ... 2N 1 1) co ncident. ` Le type caractere. Un objet de type char peut tre dni, au choix, comme e e un nombre entier pouvant reprsenter nimporte quel caract`re du jeu de caract`res de la machine utilise ; e e e e un nombre entier occupant la plus petite cellule de mmoire adressable sparment6 . Sur les machines e e e actuelles les plus rpandues cela signie gnralement un octet (8 bits). e e e Le plus souvent, un char est un entier sign ; un unsigned char est alors un entier non sign. Lorsque les e e char sont par dfaut non signs, la norme ANSI prvoit la possibilit de dclarer des signed char. e e e e e On notera que la signication dun char en C, un entier petit, est tr`s dirente de celle dun char en Pascal e e (dans ce langage, lensemble des caract`res et celui des nombres sont disjoints). En C, ch tant une variable de e e type char, rien ne soppose ` lcriture de lexpression ch - A + 32 qui est tout ` fait homog`ne, puisque a e a e enti`rement faite de nombres. e ` Le caractere  impossible . Toutes les valeurs quil est possible de ranger dans une variable de type char sont en principe des caract`res lgaux. Or la plupart des programmes qui lisent des caract`res doivent e e e tre capables de manipuler une valeur supplmentaire, distincte de tous les  vrais  caract`res, signiant  la e e e n des donnes . Pour cette raison, les variables et fonctions qui reprsentent ou renvoient des caract`res sont e e e souvent dclares int, non char : nimporte quelle valeur appartenant au type int mais nappartenant pas au e e type char peut alors servir dindicateur de n de donnes. Par exemple, une telle valeur est dnie dans le e e chier stdio.h, cest la constante symbolique EOF. Les entiers courts et longs. Il est garanti que toute donne reprsentable dans le type short est e e reprsentable aussi dans le type long7 (en bref : un long nest pas plus court quun short !), mais la taille e exacte des donnes de ces types nest pas xe par la norme du langage. e e De nos jours on trouve souvent : unsigned short : 16 bits short : 16 bits unsigned long : 32 bits long : 32 bits pour pour pour pour reprsenter e reprsenter e reprsenter e reprsenter e 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 ` la taille dentier la plus ecace, cest-`-dire la plus a a adapte ` la machine utilise. Sur certains syst`mes et compilateurs int est synonyme de short, sur dautres il e a e e est synonyme de long. Le type int peut donc poser un probl`me de portabilit8 : le mme programme, compil sur deux machines e e e e distinctes, peut avoir des comportements dirents. Do` un conseil important : nutilisez le type int que pour e u des variables locales destines ` contenir des valeurs raisonnablement petites (infrieures en valeur absolue ` e a e a 32767) . Dans les autres cas il vaut mieux expliciter char, short ou long selon le besoin. A propos des booleens. En C il nexiste donc pas de type boolen spcique. Il faut savoir qu` tout e e a endroit o` une expression boolenne est requise (typiquement, dans des instructions comme if ou while) on u e
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 utilise, le compilateur C fera en sorte que le programmeur voie ces objets de la mani`re suivante : si t est un tableau e e de char, la taille (au sens de loprateur sizeof, cf. section 2.2.11) de t[0] vaut une unit de taille, et lcart entre les adresses de e e e t[1] et t[0] vaut une unit dadressage. On peut dire que ces proprits dnissent le type char (ou, si vous prfrez, les units de e e e e ee e taille et dadressage). 7 Si on consid`re un type comme lensemble de ses valeurs, on a donc les inclusions larges char short long (et aussi float e double long double). 8 Un programme crit pour une machine ou un syst`me A est dit portable sil sut de le recompiler pour quil tourne correctement e e sur une machine dirente B. Par exemple,  putchar(A) ;  est une mani`re portable dobtenir lachage du caract`re A, tandis e e e que  putchar(65) ;  est (sur un syst`me utilisant le code ASCII) une mani`re non portable dobtenir le mme achage. Etre e e e portable est un crit`re de qualit et de abilit important. On invoque lecacit pour justier lcriture de programmes non e e e e e portables ; lexprience prouve que, lorsque son criture est possible, un programme portable est toujours meilleur quun programme e e non portable prtendu quivalent. e e

c H. Garreta, 2003

1.5 Variables

1 ELEMENTS DE BASE

peut faire gurer nimporte quelle expression ; elle sera tenue pour vraie si elle est non nulle, elle sera considre ee fausse sinon. Ainsi, dans un contexte conditionnel, expr (cest-`-dire expr  vraie ) quivaut ` a e a expr != 0 (expr dirente de 0). Inversement, lorsquun oprateur (galit, comparaison, etc.) produit une valeur boolenne, e e e e e il rend 0 pour faux et 1 pour vrai. Signalons aux esth`tes que le chier <types.h> comporte les dclarations : e e 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 reprsenter ces deux valeurs. e

1.4.2

Types numrs e e e

Un type numr, ou numration, est constitu par une famille nie de nombres entiers, chacun associ e ee e e e e a ` un identicateur qui en est le nom. Mis ` part ce qui touche ` la syntaxe de leur dclaration, il ny a pas a a e grand-chose ` dire ` leur sujet. La syntaxe de la dclaration des numrations est explique ` la section 5.3. Par a a e e e e a exemple, lnonc : e e enum jour_semaine { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche }; introduit un type numr, appel enum jour semaine, constitu par les constantes lundi valant 0, mardi e ee e e valant 1, mercredi valant 2, etc. Ainsi, les expressions mardi + 2 et jeudi reprsentent la mme valeur. e e Les valeurs dun type numr se comportent comme des constantes enti`res ; elles font donc double emploi e ee e avec celles quon dnit ` laide de #define (cf. section 8.1.2). Leur unique avantage rside dans le fait que e a e certains compilateurs dtectent parfois, mais ce nest pas exig par la norme, les mlanges entre objets de types e e e numrs distincts ; ces types sont alors le moyen daugmenter la scurit des programmes. e ee e e A propos des types numrs voyez aussi la section 5.3. e ee

1.4.3

Nombres ottants

La norme ANSI prvoit trois types de nombres ottants : float (simple prcision), double (double prcision) e e e et long double (prcision tendue). La norme ne spcie pas les caractristiques de tous ces types. Il est garanti e e e e que toute valeur reprsentable dans le type float est reprsentable sans perte dinformation dans le type double, e e et toute valeur reprsentable dans le type double lest dans le type long double. e Typiquement, sur des syst`mes de taille moyenne, un float occupe 32 bits et un double 64, ce qui donne e par exemple des float allant de -1.70E38 ` -0.29E-38 et de 0.29E-38 ` 1.70E38 avec 7 chires dcimaux a a e signicatifs, et des double allant de -0.90E308 ` -0.56E-308 et de 0.56E-308 ` 0.90E308 avec 15 chires a a dcimaux signicatifs. e Les long double correspondent gnralement aux ottants de grande prcision manipuls par certains e e e e coprocesseurs arithmtiques ou les biblioth`ques de sous-programmes qui les simulent. Mais il nest pas exclu e e que sur un syst`me particulier un long double soit la mme chose quun double. e e

1.5
1.5.1

Variables
Syntaxe des dclarations e

La forme compl`te de la dclaration dune variable sera explique ` la section 5.4. Dans le cas le plus simple e e e a on trouve spcication var-init , var-init , ... var-init ; e 10
c H. Garreta, 2003

ELEMENTS DE BASE

1.5

Variables

o` spcication est de la forme : u e auto register static extern rien et chaque var-init est de la forme : identicateur Exemples : int x, y = 0, z; extern float a, b; static unsigned short cpt = 1000; Les dclarations de variables peuvent se trouver : e en dehors de toute fonction, il sagit alors de variables globales ; ` lintrieur dun bloc, il sagit alors de variables locales ; a e dans len-tte dune fonction, il sagit alors darguments formels, placs e e soit dans les parenth`ses de len-tte (fonction dnie en syntaxe ANSI avec un prototype), e e e soit entre le nom de la fonction et le { initial (fonction dnie en syntaxe originale ou sans prototype). e 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 des variables e = expression rien unsigned char short signed const long rien int volatile float rien double long double

La question de la visibilit des identicateurs (cest-`-dire  quels sont les identicateurs auxquels on peut e a faire rfrence en un point dun programme ? ) est rgle en C comme en Pascal, avec une simplication (les ee e e fonctions ne peuvent pas tre imbriques les unes dans les autres) et une complication (tout bloc peut comporter e e ses propres dnitions de variables locales). e Un bloc est une suite de dclarations et dinstructions encadre par une accolade ouvrante { et laccolade e e fermante } correspondante. Le corps dune fonction est lui-mme un bloc, mais dautres blocs peuvent tre e e imbriqus dans celui-l`. e a Variables locales. Tout bloc peut comporter un ensemble de dclarations de variables, qui sont alors e dites locales au bloc en question. Une variable locale ne peut tre rfrence que depuis lintrieur du bloc o` e ee e e u elle est dnie ; en aucun cas on ne peut y faire rfrence depuis un point extrieur ` ce bloc. Dans le bloc o` il e ee e a u est dclar, le nom dune variable locale masque toute variable de mme nom dnie dans un bloc englobant le e e e e bloc en question.
c H. Garreta, 2003

11

1.5 Variables

1 ELEMENTS DE BASE

Toutes les dclarations de variables locales ` un bloc doivent tre crites au dbut du bloc, avant la premi`re e a e e e e instruction. Arguments formels. Pour ce qui concerne leur visibilit, les arguments formels des fonctions sont considrs e ee comme des variables locales du niveau le plus haut, cest-`-dire des variables dclares au dbut du bloc le plus a e e e extrieur9 . Un argument formel est accessible de lintrieur de la fonction, partout o` une variable locale plus e e u profonde ne le masque pas. En aucun cas on ne peut y faire rfrence depuis lextrieur de la fonction. ee e Variables globales. Le nom dune variable globale ou dune fonction peut tre utilis depuis nimporte e e quel point compris entre sa dclaration (pour une fonction : la n de la dclaration de son en-tte) et la n e e e du chier o` la dclaration gure, sous rserve de ne pas tre masque par une variable locale ou un argument u e e e e formel de mme nom. e La question de la visibilit inter-chiers est examine ` la section 1.6. On peut noter dores et dj` quelle ne e e a ea se pose que pour les variables globales et les fonctions, et quelle concerne ldition de liens, non la compilation, e car le compilateur ne traduit quun chier source ` la fois et, pendant la traduction dun chier, il ne  voit  a pas les autres.

1.5.3

Allocation et dure de vie des variables e

Les variables globales sont toujours statiques, cest-`-dire permanentes : elles existent pendant toute la a dure de lexcution. Le syst`me dexploitation se charge, immdiatement avant lactivation du programme, de e e e e les allouer dans un espace mmoire de taille adquate, ventuellement garni de valeurs initiales. e e e A loppos, les variables locales et les arguments formels des fonctions sont automatiques : lespace correse pondant est allou lors de lactivation de la fonction ou du bloc en question et il est rendu au syst`me lorsque e e le contrle quitte cette fonction ou ce bloc. Certains qualieurs (static, register, voir les sections 1.5.5 et o 1.5.6) permettent de modier lallocation et la dure de vie des variables locales. e Remarque. On note une grande similitude entre les variables locales et les arguments formels des fonctions : ils ont la mme visibilit et la mme dure de vie. En ralit cest presque la mme chose : les arguments formels e e e e e e e sont de vraies variables locales avec lunique particularit dtre automatiquement initialiss (par les valeurs des e e e arguments eectifs) lors de lactivation de la fonction.

1.5.4

Initialisation des variables

Variables statiques. En toute circonstance la dclaration dune variable statique peut indiquer une valeur e initiale ` ranger dans la variable. Cela est vrai y compris pour des variables de types complexes (tableaux ou a 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 lexcution du programme. Il sagit ici uniquement de prciser la valeur qui doit e e tre dpose dans lespace allou ` la variable, avant que lexcution ne commence. Par consquent : e e e ea e e la valeur initiale doit tre dnie par une expression constante (calculable durant la compilation) ; e e e e une telle initialisation est enti`rement gratuite, elle na aucune incidence ni sur la taille ni sur la dure du programme excutable produit. e Les variables statiques pour lesquelles aucune valeur initiale nest indique sont remplies de zros. Line e terprtation de ces zros dpend du type de la variable. e e e Variables automatiques. Les arguments formels des fonctions sont automatiquement initialiss lors de e leur cration (au moment de lappel de la fonction) par les valeurs des arguments eectifs. Cela est la dnition e e mme des arguments des fonctions. e La dclaration dune variable locale peut elle aussi comporter une initialisation. Mais il ne sagit pas de la e mme sorte dinitialisation que pour les variables statiques : linitialisation reprsente ici une aectation tout ` e e a fait ordinaire. Ainsi, place ` lintrieur dun bloc, la construction e a e int i = exp; quivaut au couple e
9 Par

/* dclaration + initialisation */ e

consquent, on ne doit pas dclarer un argument formel et une variable locale du niveau le plus haut avec le mme nom. e e e

12

c H. Garreta, 2003

ELEMENTS DE BASE

1.5

Variables

int i; ... i = exp ;

/* dclaration */ e /* affectation */

Par consquent : e lexpression qui donne la valeur initiale na pas ` tre constante, puisquelle est value ` lexcution, a e e e a e chaque fois que la fonction ou le bloc est activ ; e u e a une telle initialisation  cote  le mme prix que laectation correspondante, cest-`-dire le temps dvaluation de lexpression qui dnit la valeur initiale. e e Les variables automatiques pour lesquelles aucune valeur initiale nest indique sont alloues avec une valeur e e imprvisible. e Remarque. Dans le C original, une variable automatique ne peut tre initialise que si elle est simple e e (cest-`-dire autre que tableau ou structure). Cette limitation ne fait pas partie du C ANSI. a 1.5.5 Variables locales statiques

Le qualieur static, plac devant la dclaration dune variable locale, produit une variable qui est e e pour sa visibilit, locale ; e pour sa dure de vie, statique (cest-`-dire permanente). e a Elle nest accessible que depuis lintrieur du bloc o` elle est dclare, mais elle est cre au dbut de e u e e ee e lactivation du programme et elle existe aussi longtemps que dure lexcution de celui-ci. Exemple : e void bizarre1(void) { static int cpt = 1000; printf("%d ", cpt); cpt++; } Lorsque la dclaration dune telle variable comporte une initialisation, il sagit de linitialisation dune vae riable statique : elle est eectue une seule fois avant lactivation du programme. Dautre part, une variable e locale statique conserve sa valeur entre deux activations conscutives de la fonction. Ainsi, trois appels succese sifs de la fonction ci-dessus produisent lachage des valeurs 1000, 1001, 1002. 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 tre modie inconsidrment par une autre fonction, ou entrer e e ee en conit avec un autre objet de mme nom, tandis que dans la premi`re version elle nest visible que depuis e e lintrieur de la fonction et donc ` labri des manipulations maladroites. On notera pour nir que la version e a suivante est errone : e void bizarre3(void) { int cpt = 1000; printf("%d ", cpt); cpt++; } En eet, tous les appels de bizarre3 acheront la mme valeur 1000. e Attention. Malgr tout le bien quon vient den dire, les variables locales statiques ont une particularit e e potentiellement fort dangereuse : il en existe une seule instance pour toutes les activations de la fonction dans laquelle elles sont dclares. Ainsi, dans lexemple suivant : e e void fonction_suspecte(void) { static int i; ... fonction_suspecte(); ... }
c H. Garreta, 2003

13

1.5 Variables

1 ELEMENTS DE BASE

la valeur de la variable i avant et apr`s lappel de fonction suspecte (cest-`-dire aux points et ) peut e a ne pas tre la mme, car la deuxi`me activation de fonction suspecte acc`de aussi ` i. Cela est tout ` fait e e e e a a inhabituel pour une variable locale. Consquence ` retenir : les variables locales statiques se marient mal avec e a la rcursivit. e e

1.5.6

Variables critiques

Le qualieur register prcdant une dclaration de variable informe le compilateur que la variable en e e e question est tr`s frquemment accde pendant lexcution du programme et quil y a donc lieu de prendre e e e e e toutes les dispositions utiles pour en acclrer lacc`s. Par exemple, dans certains calculateurs de telles variables ee e sont loges dans un registre de lunit centrale de traitement (CPU) plutt que dans la mmoire centrale ; de e e o e cette mani`re lacc`s ` leur valeur ne met pas en uvre le bus de la machine. e e a Les variables ainsi dclares doivent tre locales et dun type simple (nombre, pointeur). Elles sont automae e e tiquement initialises ` zro chaque fois quelles sont cres. Le compilateur accorde ce traitement spcial aux e a e ee e variables dans lordre o` elles gurent dans les dclarations. Lorsque cela nest plus possible (par exemple, parce u e que tous les registres de la CPU sont pris) les dclarations register restantes sont ignores. Il convient donc e e 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 intressante lorsque lon doit utiliser un compilateur e rustique, peu  optimisateur . Or de nos jours les compilateurs de C ont ni par devenir tr`s perfectionns et e e int`grent des algorithmes doptimisation, parmi lesquels la dtermination des variables critiques et leur allocation e e dans les registres de la CPU. Il sav`re alors que le programmeur, en appliquant le qualieur register ` ses e a variables prfres (quil croit critiques alors quelles ne le sont pas rellement), g`ne le travail du compilateur eee e e et obtient un programme moins ecace que sil navait jamais utilis ce qualieur. e

1.5.7

Variables constantes et volatiles

Le qualieur const plac devant une variable ou un argument formel informe le compilateur que la variable e ou largument en question ne changera pas de valeur tout au long de lexcution du programme ou de lactivation e de la fonction. Ce renseignement permet au compilateur doptimiser la gestion de la variable, la nature exacte dune telle optimisation ntant pas spcie. Par exemple un compilateur peut juger utile de ne pas allouer e e e du tout une variable qualie const et de remplacer ses occurrences par la valeur initiale10 indique lors de la e e dclaration. Il est conseill de toujours dclarer const les variables et les arguments formels qui peuvent ltre. e e e e Note. Cest regrettable mais, pour la plupart des compilateurs, une variable qualie const nest pas tout e a ` fait une expression constante au sens de la section 1.3.4. En particulier, pour ces compilateurs une variable, mme qualie const, ne peut pas tre utilise pour indiquer le nombre dlments dans une dclaration de e e e e ee e tableau. Le C ANSI introduit aussi les notions de pointeur constant et de pointeur sur constante, expliques ` la e a section 5.4.2. Le sens du qualieur volatile dpend lui aussi de limplantation. Il diminue le nombre dhypoth`ses, et e e donc doptimisations, que le compilateur peut faire sur une variable ainsi qualie. Par exemple toute variable e dont la valeur peut tre modie de mani`re asynchrone (dtection dinterruption, organe dentre-sortie, etc.) e e e e e doit tre qualie volatile, sur les syst`mes o` cela a un sens. Cela prvient le compilateur que sa valeur peut e e e u e changer mystrieusement, y compris dans une section du programme qui ne comporte aucune rfrence ` cette e ee a variable. Les compilateurs sont tenus de signaler toute tentative dcelable de modication dune variable const. Mis e a ` part cela, sur un syst`me particulier ces deux qualieurs peuvent navoir aucun autre eet. Ils nappartiennent e pas au C original.
10 La dclaration dune variable const doit ncessairement comporter une initialisation car sinon, une telle variable ne pouvant e e pas tre aecte par la suite, elle naurait jamais de valeur dnie. e e e

14

c H. Garreta, 2003

ELEMENTS DE BASE

1.6

Variables, fonctions et compilation spare e e

1.6
1.6.1

Variables, fonctions et compilation spare e e


Identicateurs publics et privs e

Examinons maintenant les r`gles qui rgissent la visibilit inter-chiers des identicateurs. La question ne e e e 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 tre partags par deux chiers. Il ny a pas de e e probl`me pour les variables locales, dont la visibilit se rduit ` ltendue de la fonction ou du bloc contenant e e e a e leur dnition. Il ne sera donc question que des noms des variables globales et des noms des fonctions. e Jargon. Identicateurs publics et privs. Un nom de variable ou de fonction dni dans un chier source et e e pouvant tre utilis dans dautres chiers sources est dit public. Un identicateur qui nest pas public est appel e e e priv. e Regle 1. Sauf indication contraire, tout identicateur global est public ; le qualieur static, prcdant la dclaration dun identicateur global, rend celui-ci priv. e e e e On prendra garde au fait que le qualieur static na pas le mme eet quand il sapplique ` un identicateur e a local (static change la dure de vie, dautomatique en statique, sans toucher ` la visibilit) et quand il sapplique e a e a ` un identicateur global (static change la visibilit, de publique en prive, sans modier la dure de vie). e e e Lorsquun programme est dcompos en plusieurs chiers sources il est fortement conseill, pour ne pas dire e e e obligatoire, dutiliser le qualieur static pour rendre privs tous les identicateurs qui peuvent ltre. Si on ne e e suit pas cette recommandation on verra des chiers qui taient corrects sparment devenir errons lorsquils e e e e sont relis, uniquement parce quils partagent ` tort des identicateurs publics. e a 1.6.2 Dclaration dobjets externes e

Nous ne considrons donc dsormais que les noms publics. Un identicateur rfrenc dans un chier alors e e ee e quil est dni dans un autre chier est appel externe. En gnral, les noms externes doivent faire lobjet dune e e e e dclaration : le compilateur ne traitant quun chier ` la fois, les proprits de lobjet externe doivent tre e a ee e indiques pour que la compilation puisse avoir lieu correctement. e Jargon. Dnition et dclaration dune variable ou dune fonction. Aussi bien une dclaration quune e e e dnition dun nom de variable ou de fonction est une formule qui spcie la classe syntaxique (variable ou e e fonction) et les attributs (type, valeur initiale, etc.) de lidenticateur en question. En plus de cela : une dnition produit la cration de lobjet dont lidenticateur est le nom ; e e une dclaration se limite ` indiquer que lobjet en question a d tre cr dans un autre chier qui sera e a ue ee fourni lors de ldition de liens. e ( Crer  une variable ou une fonction cest rserver lespace correspondant, rempli par lventuelle valeur e e e initiale de la variable ou par le code de la fonction). Regle 2. Toute variable doit avoir t dnie (cest-`-dire dclare normalement) ou dclare externe avant son ee e a e e e e utilisation ; une fonction peut tre rfrence alors quelle na encore fait lobjet daucune dnition ni dclaration e ee e e e externe ; elle est alors suppose tre e e externe, a e ` rsultat entier (int), sans prototype (cf. section 4.2) ; e a e e e e e par consquent, si une fonction nest pas ` rsultat entier alors elle doit tre soit dnie soit dclare externe avant son appel, mme si elle est ultrieurement dnie dans le chier o` gure lappel. e e e u La dclaration externe dune variable sobtient en faisant prcder une dclaration ordinaire du mot-cl e e e e e extern. Exemple : extern unsigned long n; Dans un autre chier cette variable aura t dnie : ee e unsigned long n; La dclaration externe dune variable doit tre identique, au mot extern pr`s, ` sa dnition. Sauf pour les e e e a e deux points suivants : e e une dclaration externe ne doit pas comporter dinitialisateur (puisque la dclaration externe nalloue pas la variable),
c H. Garreta, 2003

15

1.6 Variables, fonctions et compilation spare e e

1 ELEMENTS DE BASE

dans une dclaration externe de tableau, il est inutile dindiquer la taille de celui-ci (puisque la dclaration e e externe nalloue pas le tableau). Exemple. Dans le chier o` sont dnies les variables n et table, on crira : u e e unsigned long n = 1000; int table[100]; Dans un autre chier, o` ces variables sont uniquement rfrences, on crira : u ee e e extern unsigned long n; extern int table[]; La dclaration externe dune fonction sobtient en crivant len-tte de la fonction, prcd du mot extern e e e e e e et suivi dun point-virgule ; le mot extern est facultatif. Exemple : dnition de la fonction e double sqr(double x) { return x * x; } Dclaration externe dans un autre chier : e double sqr(double x); ou double sqr(double); ou lun ou lautre de ces noncs, prcd du mot extern. e e e e e En syntaxe originale (cest-`-dire  sans prototype ) il faut en outre ne pas crire les arguments formels. a e Dnition : e double sqr(x) double x; { return x * x; } Dclaration externe dans un autre chier : e double sqr(); Regle 3. Dans lensemble des chiers qui constituent un programme, chaque nom public : doit faire lobjet dune et une seule dnition ; e peut tre dclar externe (y compris dans le chier o` il est dni) un nombre quelconque de fois. e e e u e Cette r`gle volontariste est simple et elle exprime la meilleure faon de programmer. Il faut savoir cependant e c que chaque syst`me tol`re des carts, qui rv`lent surtout la rusticit de lditeur de liens sous-jacent. La clart e e e e e e e e des concepts et la abilit des programmes y perdent beaucoup. e Un comportement frquent est le suivant : appelons momentanment  dclaration-dnition  une expression e e e e gnrale de la forme e e externopt declaration Nous pouvons donner la r`gle relche : e a e Regle 3. Dans lensemble des chiers qui constituent un programme, chaque nom public peut faire lobjet dun nombre quelconque de dclarations-dnitions, mais : e e e e e il doit y avoir au moins une dclaration-dnition sans le mot-cl extern ; e e il peut y avoir au plus une dclaration-dnition comportant un initialisateur. Des techniques et conseils pour crire des programmes modulaires en C sont exposs ` la section 8.2. e e a = initialisateur rien ;

16

c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2
2.1

Oprateurs et expressions e
Gnralits e e e

Dans cette section nous tudions les oprateurs et les expressions du langage C. Les expressions simples sont e e les constantes littrales (0, A, 0.31416e1), les constantes symboliques (lundi, false) et les noms de variables e (x, nombre). Les oprateurs servent ` construire des expressions complexes, comme 2 * x ou sin(0.31416e1). e a Les proprits dune expression complexe dcoulent essentiellement de la nature de loprateur qui chapeaute ee e e lexpression. On peut distinguer les expressions pures des expressions ` eet de bord. Dans tous les cas une expression a reprsente une valeur. Une expression pure ne fait que cela ; ltat du syst`me est le mme avant et apr`s son e e e e e valuation. Au contraire, une expression ` eet de bord modie le contenu dune ou plusieurs variables. Par e a exemple, lexpression y + 1 est pure, tandis que laectation x = y + 1 (qui en C est une expression) est ` eet a de bord, car elle modie la valeur de la variable x. Comme nous le verrons, en C un grand nombre dexpressions sont ` eet de bord. a Remarque 1. Les oprateurs dont il sera question ici peuvent aussi appara dans les dclarations, pour e tre e la construction des types drivs (tableaux, pointeurs et fonctions) comme dans la dclaration complexe : e e e char (*t[20])(); La signication des expressions ainsi crites est alors tr`s dirente de celle des expressions gurant dans la e e e partie excutable des programmes, mais on peut signaler dores et dj` que toutes ces constructions obissent aux e ea e mmes r`gles de syntaxe, notamment pour ce qui concerne la priorit des oprateurs et lusage des parenth`ses. e e e e e La question des dclarations complexes sera vue ` la section 5.4. e a Remarque 2. Cest une originalit de C que de considrer les  dsignateurs  complexes (les objets points, e e e e les lments des tableaux, les champs des structures, etc.) comme des expressions construites avec des oprateurs ee e et obissant ` la loi commune, notamment pour ce qui est des priorits. On se souvient quen Pascal les signes e a e qui permettent dcrire de tels dsignateurs (cest-`-dire les slecteurs [], ^ et .) nont pas statut doprateur. Il e e a e e ne viendrait pas ` lide dun programmeur Pascal dcrire 2 + (t[i]) an de lever une quelconque ambigu e a e e t sur la priorit de + par rapport ` celle de [], alors quen C de telles expressions sont habituelles. Bien sr, dans e a u 2 + (t[i]) les parenth`ses sont superues, car la priorit de loprateur [] est suprieure ` celle de +, mais ce e e e e a nest pas le cas dans (2 + t)[i], qui est une expression tout aussi lgitime que la prcdente. e e e 2.1.1 Lvalue et rvalue

Toute expression poss`de au moins deux attributs : un type et une valeur. Par exemple, si i est une variable e enti`re valant 10, lexpression 2 * i + 3 poss`de le type entier et la valeur 23. Dans la partie excutable des e e e programmes on trouve deux sortes dexpressions : Lvalue (expressions signiant  le contenu de... ). Certaines expressions sont reprsentes par une formule e e qui dtermine un emplacement dans la mmoire. La valeur de lexpression est alors dnie comme le e e e contenu de cet emplacement. Cest le cas des noms des variables, des composantes des enregistrements et des tableaux, etc. Une lvalue poss`de trois attributs : une adresse, un type et une valeur. Exemples : x, e table[i], fiche.numero. Rvalue (expressions signiant  la valeur de... ). Dautres expressions ne sont pas associes ` un emplae a cement de la mmoire : elles ont un type et une valeur, mais pas dadresse. Cest le cas des constantes et e des expressions dnies comme le rsultat dune opration arithmtique. Une rvalue ne poss`de que deux e e e e e attributs : type, valeur. Exemples : 12, 2 * i + 3. Toute lvalue peut tre vue comme une rvalue ; il sut dignorer le contenant (adresse) pour ne voir que le e contenu (type, valeur). La raison principale de la distinction entre lvalue et rvalue est la suivante : seule une lvalue peut gurer ` gauche du signe = dans une aectation. Cela justie les appellations  left value  et  right a value . Les sections suivantes prciseront, pour chaque sorte dexpression complexe, sil sagit ou non dune lvalue. e Pour les expressions simples, cest-`-dire les constantes littrales et les identicateurs, la situation est la suivante : a e sont des lvalue : les noms des variables simples : nombres et pointeurs, les noms des variables dun type struct ou union ne sont pas des lvalue : les constantes, les noms des variables de type tableau,
c H. Garreta, 2003

17

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

les noms des fonctions. 2.1.2 Priorit des oprateurs e e

C comporte de tr`s nombreux oprateurs. La plupart des caract`res spciaux dsignent des oprations et e e e e e e subissent les mmes r`gles syntaxiques, notamment pour ce qui concerne le jeu des priorits et la possibilit de e e e e parenthsage. La table 2 montre lensemble de tous les oprateurs, classs par ordre de priorit. e e e e prior 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 oprateurs e () [] ! ~ *bin / + -bin << >> < <= == != &bin ^ | && || ? : = *= , sens de lassociativit e sizeof (type) >>= &= ^= |=

. ++ %

-> --

-un

*un

&un

>

>=

/=

%=

+=

-=

<<=

Tab. 2 Oprateurs du langage C e Remarque. Dans certains cas un mme signe, comme -, dsigne deux oprateurs, lun unaire (` un argue e e a ment), lautre binaire (` deux arguments). Dans le tableau 2, les suxes un et bin prcisent de quel oprateur a e e il sagit. Le signe indique lassociativit  de gauche ` droite  ; par exemple, lexpression x - y - z signie (x e a y) - z. Le signe indique lassociativit de  droite ` gauche  ; par exemple, lexpression x = y = z signie e a x = (y = z). Notez que le sens de lassociativit des oprateurs prcise la signication dune expression, mais non la e e e chronologie de lvaluation des sous-expressions. e

2.2
2.2.1

Prsentation dtaille des oprateurs e e e e


Appel de fonction ()

Opration : application dune fonction ` une liste de valeurs. Format : e a exp 0 ( exp 1 , ... exp n ) exp 1 , ... exp n sont les arguments eectifs de lappel de la fonction. exp 0 doit tre de type fonction rendant une e valeur de type T ou bien11 adresse dune fonction rendant une valeur de type T. Alors exp 0 ( exp 1 , ... exp n ) poss`de le type T. La valeur de cette expression dcoule de la dnition de la fonction en question (` lintrieur e e e a e de la fonction, cette valeur est prcise par une ou plusieurs instructions  return exp ; ). e e Lexpression exp 0 ( exp 1 , ... exp n ) nest pas une lvalue. Exemple : y = sqr(2 * x) + 3; sachant que sqr est le nom dune fonction rendant un double, lexpression sqr(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 de son argument, soit ici la valeur 4x2 . e Les contraintes supportes par les arguments eectifs ne sont pas les mmes dans le C ANSI et dans le C e e original (ceci est certainement la plus grande dirence entre les deux versions du langage). e En C ANSI, si la fonction a t dnie ou dclare avec prototype (cf. section 4.1) : ee e e e
11 Cette

double possibilit est commente ` la section 6.3.4. e e a

18

c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

le nombre des arguments eectifs doit correspondre ` celui des arguments formels12 ; a chaque argument eectif doit tre compatible, au sens de laectation, avec largument formel correspone dant ; la valeur de chaque argument eectif subit ventuellement les mmes conversions quelle subirait dans e e laectation argument formel = argument eectif En C original, ou en C ANSI si la fonction na pas t dnie ou dclare avec prototype : ee e e e aucune contrainte (de nombre ou de type) nest impose aux arguments eectifs ; e 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 sqr dnie ` la section 1.6.2, lappel e a y = sqr(2); est erron en C original (la fonction reoit la reprsentation interne de la valeur enti`re 2 en  croyant  que e c e e cest la reprsentation interne dun double) mais il est correct en C ANSI (lentier 2 est converti en double au e moment de lappel). Toutes ces questions sont reprises plus en dtail ` la section 4. e a Attention. On notera que les parenth`ses doivent appara e tre, mme lorsque la liste des arguments est vide. e Leur absence ne provoque pas derreur, mais change compl`tement le sens de lexpression. Cela constitue un e pi`ge assez vicieux tendu aux programmeurs dont la langue maternelle est Pascal. e 2.2.2 Indexation []

Definition restreinte (lment dun tableau). Opration : acc`s au ieme lment dun tableau. Format : ee e e ee exp 0 [ exp 1 ] exp 0 doit tre de type  tableau dobjets de type T , exp 1 doit tre dun type entier. Alors exp 0 [exp 1 ] est de e e type T ; cette expression dsigne llment du tableau dont lindice est donn par la valeur de exp 1 . e ee e Exemple. Lexpression t[i] dsigne le (i+1)eme lment du tableau t. e ee Deux dtails auxquels on tient beaucoup en C : e le premier lment dun tableau a toujours lindice 0 ; ee il nest jamais vri que la valeur de lindice dans une rfrence ` un tableau appartient ` lintervalle e e ee a a 0...N 1 dtermin par le nombre N dlments allous par la dclaration du tableau. Autrement dit, il e e ee e e ny a jamais de  test de dbordement . e En C, les tableaux sont toujours ` un seul indice ; mais leurs composantes peuvent tre ` leur tour des a e a tableaux. Par exemple, un lment dune matrice rectangulaire sera not : ee 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 t dclare par une expression de ee e e la forme (NL et NC sont des constantes) : double m[NL][NC]; ` Definition complete (indexation au sens large). Opration : acc`s ` un objet dont ladresse est donne e e a e par une adresse de base et un dplacement. Format : e exp 0 [ exp 1 ] exp 0 doit tre de type  adresse dun objet de type T , exp 1 doit tre de type entier. Alors exp 0 [exp 1 ] dsigne e e e lobjet de type T ayant pour adresse (voir la gure 2) : exp 0 + exp 1 taille(T) Il est clair que, si exp 0 est de type tableau, les deux dnitions de lindexation donnes co e e ncident. Exemple : si t est un tableau dentiers et p un  pointeur vers entier  auquel on a aect ladresse de t[0], alors les e expressions suivantes dsignent le mme objet : e e t[i]
12 Il

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.
existe nanmoins un moyen pour crire des fonctions avec un nombre variable darguments (cf. section 4.3.4) e e

c H. Garreta, 2003

19

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

exp0

exp1

Fig. 2 Lindexation

2.2.3

Slection . e

Opration : acc`s ` un champ dune structure ou dune union. Format : e e a exp . identif exp doit possder un type struct ou union, et identif doit tre le nom dun des champs de la structure ou de e e lunion en question. En outre, exp doit tre une lvalue. Alors, exp.identif dsigne le champ identif de lobjet e e dsigne par exp. e Cette expression est une lvalue, sauf si elle est de type tableau. Exemple. Avec la dclaration e struct personne { long int num; struct { char rue[32]; char *ville; } adresse; } fiche; les expressions fiche.num fiche.adresse fiche.adresse.rue etc. dsignent les divers champs de la variable che. Seules les deux premi`res sont des lvalue. e e

2.2.4

Slection dans un objet point -> e e

Opration : acc`s au champ dune structure ou dune union pointe. Format : e e e exp->identif exp doit possder le type  adresse dune structure ou dune union  et identif doit tre le nom dun des champs e e de la structure ou de lunion en question. Dans ces conditions, exp->identif dsigne le champ identif de la e structure ou de lunion dont ladresse est indique par la valeur de exp. e Cette expression est une lvalue, sauf si elle est de type tableau. Ainsi, exp->identif est une autre mani`re de noter (*exp).identif (remarquez que les parenth`ses sont e e indispensables). Par exemple, avec la dclaration : e 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

Ngation ! e

Opration : ngation logique. Format : e e !exp 20


c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

Aucune contrainte. Cette expression dsigne une valeur de lensemble {0, 1}, dnie par : e e !exp Cette expression nest pas une lvalue. Remarque. Bien que cela ne soit pas exig par le langage C, on vitera de  nier  (et plus gnralement de e e e e comparer ` zro) des expressions dun type ottant (float, double). A cause de limprcision des calculs avec a e e de tels nombres, lgalit ` zro dun ottant nest souvent que le fruit du hasard. e ea e 2.2.6 Complment ` 1 ~ e a 1, si exp = 0 0, si exp = 0

Opration : ngation bit ` bit. Format : e e a ~exp exp doit tre dun type entier. Cette expression dsigne lobjet de mme type que exp qui a pour codage interne e e e 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 complment ` un nest pas une opration abstraite (cf. section 2.3.3). La portabilit dun e a e e programme o` cet oprateur gure nest donc pas assure. u e e 2.2.7 Les cl`bres ++ et -ee

Il existe deux oprateurs unaires ++ dirents : lun est postx (crit derri`re loprande), lautre prx e e e e e e e e (crit devant). e 1. Opration : post-incrmentation. Format : e e exp++ exp doit tre de type numrique (entier ou ottant) ou pointeur. Ce doit tre une lvalue. Cette expression est e e e caractrise par : e e un type : celui de exp ; une valeur : la mme que exp avant lvaluation de exp++ ; e e un eet de bord : le mme que celui de laectation exp = exp + 1. e 2. Opration : pr-incrmentation. Format : e e e ++exp exp doit tre de type numrique (entier ou ottant) ou pointeur. Ce doit tre une lvalue. Cette expression est e e e caractrise par : e e un type : celui de exp ; une valeur : la mme que exp apr`s lvaluation de exp++ ; e e e un eet de bord : le mme que celui de laectation exp = exp + 1. e Les expressions exp++ et ++exp ne sont pas des lvalue. Exemple. Laectation y = x++ ; y = ++x ; quivaut ` e a y = x; x = x + 1; x = x + 1; y = x;

Loprateur ++ bncie de larithmtique des adresses au mme titre que +. Ainsi, si exp est de type e e e e e  pointeur vers un objet de type T , la quantit eectivement ajoute ` exp par lexpression exp++ dpend de e e a e la taille de T. Il existe de mme deux oprateurs unaires -- donnant lieu ` des expressions exp-- et --exp. Lexplication e e a est la mme, en remplaant +1 par 1. e c Application : ralisation dune pile. Il est tr`s agrable de constater que les oprateurs ++ et -- et la e e e e mani`re dindexer les tableaux en C se combinent harmonieusement et permettent par exemple la ralisation e e simple et ecace de piles par des tableaux, selon le schma suivant : e

c H. Garreta, 2003

21

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

dclaration et initialisation dune pile (OBJET est un type prdni, dpendant du probl`me particulier e e e e e considr ; MAXPILE est une constante reprsentant un majorant du nombre dlments dans la pile) : ee e ee OBJET espace[MAXPILE]; int nombreElements = 0; opration  empiler la valeur de x  : e espace[nombreElements++] = x; e e opration  dpiler une valeur et la ranger dans x  : x = espace[--nombreElements]; On notera que, si on proc`de comme indiqu ci-dessus, la variable nombreElements poss`de constamment e e e la valeur que son nom sugg`re : le nombre dlments eectivement prsents dans la pile. e ee e

2.2.8

Moins unaire -

Opration : changement de signe. Format : e -exp exp doit tre une expression numrique (enti`re ou relle). Cette expression reprsente lobjet de mme type e e e e e e que exp dont la valeur est loppose de celle de exp. Ce nest pas une lvalue. e

2.2.9

Indirection *

Opration : acc`s ` un objet point. On dit aussi  drference . Format : e e a e ee *exp exp doit tre une expression de type  adresse dun objet de type T . *exp reprsente alors lobjet de type T e e ayant pour adresse la valeur de exp. Lexpression *exp est une lvalue. Remarque. On prendra garde au fait quil existe un bon nombre doprateurs ayant une priorit suprieure e e e a ` celle de *, ce qui oblige souvent ` utiliser des parenth`ses. Ainsi par exemple les expressions Pascal e[i] et a e e[i] doivent respectivement scrire, en C, (*e)[i] et *(e[i]), la deuxi`me pouvant aussi scrire *e[i]. e e e

2.2.10

Obtention de ladresse &

Opration : obtention de ladresse dun objet occupant un emplacement de la mmoire. Format : e e &exp exp doit tre une expression dun type quelconque T. Ce doit tre une lvalue. Lexpression &exp a pour type e e  adresse dun objet de type T  et pour valeur ladresse de lobjet reprsent par exp. Lexpression &exp nest e e pas une lvalue. Ainsi, si i est une variable de type int et p une variable de type  pointeur vers un int , alors ` la suite a de linstruction p = &i; i et *p dsignent le mme objet. e e Exemple 1. Lutilisation la plus frquente de cet oprateur est lobtention de ladresse dun objet en vue de e e son passage ` une fonction qui le modie : a scanf("%d %lf %s", &i, &t[j], &p->nom); Exemple 2. Une autre utilisation lgante de cet oprateur est la cration de composantes  xes  dans ee e e les structures cha ees. Le programme suivant dclare une liste cha ee circulaire reprsente par le pointeur n e n e e entree et rduite pour commencer ` un unique maillon qui est son propre successeur (voir gure 3) ; dautres e a maillons seront crs dynamiquement durant lexcution : ee e 22
c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

entree

en_tete_fixe
Fig. 3 Maillon xe en tte dune liste cha ee e n

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 rduite ` un nom de tableau, ` un nom de fonction ou ` une constante cha e a a a ne de caract`res est considre comme une constante de type adresse ; loprateur & appliqu ` de telles expressions e ee e ea est donc sans objet. Un tel emploi de & devrait tre considr comme erron, mais beaucoup de compilateurs se e ee e contentent de lignorer. 2.2.11 Oprateur sizeof e

Opration : calcul de la taille correspondant ` un type. Premi`re forme : e a e sizeof ( descripteur-de-type ) Cette expression reprsente un nombre entier qui exprime la taille quoccuperait en mmoire un objet possdant e e e le type indiqu (les descripteurs de types sont expliqus ` la section 5.4). e e a Deuxi`me forme : e sizeof exp exp est une expression quelconque, qui nest pas value par sizeof. Lexpression sizeof exp reprsente la e e e taille quoccuperait en mmoire un objet possdant le mme type que exp. e e e Dans un cas comme dans lautre sizeof... est une expression constante. En particulier, ce nest pas une lvalue. La taille des objets est exprime en nombre doctets. Plus exactement, lunit choisie est telle que la valeur e e de sizeof(char) soit 1 (on peut aussi voir cela comme une dnition du type char). e Remarque 1. Dans le C original, le type de lexpression sizeof... est int. Dans le C ANSI, ce type peut changer dun syst`me ` un autre. Pour cette raison il est dni, sous lappellation size t, dans le chier e a e stddef.h. Il est recommand de dclarer de type size t toute variable (resp. toute fonction) devant contenir e e (resp. devant renvoyer) des nombres qui reprsentent des tailles. e Remarque 2. Lorsque son oprande est un tableau (ou une expression de type tableau) la valeur rendue e par sizeof est lencombrement eectif du tableau. Par exemple, avec la dclaration e char tampon[80]; lexpression sizeof tampon vaut 80, mme si cela para en contradiction avec le fait que tampon peut tre vu e t e comme de type  adresse dun char . Il en dcoule une proprit bien utile : quel que soit le type du tableau e ee t, la formule sizeof t / sizeof t[0] exprime toujours le nombre dlments de t. ee 2.2.12 Conversion de type (cast operator)

Opration : conversion du type dune expression. Format : e ( type 2 ) exp type 2 reprsente un descripteur de type ; exp est une expression quelconque. Lexpression ci-dessus dsigne un e e lment de type type 2  proche  de llment reprsent par exp. ee ee e e
c H. Garreta, 2003

23

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

Lexpression (type 2 )exp nest pas une lvalue. Les conversions lgitimes (et utiles) sont13 : e Entier vers un entier plus long (ex. char int). Le codage de exp est tendu de telle mani`re que la e e valeur reprsente soit inchange. e e e Entier vers un entier plus court (ex. long short). Si exp est reprsentable dans le type de destination, e sa valeur est la mme apr`s conversion. Sinon, la valeur de exp est purement et simplement tronque (une e e e telle troncation, sans signication abstraite, est rarement utile). Entier sign vers entier non sign, ou le contraire. Cest une conversion  sans travail  : le compilateur e e se borne ` interprter autrement la valeur de exp, sans eectuer aucune transformation de son codage a e interne. Flottant vers entier : la partie fractionnaire de la valeur de exp est supprime. Par exemple, le ottant e 3.14 devient lentier 3. Attention, on peut voir cette conversion comme une ralisation de la fonction e mathmatique  partie enti`re , mais uniquement pour les nombres positifs : la conversion de -3.14 e e donne -3, non -4. Entier vers ottant. Sauf cas de dbordement (le rsultat est alors imprvisible), le ottant obtenu est e e e 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 change linterprtation de la valeur de exp, sans eectuer aucune transformation de son e codage interne. Danger ! Une telle conversion est enti`rement place sous la responsabilit du programmeur, le compilateur e e e laccepte toujours. Entier vers adresse dun objet de type T. Cest encore une conversion sans travail : la valeur de exp est interprte comme un pointeur, sans transformation de son codage interne. ee Danger ! Une telle conversion est enti`rement place sous la responsabilit du programmeur. De plus, si la e e e reprsentation interne de exp na pas la taille voulue pour un pointeur, le rsultat est imprvisible. e e e Les conversions o` le type de exp ou le type type 2 est un type struct ou union sont interdites. u Exemple. Une utilisation classique de loprateur de conversion de type entre types pointeurs consiste ` e a sen servir pour  voir  un espace mmoire donn par son adresse comme possdant une certaine structure alors e e e quen fait il na pas t ainsi dclar : ee e e dclaration dune structure : e struct en_tete { long taille; struct en_tete *suivant; }; dclaration dun pointeur  gnrique  : e e e void *ptr; imaginez que pour des raisons non dtailles ici ptr poss`de ` un endroit donn une valeur quil est e e e a e lgitime de considrer comme ladresse dun objet de type struct en tete (alors que ptr na pas ce e e type-l`). Voici un exemple de manipulation cet objet : a ((struct en_tete *) ptr)->taille = n; Bien entendu, une telle conversion de type est faite sous la responsabilit du programmeur, seul capable de e  prouver  qu` tel moment du programme ptr pointe bien un objet de type struct en tete. a Remarque. En toute rigueur, le fait que lexpression dtermine par un oprateur de conversion de type ne e e e soit pas une lvalue interdit des expressions qui auraient pourtant t pratiques, comme ee ((int) x)++; Cependant, beaucoup de compilateurs acceptent cette expression, la traitant comme x = (le type de x)((int) x + 1) ;
13 Attention. Lexpression (type)exp reprsente une valeur obtenue ` partir de celle de exp. Dans le cas o` exp est une variable, e a u il ne faut pas oublier que, contrairement ` ce que pourraient suggrer certaines explications donnes ici, lexpression (type)exp ne a e e modie pas exp.

24

c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

2.2.13

Oprateurs arithmtiques e e

Ce sont les oprations arithmtiques classiques : addition, soustraction, multiplication, division et modulo e e (reste de la division enti`re). Format : e + - * exp1 exp2 / % Aucune de ces expressions nest une lvalue. Avant lvaluation de lexpression, les oprandes subissent les conversions  usuelles  (cf. section 2.3.1). Le e e langage C supporte larithmtique des adresses (cf. section 6.2.1). e A propos de la division. En C on note par le mme signe la division enti`re, qui prend deux oprandes e e e entiers (short, int, long, etc.) et donne un rsultat entier, et la division ottante dans laquelle le rsultat est e e ottant (float, double). Dautre part, les r`gles qui commandent les types des oprandes et du rsultat des e e e expressions arithmtiques (cf. section 2.3.1), et notamment la r`gle dite  du plus fort , ont la consquence e e e importante suivante : dans lexpression expr 1 / expr 2 si expr 1 et expr 2 sont toutes deux enti`res alors  /  est traduit par lopration  division enti`re 14 , et e e e le rsultat est entier, e e e si au moins une des expressions expr 1 ou expr 2 nest pas enti`re, alors lopration faite est la division ottante des valeurs de expr 1 et expr 2 toutes deux converties dans le type double. Le rsultat est une e valeur double qui approche le rationnel expr1 du mieux que le permet la la prcision du type double. e expr2 Il rsulte de cette r`gle un pi`ge auquel il faut faire attention : 1/2 ne vaut pas 0.5 mais 0. De mme, dans e e e e int somme, nombre; float moyenne; ... moyenne = somme / 100; la valeur de moyenne nest pas ce que ce nom sugg`re, car somme et 100 sont tous deux entiers et lexpression e somme / 100 est enti`re, donc tronque (et il ne faut pas croire que laectation ultrieure ` la variable ottante e e e a moyenne pourra retrouver les dcimales perdues). Dans ce cas particulier, la solution est simple : e moyenne = somme / 100.0; Mme probl`me si le dnominateur avait t une variable enti`re, comme dans e e e ee e moyenne = somme / nombre; ici la solution est un peu plus complique : e moyenne = somme / (double) nombre;

2.2.14

Dcalages << >> e

Opration : dcalages de bits. Format : e e exp1 << >> exp2

exp 1 et exp 2 doivent tre dun type entier (char, short, long, int...). Lexpression exp 1 << exp 2 (resp. exp 1 >> e exp 2 ) reprsente lobjet de mme type que exp 1 dont la reprsentation interne est obtenue en dcalant les bits e e e e de la reprsentation interne de exp 1 vers la gauche15 (resp. vers la droite) dun nombre de positions gal ` la e e a valeur de exp 2 . Autrement dit,
14 On prendra garde au fait que si les oprandes ne sont pas tous deux positifs la division enti`re du langage C ne coincide pas e e avec le  quotient par dfaut  (quotient de la  division euclidienne  des matheux). e 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 dfaut de 17 par 5 est plutt 4 (17 = 5 (4) + 3). e o 15 Par convention la droite dun nombre cod en binaire est le ct des bits de poids faible (les units) tandis que la gauche est e o e e celui des bits de poids forts (correspondant aux puissances 2k avec k grand).

c H. Garreta, 2003

25

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

exp 1 << exp 2 est la mme chose que e ((exp 1 << 1) << 1) ... << 1 sin on peut considrer que la formule << 1 appara exp 2 fois dans lexpression ci-dessus. Remarque analogue e t pour le dcalage ` droite >>. e a Les bits sortants sont perdus. Les bits entrants sont : dans le cas du dcalage ` gauche, des zros ; e a e e a e e e dans le cas du dcalage ` droite : si exp 1 est dun type non sign, des zros ; si exp 1 est dun type sign, des copies du bit de signe16 . Autrement dit, si on suppose que la valeur de exp 1 est code sur huit bits, nots e e b7b6b5b4b3b2b1b0 (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 lvaluation du rsultat, les oprandes subissent les conversions usuelles (cf. section 2.3.1). Ces exe e e pressions ne sont pas des lvalue. Remarque. Les oprateurs de dcalage de bits ne sont pas des oprations abstraites (cf. section 2.3.3). La e e e portabilit dun programme o` ils gurent nest donc pas assure. e u e 2.2.15 Comparaisons == != < <= > >=

Il sagit des comparaisons usuelles : gal, dirent, infrieur, infrieur ou gal, suprieur, suprieur ou gal. e e e e e e e e Format : == != < exp1 exp2 <= > >= exp 1 et exp 2 doivent tre dun type simple (nombre ou pointeur). Cette expression reprsente lun des lments e e ee (de type int) 1 ou 0 selon que la relation indique est ou non vrie par les valeurs de exp 1 et exp 2 . e e e Avant la prise en compte de loprateur, les oprandes subissent les conversions usuelles (cf. section 2.3.1). e e Ces expressions ne sont pas des lvalue. Notez que, contrairement ` ce qui se passe en Pascal, ces oprateurs ont une priorit suprieure ` celle des a e e e a connecteurs logiques, ce qui vite beaucoup de parenth`ses disgracieuses. Ainsi, en C, lexpression e e 0 <= x && x < 10 a une syntaxe correcte. A propos de  etre vrai  et  etre non nul . Comme on la dit (cf. section 1.4.1), le type boolen e 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 consquence de ce fait est la suivante : ` la place de e a if (i != 0) etc. while (*ptchar != \0) etc. for (p = liste; p != NULL; p = p->suiv) etc. on peut crire respectivement e if (i) etc. while (*ptchar) etc. for (p = liste; p; p = p->suiv) etc. On admet gnralement qu` cause de proprits techniques des processeurs, ces expressions raccourcies e e a ee ralisent une certaine optimisation des programmes. Cest pourquoi les premiers programmeurs C, qui ne dispoe saient que de compilateurs simples, peu optimisateurs, ont pris lhabitude de les utiliser largement. On ne peut
16 De cette mani`re le dcalage ` gauche correspond (sauf dbordement) ` la multiplication par 2exp2 , tandis que le dcalage ` e a e e a e a droite correspond ` la division enti`re par 2exp2 , aussi bien si exp 1 est signe que si elle est non signe a e e e

26

c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

plus aujourdhui conseiller cette pratique. En eet, les compilateurs actuels sont susamment perfectionns e pour eectuer de leur propre chef cette sorte doptimisations et il ny a plus aucune justication de lemploi de telles comparaisons implicites, qui rendent les programmes bien moins expressifs. Attention. La relation dgalit se note ==, non =. Voici une faute possible chez les nouveaux venus ` C e e a en provenance de Pascal qui, pour traduire le bout de Pascal if a = 0 then etc., crivent e if (a = 0) etc. Du point de vue syntaxique cette construction est correcte, mais elle est loin davoir leet escompt. En tant e quexpression, a = 0 vaut 0 (avec, comme eet de bord, la mise ` zro de a). Bien sr, il fallait crire a e u e if (a == 0) etc.

2.2.16

Oprateurs de bits & | ^ e

Ces oprateurs reprsentent des oprations logiques sur les bits des reprsentations internes des valeurs des e e e e oprandes : AND, OR, XOR. Format : e & | exp2 exp1 ^ exp 1 et exp 2 doivent tre dun type entier. Cette expression reprsente un objet de type entier dont le codage e e interne est construit bit par bit ` partir des bits correspondants des valeurs de exp 1 et exp 2 , selon la table a suivante, dans laquelle (exp k )i signie  le ieme bit de exp k  : (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 lvaluation du rsultat, les oprandes subissent les conversions usuelles (cf. section 2.3.1). Ces exe e e pressions ne sont pas des lvalue. Exemple. Un cas frquent dutilisation de ces oprateurs est la ralisation densembles, ou plutt de souse e e o ensembles dun intervalle dentiers [ 0 ... N 1 ], N valant 8, 16 ou 32. Par exemple, dans une biblioth`que graphique on peut souhaiter une fonction eectuant lachage dun e texte muni dun ensemble dattributs (gras, italique, soulign, capitalis, etc.) telle que : e e void afficher(char *texte, unsigned long attributs); Pour faire en sorte que largument attributs puisse reprsenter un ensemble dattributs quelconque, on e associe les attributs ` des entiers conventionnels : a #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 utilises pour reprsenter les attributs sont des puissances de 2 distinctes, cest-`-dire des nombres e e a qui, crits en binaire, comportent un seul 1 plac diremment de lun ` lautre. e e e a Lutilisateur dne telle fonction emploie loprateur | pour composer, lors de lappel, lensemble dattributs e qui lintresse : e afficher("Bonjour", GRAS | ITALIQUE); // affichage en gras et italique De son ct, lauteur de la fonction utilise loprateur & pour savoir si un attribut appartient ou non ` oe e a lensemble donn : e ... if ((attributs & GRAS) != 0) prendre les dispositions ncessaires pour acher en gras e else if ((attributs & ITALIQUE) != 0) prendre les dispositions ncessaires pour acher en italique e ...

c H. Garreta, 2003

27

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

2.2.17

Connecteurs logiques && et ||

Oprations : conjonction et disjonction. Format : e exp1 && || exp2

exp 1 et exp 2 sont deux expressions de nimporte quel type. Cette expression reprsente un lment de type int e ee parmi { 0, 1 } dni de la mani`re suivante : e e Pour valuer exp 1 && exp 2 : exp 1 est value dabord et e e e si la valeur de exp 1 est nulle, exp 2 nest pas value et exp 1 && exp 2 vaut 0 ; e e sinon exp 2 est value et exp 1 && exp 2 vaut 0 ou 1 selon que la valeur de exp 2 est nulle ou non. e e Pour valuer exp 1 || exp 2 : exp 1 est value dabord et e e e si la valeur de exp 1 est non nulle, exp 2 nest pas value et exp 1 || exp 2 vaut 1 ; e e sinon exp 2 est value et exp 1 || exp 2 vaut 0 ou 1 selon que la valeur de exp 2 est nulle ou non. e e Ces expressions ne sont pas des lvalue. Applications. Ainsi, C garantit que le premier oprande sera valu dabord et que, sil sut ` dterminer e e e a e le rsultat de la conjonction ou de la disjonction, alors le second oprande ne sera mme pas valu. Pour le e e e e e programmeur, cela est une bonne nouvelle. En eet, il nest pas rare quon crive des conjonctions dont le e premier oprande  prot`ge  (dans lesprit du programmeur) le second ; si cette protection ne fait pas partie e e de la smantique de loprateur pour le langage utilis, le programme rsultant peut tre faux. Considrons e e e e e e lexemple suivant : un certain tableau table est form de nombre cha e nes de caract`res ; soit ch une variable e cha Lopration  recherche de ch dans table  peut scrire : ne. e e 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 value quapr`s avoir vri e e e e 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 consquences tout ` fait dsastreuses). e a e Une autre consquence de cette mani`re de concevoir les connecteurs logiques est stylistique. En C la conjonce e tion et la disjonction svaluent dans lesprit des instructions plus que dans celui des oprations. Lapplication e e pratique de cela est la possibilit dcrire sous une forme fonctionnelle des algorithmes qui dans dautres lane e gages restent squentiels. Voici un exemple : le prdicat qui caractrise la prsence dun lment dans une liste e e e e ee cha ee. Version (rcursive) habituelle : n e 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 smantique des oprateurs && et || : e e 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 ? :

Opration : sorte de if...then...else... se prsentant sous forme dexpression. Format : e e exp 0 ? exp 1 : exp 2 exp 0 est dun type quelconque. exp 1 et exp 2 doivent tre de types compatibles. Cette expression est value de e e e la mani`re suivante : e La condition exp 0 est value dabord e e si sa valeur est non nulle, exp 1 est value et dnit la valeur de lexpression conditionnelle. Dans ce cas, e e e exp 2 nest pas value ; e e 28
c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.2

Prsentation dtaille des oprateurs e e e e

sinon, exp 2 est value et dnit la valeur de lexpression conditionnelle. Dans ce cas, exp 1 nest pas e e e value. e e Lexpression exp 0 ?exp 1 :exp 2 nest pas une lvalue. Exemple. Loprateur conditionnel est souvent interchangeable avec linstruction conditionnelle, mais pere met parfois de rels allgements du code. Imaginons un programme devant acher un des textes non ou oui e e selon que la valeur dune variable reponse est nulle ou non. Solutions classiques de ce micro-probl`me : e if (reponse) printf("la rponse est oui"); e else printf("la rponse est non"); e ou bien, avec une variable auxiliaire : char *texte; ... if (reponse) texte = "oui"; else texte = "non"); printf("la rponse est %s", texte); e Avec loprateur conditionnel : e printf("la rponse est %s", reponse ? "oui" : "non"); e 2.2.19 Aectation =

Opration : aectation, considre comme une expression. Format : e ee exp 1 = exp 2 exp 1 doit tre une lvalue. Soit type 1 le type de exp 1 ; laectation ci-dessus reprsente le mme objet que e e e ( type 1 ) exp 2 (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 mmoire dtermin par exp 1 . e e e Lexpression exp 1 = exp 2 nest pas une lvalue. Contrairement ` ce qui se passe dans dautres langages, une aectation est donc considre en C comme une a ee sorte dexpression : elle  fait  quelque chose, mais aussi elle  vaut  une certaine valeur et, ` ce titre, elle peut a gurer comme oprande dans une sur-expression. On en dduit la possibilit des aectations multiples, comme e e e dans lexpression : a = b = c = 0; comprise comme a = (b = (c = 0)). Elle aura donc le mme eet que les trois aectations a = 0 ; b = 0 ; e c = 0 ;. Autre exemple, lecture et traitement dune suite de caract`res dont la n est indique par un point : e e while ((c = getchar()) != .) exploitation de c Des contraintes p`sent sur les types des deux oprandes dune aectation exp 1 = exp 2 . Lorsquelles sont e e satisfaites on dit que exp 1 et exp 2 sont compatibles pour laectation. Essentiellement : e e deux types numriques sont toujours compatibles pour laectation. La valeur de exp 2 subit ventuellement une conversion avant dtre range dans exp 1 . La mani`re de faire cette conversion est la mme que dans e e e e le cas de loprateur de conversion (cf. section 2.2.12) ; e a si exp 1 et exp 2 sont de types adresses distincts, certains compilateurs (dont ceux conformes ` la norme ANSI) les considreront comme incompatibles tandis que dautres compilateurs se limiteront ` donner un e a message davertissement lors de laectation de exp 2 ` exp 1 ; a e dans les autres cas, exp 1 et exp 2 sont compatibles pour laectation si et seulement si elles sont de mme 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 dduit que : e on ne peut aecter un tableau ` un autre, mme sils sont dnis de mani`re rigoureusement identique a e e e (un nom de tableau nest pas une lvalue) ; a a on peut aecter le contenu dune variable de type structure ou union ` une autre, ` la condition quelles aient t dclares comme ayant exactement le mme type. ee e e e
c H. Garreta, 2003

29

2.2 Prsentation dtaille des oprateurs e e e e

OPERATEURS ET EXPRESSIONS

2.2.20

Autres oprateurs daectation += *= etc. e

Opration binaire vue comme une modication du premier oprande. Format : e e += -= *= /= %= exp2 exp1 >>= <<= &= ^= |= exp 1 doit tre une lvalue. Cela fonctionne de la mani`re suivante : si reprsente lun des oprateurs + - * / % e e e e >> << & ^ |, alors exp 1 = exp 2 peut tre vue comme ayant la mme valeur et le mme eet que e e e exp 1 = exp 1 exp 2 mais avec une seule valuation de exp 1 . Lexpression rsultante nest pas une lvalue. e e Exemple. Ecrivons la version itrative usuelle de la fonction qui calcule xn avec x ottant et n entier non e ngatif : e 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 mmes commentaires qu` loccasion de plusieurs e a autres lments du langage C : ee lemploi de ces oprateurs constitue une certaine optimisation du programme. En langage machine, la e suite dinstructions qui traduit textuellement a += c est plus courte que celle qui correspond ` a = b a + c. Or, un compilateur rustique traitera a = a + c comme un cas particulier de a = b + c, sans voir lquivalence avec la premi`re forme. e e hlas, lemploi de ces oprateurs rend les programmes de plus en plus denses et de moins en moins faciles e e a ` lire, ce qui favorise lapparition derreurs de programmation. de nos jours les compilateurs de C sont devenus assez perfectionns pour dceler automatiquement la pose e sibilit de telles optimisations. Par consquent, largument de lecacit ne justie plus quon obscurcisse e e e un programme par lemploi de tels oprateurs. e Il faut savoir, dautre part, quil existe des situations o` lemploi de ces oprateurs nest pas quune question u e decacit. En eet, si exp 1 est une expression sans eet de bord, alors les expressions exp 1 = exp 2 et exp 1 e = exp 1 exp 2 sont rellement quivalentes. Mais ce nest plus le cas si exp 1 a un eet de bord. Il est clair, par e e exemple, que les deux expressions suivantes ne sont pas quivalentes (la premi`re est tout simplement errone e e e errone)17 : e nombre[rand() % 100] = nombre[rand() % 100] + 1; // ERREUR ! et nombre[rand() % 100] += 1; 2.2.21 Loprateur virgule , e

Opration : valuation en squence. Format : e e e


17 La

fonction rand() renvoie un entier alatoire distinct chaque fois quelle est appele. e e

30

c H. Garreta, 2003

2 OPERATEURS ET EXPRESSIONS

2.3

Autres remarques

exp 1 , exp 2 exp 1 et exp 2 sont quelconques. Lvaluation de exp 1 , exp 2 consiste en lvaluation de exp 1 suivie de lvaluation e e e de exp 2 . Lexpression exp 1 , exp 2 poss`de le type et la valeur de exp 2 ; le rsultat de lvaluation de exp 1 est e e e oubli, mais non son ventuel eet de bord (cet oprateur nest utile que si exp 1 a un eet de bord). e e e Lexpression exp 1 , exp 2 nest pas une lvalue. Exemple. Un cas frquent dutilisation de cet oprateur concerne la boucle for (cf. section 3.2.6), dont la e e syntaxe requiert exactement trois expressions ` trois endroits bien prcis. Parfois, ces expressions doivent tre a e e doubles : e ... for (pr = NULL, p = liste; p != NULL; pr = p, p = p->suivant) if (p->valeur == x) break; ... Remarque. Dans des contextes o` des virgules apparaissent normalement, par exemple lors dun appel u de fonction, des parenth`ses sont requises an de forcer le compilateur ` reconna e a tre loprateur virgule. Par e exemple, lexpression une_fonction(exp 1 , (exp 2 , exp 3 , exp 4 ), exp 5 ); reprsente un appel de une fonction avec trois arguments eectifs : les valeurs de exp 1 , exp 4 et exp 5 . Avant e lvaluation de exp 4 , les expressions exp 2 puis exp 3 auront t values. e eee e

2.3
2.3.1

Autres remarques
Les conversions usuelles

Les r`gles suivantes sappliquent aux expressions construites ` laide dun des oprateurs *, /, %, +, -, <, <=, e a e >, >=, ==, !=, &, ^, |, && et ||. Mutatis mutandis, elles sappliquent aussi ` celles construites avec les oprateurs a e *=, /=, %=, +=, -=, <<=, >>=, &=, ^= et |=. Dans ces expressions, les oprandes subissent certaines conversions avant que lexpression ne soit value. e e e En C ANSI ces conversions, dans l ordre logique o` elles sont faites, sont les suivantes : u Si un des oprandes est de type long double, convertir lautre dans le type long double ; le type de e lexpression sera long double. Sinon, si un des oprandes est de type double, convertir lautre dans le type double ; le type de lexpression e sera double. Sinon, si un des oprandes est de type float, convertir lautre dans le type float ; le type de lexpression e sera float18 . Eectuer la promotion enti`re : convertir les char, les short, les numrations et les champs de bits en e e e des int. Si lune des valeurs ne peut pas tre reprsente dans le type int, les convertir toutes dans le e e e type unsigned int. e Ensuite, si un des oprandes est unsigned long, convertir lautre en unsigned long ; le type de lexpression sera unsigned long. e Sinon, si un des oprandes est long et lautre unsigned int19 : e e si un long peut reprsenter toutes les valeurs unsigned int, alors convertir loprande de type unsigned int en long. Le type de lexpression sera long ; sinon, convertir les deux oprandes en unsigned long. Le type de lexpression sera unsigned long. e e Sinon, si un des oprandes est de type long, convertir lautre en long ; le type de lexpression sera long. Sinon, si un des oprandes est de type unsigned int, convertir lautre en unsigned int ; le type de lexpression e sera unsigned int. Sinon, et si lexpression est correcte, cest que les deux oprandes sont de type int ; le type de lexpression e sera int.
18 Cette r`gle est appaue avec le C ANSI : le compilateur accepte de faire des calculs sur des ottants en simple prcision. Dans e e le C original, elle doit tre remplace par la r`gle suivante :  sinon, si lun des oprandes est de type float, convertir les deux e e e e oprandes dans le type double ; le type de lexpression sera double . e 19 Cette r`gle complique est apparue avec le C ANSI. En C original, le type unsigned  tire vers lui  les autres types. e e

c H. Garreta, 2003

31

2.3 Autres remarques

OPERATEURS ET EXPRESSIONS

2.3.2

Lordre dvaluation des expressions e

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

Beaucoup doprateurs tudis dans cette section (oprateurs arithmtiques, comparaisons, logiques, etc.) e e e e e reprsentent des oprations abstraites, cest-`-dire possdant une dnition formelle qui ne fait pas intervenir e e a e e les particularits de limplantation du langage. Bien sr, les oprandes sont reprsents dans la machine par des e u e e e congurations de bits, mais seule leur interprtation comme des entits de niveau suprieur (nombres entiers, e e e ottants...) est utile pour dnir leet de lopration en question ou les contraintes quelle subit. e e A loppos, un petit nombre doprateurs, le complment ` un (~), les dcalages (<< et >>) et les oprations e e e a e e bit-`-bit (&, ^ et |), nont pas de signication abstraite. Les transformations quils eectuent sur les suites de bits a qui sont les codages internes de leurs oprandes nont pas dinterprtation formelle ` un niveau suprieur. De e e a e telles oprations sont rserves aux programmes qui remplissent des fonctions de tr`s bas niveau, cest-`-dire qui e e e e a sont aux points de contact entre la composante logicielle et la composante matrielle dun syst`me informatique. e e Laspect de cette question qui nous intresse le plus ici est celui-ci : la portabilit des programmes contenant e e des oprations non abstraites nest pas assure. Ce dfaut, qui nest pas rdhibitoire dans lcriture de fonctions e e e e e de bas niveau (ces fonctions ne sont pas destines ` tre portes), doit rendre le programmeur tr`s prcautionneux e ae e e e d`s quil sagit dutiliser ces oprateurs dans des programmes de niveau suprieur, et le pousser ` : e e e a isoler les oprations non abstraites dans des fonctions de petite taille bien repres ; e ee documenter soigneusement ces fonctions ; constituer des jeux de tests validant chacune de ces fonctions.

32

c H. Garreta, 2003

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  lment ... lment , indique un lment pouvant ee ee ee appara un nombre quelconque, ventuellement nul, de fois. tre e 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 { dclaration ... dclaration e e 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
c H. Garreta, 2003

33

3.2 Prsentation dtaille des instructions e e e

INSTRUCTIONS

un sparateur dinstructions mais un terminateur de certaines instructions. Autrement dit, il appartient ` la e a syntaxe de chaque instruction de prciser si elle doit ou non tre termine par un point-virgule, indpendamment e e e e de ce par quoi linstruction est suivie dans le programme. Loubli du point-virgule ` la n dune instruction qui en requiert un est toujours une erreur, quelle que soit a la situation de cette instruction. Un surnombre de points-virgules cre des instructions vides. e

3.2
3.2.1

Prsentation dtaille des instructions e e e


Blocs

Un bloc est une suite de dclarations et dinstructions encadre par les deux accolades { et }. Du point de e e vue de la syntaxe il se comporte comme une instruction unique et peut gurer en tout endroit o` une instruction u simple est permise. Le bloc le plus extrieur dune fonction et les autres blocs plus profonds ont le mme statut. En particulier, e e quelle que soit sa position dans le programme, un bloc peut comporter ses propres dclarations de variables. Sauf e si elle est dclare extern, une variable dnie dans un bloc est locale ` ce bloc et donc inconnue ` lextrieur. e e e a a e Dans le bloc o` elle est dnie, une telle variable masque, sans le dtruire, tout autre objet de mme nom connu u e e e a ` lextrieur du bloc. e Sauf si elles sont qualies static (ou extern), de telles variables sont cres et ventuellement initialises e ee e e lors de lactivation du bloc ; elles sont dtruites d`s que le contrle quitte le bloc. Il ne faut donc pas esprer e e o e 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 simultanment ; le compilateur peut donc leur allouer le mme emplacee e ment de la mmoire. e

3.2.2

Instruction-expression

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

Intuitivement, une instruction-expression reprsente lordre  valuez cette expression, ensuite oubliez le e e rsultat . Il est clair que si lexpression na pas deet de bord, cela naura servi ` rien (exemple a). Laspect e a utile de cette notion est : toute expression avec eet de bord pourra tre value uniquement pour son eet de e e e bord (exemple b). Avec deux cas particuliers tr`s intressants : e e puisque laectation est une expression, on retrouve bien linstruction daectation, fondamentale dans tous les langages de programmation (exemple c) ; toute fonction peut tre appele  comme une procdure  (exemple20 d ), cest-`-dire en ignorant la valeur e e e a quelle rend.
20 On

verra le moment venu (cf. section 7.2.5 d) que printf rend un rsultat parfois utile. e

34

c H. Garreta, 2003

3 INSTRUCTIONS

3.2

Prsentation dtaille des instructions e e e

Remarque. Une consquence regrettable de tout cela est un pi`ge assez vicieux tendu aux pascaliens. e e Imaginons que lirecaractere soit le nom dune fonction sans argument. Lappel correct de cette fonction scrit : e 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, lnonc e e lirecaractere; sera trouv lgal par le compilateur. Or cette expression (tout ` fait analogue ` lexemple a ci-dessus) ne produit e e a a pas lappel de la fonction et ne traduit donc probablement pas la pense du programmeur. e 3.2.3 Etiquettes et instruction goto

Format : tiquette : instruction e Une tiquette est un identicateur ; elle doit tre place devant une fonction, spare de celle-ci par un e e e e e caract`re deux points. Elle na pas ` faire lobjet dune dclaration explicite ; il sut de lcrire devant une e a e e instruction pour quelle soit automatiquement connue comme un nom ` porte locale. Elle est alors utilisable a e partout dans la fonction o` elle appara (avant et apr`s linstruction quelle prxe) et elle reste inconnue en u t e e dehors de la fonction. Linstruction goto tiquette ; e transf`re le contrle ` linstruction prxe par ltiquette en question. e o a e e e Thoriquement, tout algorithme peut tre programm sans utiliser linstruction goto. Dans certains langages e e e comme Pascal, elle est utilise pour obtenir labandon dune structure de contrle (exemple : une boucle) depuis e o lintrieur de la structure. Un tel emploi de goto est avantageusement remplac en C par lutilisation des e e instructions return, break et continue. Il est donc rare que lon ait besoin de linstruction goto en C. Elle ne se rv`le utile que lorsquil faut e e abandonner plusieurs structures de contrle (if, while, for...) imbriques les unes dans les autres. Exemple : o e 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 les deux boucles internes (sur j et k) e ... // 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`re forme, expression est value : si elle est vraie (i.e. non nulle) instruction 1 est excute ; e e e e e si elle est fausse (nulle) instruction 2 est excute. Dans la deuxi`me forme, expression est value : si elle est e e e e e vraie instruction 1 est excute ; sinon, rien nest excut. e e e e
c H. Garreta, 2003

35

3.2 Prsentation dtaille des instructions e e e

INSTRUCTIONS

On notera que lexpression conditionnelle doit gurer entre parenth`ses. Celles-ci font partie de la syntaxe e du if, non de celle de lexpression. Lorsque plusieurs instructions if sont imbriques, il est convenu que chaque else se rapporte au dernier e if pour lequel le compilateur a rencontr une condition suivie dexactement une instruction. Le listing du e 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`me se pose dans tous les langages qui orent deux varits dinstruction conditionnelle. On le rsout e ee e 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 prvoit exactement une instruction entre la condition et le else. Par consquent, un e e exc`s de points-virgules ` la suite de instruction 1 constitue une erreur. Voici une faute quon peut faire quand e a on dbute : e 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`ses. Formats : e while (expression) instruction et do instruction while (expression); Le fonctionnement de ces instructions est dcrit par les organigrammes de la gure 4. Fondamentalement, e il sagit de ritrer lexcution dune certaine instruction tant quune certaine instruction, vue comme une e e e condition, reste vraie. Dans la structure while on vrie la condition avant dexcuter linstruction, tandis que e e dans la structure do...while on la vrie apr`s. e e 36
c H. Garreta, 2003

3 INSTRUCTIONS

3.2

Prsentation dtaille des instructions e e e

expression != 0
oui non oui

instruction

instruction

expression != 0
non

suite du programme

suite du programme

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

Linstruction do...while... excute donc au moins une fois linstruction qui constitue son corps avant e dvaluer la condition de continuation. Cest en cela quelle est lanalogue de linstruction repeat...until du e 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 quivalente ( rpter jusqu` ce que... ) sont inverses lune e e e a de lautre. 3.2.6 Instruction for

Format : for ( expr 1 ; expr 2 ; expr 3 ) instruction Par dnition, cette construction quivaut strictement ` celle-ci : e e a 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 ncessaires avant lentre dans la boucle ; e e e e e expr 2 est le test de continuation de la boucle ; il est valu avant lexcution du corps de la boucle ; expr 3 est une expression (par exemple une incrmentation) value ` la n du corps de la boucle. e e e a 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 tre absentes (les points-virgules doivent cependant appara e tre). Par exemple for ( ; expr 2 ; ) instruction quivaut ` e a while (expr 2 ) instruction La condition de continuation expr 2 peut elle aussi tre absente. On consid`re alors quelle est toujours vraie. e e Ainsi, la boucle  indnie  peut se programmer : e for ( ; ; ) instruction Bien entendu, il faut dans ce cas que labandon de la boucle soit programm ` lintrieur de son corps (sinon ea e cette boucle indnie devient innie !). Exemple : e
c H. Garreta, 2003

37

3.2 Prsentation dtaille des instructions e e e

INSTRUCTIONS

for (;;) { printf("donne un nombre (0 pour sortir): "); scanf("%d", &n); if (n == 0) break; ... exploitation de la donne n e ... } Lorsque labandon de la boucle correspond aussi ` labandon de la procdure courante, break est avantaa e geusement remplace par return comme dans : e char *adresse_de_fin(char *ch) { /* renvoie adresse du caract`re qui suit la cha^ne ch */ e 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 value ; e e sil existe un nonc case avec une constante qui gale la valeur de expression, le contrle est transfr ` e e e o eea linstruction qui suit cet nonc ; e e si un tel case nexiste pas, et si lnonc default existe, alors le contrle est transfr ` linstruction qui e e o eea suit lnonc default ; e e si la valeur de expression ne correspond ` aucun case et sil ny a pas dnonc default, alors aucune a e e instruction nest excute. e e Attention. Lorsquil y a branchement russi ` un nonc case, toutes les instructions qui le suivent sont e a e e excutes, jusqu` la n du bloc ou jusqu` une instruction de rupture (break). Autrement dit, linstruction e e a a switch sapparente beaucoup plus ` une sorte de goto  paramtr  (par la valeur de lexpression) qu` a e e 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 mme eet que e laectation j = i. Pour obtenir un comportement similaire ` celui du case...of... de Pascal, on doit utiliser linstruction a break, comme dans lexemple suivant, dans lequel on compte la frquence de chaque chire et des caract`res e e blancs dans un texte : 38
c H. Garreta, 2003

3 INSTRUCTIONS

3.2

Prsentation dtaille des instructions e e e

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 porte dune structure de contrle (instructions for, while, do et switch), linstruction break e o provoque labandon de la structure et le passage ` linstruction crite immdiatement derri`re. Lutilisation de a e e e break ailleurs qu` lintrieur dune de ces quatre instructions constitue une erreur (que le compilateur signale). a e Par exemple la construction for (expr 1 ; expr 2 ; expr 3 ) { ... break; ... } quivaut ` e a { for (expr 1 ; expr 2 ; expr 3 ) { ... goto sortie; ... } sortie: ; } Linstruction continue est moins souvent utilise. Dans la porte dune structure de contrle de type boucle e e o (while, do ou for), elle produit labandon de litration courante et, le cas chant, le dmarrage de litration e e e e e suivante. Elle agit comme si les instructions qui se trouvent entre linstruction continue et la n du corps de la boucle avaient t supprimes pour litration en cours : lexcution continue par le test de la boucle (prcd, ee e e e e e e dans le cas de for, par lexcution de lexpression dincrmentation). e e Lorsque plusieurs boucles sont imbriques, cest la plus profonde qui est concerne par les instructions break e e et continue. Dans ce cas, lemploi de ces instructions ne nous semble pas uvrer pour la clart des programmes. e
c H. Garreta, 2003

39

3.2 Prsentation dtaille des instructions e e e

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`re forme expression est value ; son rsultat est la valeur que la fonction renvoie ` la fonction e e e e a appelante ; cest donc la valeur que lappel de la fonction reprsente dans lexpression o` il gure. Si ncessaire, e u e la valeur de expression est convertie dans le type de la fonction (dclar dans len-tte), les conversions autorises e e e e tant les mmes que celles faites ` loccasion dune aectation. e e a Dans la deuxi`me forme, la valeur retourne par la fonction reste indtermine. On suppose dans ce cas que e e e e la fonction appelante nutilise pas cette valeur ; il est donc prudent de dclarer void cette fonction. e Absence dinstruction return dans une fonction. Lorsque la derni`re instruction (dans lordre de e lexcution) dune fonction est termine, le contrle est rendu galement ` la procdure appelante. Tout se passe e e o e a e comme si laccolade fermante qui termine le corps de la fonction tait en ralit crite sous la forme e e ee ... return; }

40

c H. Garreta, 2003

4 FONCTIONS

Fonctions

Beaucoup de langages distinguent deux sortes de sous-programmes21 : les fonctions et les procdures. Lappel e dune fonction est une expression, tandis que lappel dune procdure est une instruction. Ou, si on prf`re, e ee lappel dune fonction renvoie un rsultat, alors que lappel dune procdure ne renvoie rien. e e En C on retrouve ces deux mani`res dappeler les sous-programmes, mais du point de la syntaxe le langage e ne conna que les fonctions. Autrement dit, un sous-programme est toujours suppos renvoyer une valeur, mme t e e lorsque celle-ci na pas de sens ou na pas t spcie. Cest pourquoi on ne parlera ici que de fonctions. ee e e Cest dans la syntaxe et la smantique de la dnition et de lappel des fonctions que rsident les principales e e e dirences entre le C original et le C ANSI. Nous expliquons principalement le C ANSI, rassemblant dans une e section spcique (cf. section 4.2) la mani`re de faire du C original. e e

4.1
4.1.1

Syntaxe ANSI ou avec prototype


Dnition e

Une fonction se dnit par la construction : e type opt ident ( dclaration-un-ident , ... dclaration-un-ident ) e e instruction-bloc Notez quil ny a pas de point-virgule derri`re le ) de la premi`re ligne. La prsence dun point-virgule e e e a ` cet endroit provoquerait des erreurs bien bizarres, car la dnition serait prise pour une dclaration (cf. e e section 4.1.4). La syntaxe indique ici est incompl`te ; elle ne convient quaux fonctions dont le type est dni simplement, e e e par un identicateur. On verra ultrieurement (cf. section 5.4.1) la mani`re de dclarer des fonctions rendant e e e un rsultat dun type plus complexe. e La premi`re ligne de la dnition dune fonction sappelle len-tte, ou parfois le prototype, de la fonction. e e e Chaque formule dclaration-un-ident poss`de la mme syntaxe quune dclaration de variable22 . e e e e Exemple. int extract(char *dest, char *srce, int combien) { /* copie dans dest les combien premiers caract`res de srce */ e /* renvoie le nombre de caract`res effectivement copis e e */ int compteur; for (compteur = 0; compteur < combien && *srce != \0; compteur++) *dest++ = *srce++; *dest = \0; return compteur; } Contrairement ` dautres langages, en C on ne peut pas dnir une fonction ` lintrieur dune autre : toutes a e a e les fonctions sont au mme niveau, cest-`-dire globales. Cest le cas notamment de main, qui est une fonction e a comme les autres ayant pour seule particularit un nom convenu. e 4.1.2 Type de la fonction et des arguments

Len-tte de la fonction dnit le type des objets quelle renvoie. Ce peut tre : e e e e tout type numrique ; tout type pointeur ; tout type struct ou union. Si le type de la fonction nest pas indiqu, le compilateur suppose quil sagit dune fonction ` rsultat e a e entier. Ainsi, lexemple prcdent aurait aussi pu tre crit de mani`re quivalente (mais cette pratique nest e e e e e e pas conseille) : e
21 La notion de sous-programme (comme les procedures de Pascal, les subroutines de Fortran, etc.) est suppose ici connue du e lecteur. 22 Restriction : on na pas le droit de  mettre en facteur  un type commun ` plusieurs arguments. Ainsi, len-tte de la fonction a e extract donne en exemple ne peut pas scrire sous la forme char *extract(char *dest, *srce, int n). e e

c H. Garreta, 2003

41

4.1 Syntaxe ANSI ou avec prototype

FONCTIONS

extract(char *dest, char *srce, int combien) etc. Fonctions sans resultat. Lorsquune fonction ne renvoie pas une valeur, cest-`-dire lorsquelle corresa pond plus ` une procdure qu` une vraie fonction, il est prudent de la dclarer comme rendant un objet de type a e a e voidvoid. Ce type est garanti incompatible avec tous les autres types : une tentative dutilisation du rsultat e de la fonction provoquera donc une erreur ` la compilation. Le programmeur se trouve ainsi ` labri dune a a utilisation intempestive du rsultat de la fonction. Exemple : e void extract(char *dest, char *srce, int combien) { /* copie dans dest les combien premiers caract`res de srce */ e /* 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 dnition prend la forme e type opt ident ( void ) instruction-bloc On notera que, sauf cas exceptionnel, on ne doit pas crire une paire de parenth`ses vide dans la dclaration ou e e e la dnition dune fonction. En eet, un en-tte de la forme e e 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 contrls par le compilateur. oe 4.1.3 Appel des fonctions

Lappel dune fonction se fait en crivant son nom, suivi dune paire de parenth`ses contenant ventuellement e e e une liste darguments eectifs. Notez bien que les parenth`ses doivent toujours appara e tre, mme si la liste darguments est vide : si un e nom de fonction appara dans une expression sans les parenth`ses, alors il a la valeur dune constante adresse t e (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 formels23 de la fonction reprsentent dauthentiques variables locales initialises, lors de lappel de la fonction, e e par les valeurs des arguments eectifs24 correspondants. Supposons quune fonction ait t dclare ainsi ee e e 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 excutait les aectations : e arg formel 1 = arg eectif 1 ... arg formel k = arg eectif k Cela a des consquences tr`s importantes : e e e e e les erreurs dans le nombre des arguments eectifs sont dtectes et signales, si le type dun argument eectif nest pas compatible avec celui de largument formel correspondant, une erreur est signale, e e e e les valeurs des arguments eectifs subissent les conversions ncessaires avant dtre ranges dans les arguments formels correspondants (exactement les mmes conversions qui sont faites lors des aectations). e
23 Les arguments formels dune fonction sont les identicateurs qui apparaissent dans la dnition de la fonction, dclars ` e e e a lintrieur de la paire de parenth`ses. e e 24 Les arguments eectifs dun appel de fonction sont les expressions qui apparaissent dans lexpression dappel, crites ` lintrieur e a e de la paire de parenth`ses caractristiques de lappel. e e

42

c H. Garreta, 2003

4 FONCTIONS

4.2

Syntaxe originale ou sans prototype

Remarque. Le langage ne spcie pas lordre chronologique des valuations des arguments eectifs dun e e appel de fonction. Ces derniers doivent donc tre sans eets de bord les uns sur les autres. Par exemple, il est e impossible de prvoir quelles sont les valeurs eectivement passes ` une fonction lors de lappel : e e a x = une_fonction(t[i++], t[i++]); // ERREUR !!! Si i0 est la valeur de i juste avant lexcution de linstruction ci-dessus, alors cet appel peut aussi bien e se traduire par une fonction(t[i0 ], t[i0 + 1]) que par une fonction(t[i0 + 1], t[i0 ]) ou mme par e une fonction(t[i0 ], t[i0 ]). Ce nest srement pas indirent ! u e Appel dune fonction inconnue. En C une fonction peut tre appele alors quelle na pas t dnie e e ee e (sous-entendu : entre le dbut du chier et lendroit o` lappel gure). Le compilateur suppose alors e u 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, empche les contrles et conversions mentionns plus haut). e o e De plus, ces hypoth`ses sur la fonction constituent pour le compilateur une premi`re dclaration de la e e e fonction. Toute dnition ou dclaration ultrieure tant soit peu dirente sera qualie de  redclaration e e e e e e illgale 25 . e Lorsque les hypoth`ses ci-dessus ne sont pas justes, en particulier lorsque la fonction ne renvoie pas un int, e il faut : soit crire la dnition de la fonction appele avant celle de la fonction appelante, e e e soit crire, avant lappel de la fonction, une dclaration externe de cette derni`re, comme expliqu ` la e e e ea section 4.1.4. 4.1.4 Dclaration externe dune fonction e

Une dclaration externe dune fonction est une dclaration qui nest pas en mme temps une dnition. On e e e e  annonce  lexistence de la fonction (dnie plus loin, ou dans un autre chier) tout en prcisant le type de son e e rsultat et le nombre et les types de ses arguments, mais on ne donne pas son corps, cest-`-dire les instructions e a qui la composent. Cela se fait en crivant e soit un en-tte identique ` celui qui gure dans la dnition de la fonction (avec les noms des arguments e a e formels), suivi dun point-virgule ; soit la formule obtenue ` partir de len-tte prcdent, en y supprimant les noms des arguments formels. a e e e Par exemple, si une fonction a t dnie ainsi ee e void truc(char dest[80], char *srce, unsigned long n, float x) { corps de la fonction } alors des dclarations externes correctes sont : e 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 appeles des prototypes de la fonction. Le mot extern est facultatif, mais le pointe virgule est essentiel. Dans la premi`re forme, les noms des arguments formels sont des identicateurs sans e aucune porte. e

4.2
4.2.1

Syntaxe originale ou sans prototype


Dclaration et dnition e e

Definition. En syntaxe originale la dnition dune fonction prend la forme e type opt ident ( ident , ... ident ) dclaration ... dclaration e e instruction-bloc
25 Cest une erreur surprenante, car le programmeur, oubliant que lappel de la fonction a entra e une dclaration implicite, n e conoit sa dnition ou dclaration ultrieure comme tant la premi`re dclaration de la fonction. c e e e e e e

c H. Garreta, 2003

43

4.2 Syntaxe originale ou sans prototype

FONCTIONS

Les parenth`ses de len-tte ne contiennent ici que la liste des noms des arguments formels. Les dclarations e e e de ces arguments se trouvent immdiatement apr`s len-tte, avant laccolade qui commence le corps de la e e e fonction. Exemple : int extract(dest, srce, combien) /* copie dans dest les combien premiers caract`res de srce */ e /* renvoie le nombre de caract`res effectivement copis e e */ char *dest, char *srce; int combien; { int compteur; for (compteur = 0; compteur < combien && *srce != \0; compteur++) *dest++ = *srce++; *dest = \0; return compteur; } Declaration externe. En syntaxe originale, la dclaration externe de la fonction prcdente prend une e e e des formes suivantes : extern int extract(); ou int extract(); Comme on le voit, ni les types, ni mme les noms, des arguments formels napparaissent dans une dclaration e e externe26 . 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 27 , indiqus lors de la dnition de la fonction. Chaque argument est valu e e e e sparment, puis e e 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 laisses telles quelles. e Juste avant le transfert du contrle ` la fonction, ces valeurs sont copies dans les arguments formels coro a e respondants. Ou plus exactement, dans ce que la fonction appelante croit tre les emplacements des arguments e formels de la fonction appele : il ny a aucune vrication de la concordance, ni en nombre ni en type, des e e arguments formels et des arguments eectifs. Cela est une source derreurs tr`s importante. Par exemple, la e fonction sqr ayant t ainsi dnie ee e double sqr(x) double x; { return x * x; } lappel x = sqr(2); est erron, car la fonction appelante dpose la valeur enti`re 2 ` ladresse du premier argument formel (quelle e e e a  croit  entier). Or la fonction appele croit recevoir le codage dun nombre ottant double. Le rsultat na e e aucun sens. Des appels corrects auraient t ee x = sqr((double) 2); ou x = sqr(2.0);
26 Par consquent, les dclarations externes montres ici sont sans aucune utilit, puisque int est le type suppos des fonctions e e e e e non dclares. e e 27 Mais oui, vous avez bien lu ! Cest l` la principale caractristique de la smantique dappel des fonctions du C original. a e e

44

c H. Garreta, 2003

4 FONCTIONS

4.3

Arguments des fonctions

4.2.3

Coexistence des deux syntaxes

A ct de ce que nous avons appel la syntaxe ANSI, la syntaxe originale pour la dnition et la dclaration oe e e e 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 t dnie ou dclare sous la syntaxe originale, les appels de ee e e e fonction sont faits sans vrication ni conversion des arguments eectifs. Au contraire, lorsque la fonction a t e ee spcie sous la syntaxe ANSI, ces contrles et conversions sont eectus. e e o e A cause de cette coexistence, si une fonction na pas dargument, la dnition de son en-tte en C ANSI doit e e scrire sous la forme bien peu heureuse : e type opt ident ( void ) car une paire de parenth`ses sans rien dedans signierait non pas que la fonction na pas darguments, mais e quelle est dclare sans prototype, cest-`-dire que ses appels doivent tre traits sans contrle des arguments. e e a e e o Exemples : int getchar(void); double moyenne(); // une fonction sans arguments dont les appels seront contrls oe // une fonction avec ou sans arguments, aux appels incontrls oe

4.3
4.3.1

Arguments des fonctions


Passage des arguments

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

Apparaissant dans la partie excutable dun programme, le nom dun tableau, et plus gnralement toute e e e expression dclare  tableau de T , est considre comme ayant pour type  adresse dun T  et pour valeur e e ee ladresse du premier lment du tableau. Cela ne fait pas intervenir la taille eective du tableau28 . Ainsi lorsquun ee argument eectif est un tableau, lobjet eectivement pass ` la fonction est uniquement ladresse du premier ea lment et il sut que lespace rserv dans la fonction pour un tel argument ait la taille, xe, dun pointeur. ee e e Dautre part, en C il nest jamais vri que les indices des tableaux appartiennent bien ` lintervalle 0...N 1 e e a dtermin par le nombre dlments indiqu dans la dclaration. Il en dcoule la proprit suivante : e e ee e e e ee Dans la dclaration dun argument formel t de type  tableau de T  : e lindication du nombre dlments de t est sans utilit29 ; ee e les formules  type t[ ]  et  type *t  sont tout ` fait quivalentes ; a e la fonction pourra tre appele indiremment avec un tableau ou un pointeur pour argument eectif. e e e Exemple. Len-tte  vague  de la fonction suivante e int strlen(cha de caract`res s) { ne e int i = 0; while (s[i] != 0) i++; return i; } peut indiremment tre concrtise de lune des trois mani`res suivantes : e e e e e int strlen(char s[80]) int strlen(char s[]) int strlen(char *s)
28 Il nen est pas de mme lors de la dnition, o` la taille du tableau ne peut pas tre ignore (elle est indispensable pour e e u e e lallocation de lespace mmoire). e 29 Attention, cette question se complique notablement dans le cas des tableaux multidimensionnels. Cf. 6.2.3

c H. Garreta, 2003

45

4.3 Arguments des fonctions

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 passs par valeur, les tableaux sont passs par adresse. e e Passage par valeur des tableaux. Cependant, puisque les structures sont passes par valeur, elles e fournissent un moyen pour obtenir le passage par valeur dun tableau, bien que les occasions o` cela est ncessaire u e semblent assez rares. Il sut de dclarer le tableau comme le champ unique dune structure  enveloppe  ne e servant qu` cela. Exemple : a 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 ache est 1, ce qui prouve que la fonction appele na modi quune copie locale du tableau e e e envelopp, cest-`-dire que celui-ci a bien t pass par valeur (les structures seront vues ` la section 5.2.1). e a ee e a

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 passs par valeur. Lorsque largument eectif est une variable, ce mcanisme empche quelle puisse e e e tre modie par la fonction appele. Or une telle modication est parfois souhaitable ; elle requiert que la e e e fonction puisse accder non seulement ` la valeur de la variable, mais aussi ` son adresse. e a a Cest ce qui sappelle le passage par adresse des arguments. Alors que certains langages le prennent en charge, C ne fournit aucun mcanisme spcique : le passage de ladresse de largument doit tre programm e e e e explicitement en utilisant comme argument un pointeur vers la variable. Par exemple, supposons avoir ` crire une fonction ae int quotient(a, b) cense renvoyer le quotient de la division euclidienne de lentier a par lentier b et devant en plus remplacer a e par le reste de cette division. On devra la dnir : e int quotient(int *a, int b) { int r = *a / b; *a = *a % b; return r; } Ci-dessus, b reprsente un entier ; a ladresse dun entier. La notation *a fait rfrence ` une variable (ou, e ee a plus gnralement, une lvalue) appartenant ` la procdure appelante. e e a e Largument eectif correspondant ` un tel argument formel doit tre une adresse. Souvent, cela est obtenu a e par lutilisation de loprateur &. Par exemple, si x, y et z sont des variables enti`res, un appel correct de la e e fonction prcdente sera e e z = quotient(&x, y); Bien entendu, loprateur & ne doit pas tre utilis si largument eectif est dj` une adresse. Le schma e e e ea e suivant rsume les principales situations possibles. Soient les fonctions e 46
c H. Garreta, 2003

4 FONCTIONS

4.3

Arguments des fonctions

f(int a) { ... } g(int *b) { ... } h(int c, int *d) { ... } A lintrieur de la fonction h, des appels corrects de f et g sont : e f(c); f(*d); g(&c); g(d); cette derni`re expression correspondant, bien sr, ` g(&*d). e u a 4.3.4 Arguments en nombre variable

Le langage C permet de dnir des fonctions dont le nombre darguments nest pas x et peut varier dun e e appel ` un autre. Nous exposons ici la mani`re dont cette facilit est ralise dans le C ANSI. Dans le C a e e e e original elle existe aussi ; elle consiste ` utiliser, sans garde-fou, une faiblesse du langage (la non-vrication du a e nombre darguments eectifs) et une caractristique du syst`me sous-jacent (le sens dempilement des arguments e e eectifs). Mais tout cela est susamment casse-cou pour que nous prfrions nexpliquer que la mani`re ANSI ee e de faire, un peu plus able. Une fonction avec un nombre variable darguments est dclare en explicitant quelques arguments xes, au e e moins un, suivis dune virgule et de trois points :  , ...  . Exemple : int max(short n, int x0, ...) Une telle fonction peut tre appele avec un nombre quelconque darguments eectifs, mais il faut quil y en e e ait au moins autant quil y a darguments formels nomms, ici deux. Exemple dappel : e m = max(3, p, q, r); Pour les arguments nomms, laectation des valeurs des arguments eectifs aux arguments formels est faite e 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 lintrieur de la fonction on acc`de aux arguments anonymes au moyen des macros va start et va arg e e dnies dans le chier stdarg.h, comme le montre lexemple suivant : e #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 lments dnis dans le chier stdarg.h sont : ee e va list pointeur Dclaration de la variable pointeur, qui sera automatiquement gre par le dispositif dacc`s e ee e 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.
c H. Garreta, 2003

47

4.3 Arguments des fonctions

FONCTIONS

va start(pointeur , dernier argument) Initialisation de la variable pointeur ; dernier argument doit tre le dere nier des arguments explicitement nomms dans len-tte de la fonction. e e 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 dj` obtenu. Chaque fois, ea type doit dcrire le type de largument qui est en train dtre rfrenc. e e ee e Comme lexemple le montre, le principe des fonctions avec un nombre variable darguments est de dclarer e eectivement au moins un argument, dont la fonction peut dduire ladresse des autres. Il faut aussi choisir le e moyen dindiquer ` la fonction le nombre de valeurs rellement fournies. Dans lexemple ci-dessus ce nombre est a e pass comme premier argument ; une autre technique consiste ` utiliser une valeur  intruse  (ex : le pointeur e a NULL parmi des pointeurs valides). La fonction printf est un autre exemple de fonction avec un nombre variable darguments. Elle est dclare (dans le chier stdio.h) : e e 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 traites ` la section 6.3.1. e a

48

c H. Garreta, 2003

5 OBJETS STRUCTURES

5
5.1
5.1.1

Objets structurs e
Tableaux
Cas gnral e e

Dans le cas le plus simple la dclaration dun tableau se fait par une formule comme : e type-de-base nom [ expression opt ] ; Exemple : unsigned long table[10]; Lexpression optionnelle qui gure entre les crochets spcie le nombre dlments du tableau. Le premier e ee lment poss`de toujours lindice 0. Par consquent, un tableau t dclar de taille N poss`de les lments ee e e e e e ee t0 , t1 , ... tN 1 . Ainsi la dnition prcdente alloue un tableau de 10 lments, nomms respectivement table[0], e e e ee e table[1], ... table[9]. Lexpression optionnelle qui gure entre les crochets doit tre de type entier et constante au sens de la e section 1.3.4, cest-`-dire une expression dont tous les lments sont connus au moment de la compilation. De a ee cette mani`re le compilateur peut lvaluer et conna la quantit despace ncessaire pour loger le tableau. Elle e e tre e e est obligatoire lorsquil sagit dune dnition, car il y a alors allocation eective du tableau. Elle est facultative e dans le cas des dclarations qui ne sont pas des dnitions, cest-`-dire : e e a lors de la dclaration dun tableau qui est un argument formel dune fonction ; e lors de la dclaration dun tableau externe (dni dans un autre chier). e e Semantique du nom dun tableau. En gnral lorsque le nom dun tableau appara dans une expression e e t il y joue le mme rle quune constante de type adresse ayant pour valeur ladresse du premier lment du e o ee tableau. Autrement dit, si t est de type tableau, les deux expressions t sont quivalentes. e Les exceptions ` cette r`gle, cest-`-dire les expressions de type tableau qui ne sont pas quivalentes ` a e a e a ladresse du premier lment du tableau, sont ee loccurrence du nom du tableau dans sa propre dclaration ; e lapparition dun nom du tableau comme argument de sizeof. Ce sont les seules occasions o` le compilateur veut bien se souvenir quun nom de tableau reprsente aussi un u e espace mmoire possdant une certaine taille. e e 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 ` un autre. Si a et b sont deux tableaux, mme ayant des types identiques, a e laectation b = a sera comprise comme laectation dune adresse ` une autre, et a rejete, car b nest pas modiable. e Tableaux multidimensionnels. C ne prvoit que les tableaux ` un seul indice, mais les lments des e a ee tableaux peuvent ` leur tour tre des tableaux. La dclaration dun tableau avec un nombre quelconque dindices a e e suit la syntaxe : type nom [ expression opt ] [ expression opt ] ... [ expression opt ] qui est un cas particulier de formules plus gnrales expliques ` la section 5.4.1. Par exemple, la dclaration e e e a e double matrice[10][20]; introduit matrice comme tant le nom dun tableau de 10 lments30 qui, chacun ` son tour, est un tableau de e ee a 20 lments de type double. ee 5.1.2 Initialisation des tableaux &t[0]

Une variable de type tableau peut tre initialise lors de sa dclaration. Cela se fait par une expression ayant e e e la syntaxe : { expression , expression , ... expression }
30 Dans le cas dune matrice, la coutume est dappeler ces lments les lignes de la matrice. Cela permet de dire alors :  en C les ee matrices sont ranges par lignes . e

c H. Garreta, 2003

49

5.1 Tableaux

OBJETS STRUCTURES

Exemple : int t[5] = { 10, 20, 30, 40, 50 }; Les expressions indiques doivent tre des expressions constantes au sens de la section 1.3.4. Sil y a moins e e dexpressions que le tableau a dlments, les lments restants sont remplis de zros (cas des variables globales) ee ee e ou bien restent indtermins (cas des variables locales). Sil y a plus dexpressions dinitialisation que dlments e e ee dans le tableau, une erreur est signale. e Dautre part, la prsence dun initialisateur dispense dindiquer la taille du tableau. Il est convenu que ce e dernier doit avoir alors pour taille le nombre eectif de valeurs initiales. Par exemple, la dnition e int t[] = { 10, 20, 30, 40, 50 }; alloue un tableau ` 5 composantes garni avec les valeurs indiques. a e Lorsque le tableau comporte des sous-tableaux, cest-`-dire lorsque cest un tableau ` plusieurs indices, la a a liste des expressions dinitialisation peut comporter des sous-listes indiques ` leur tour avec des accolades, mais e a ce nest pas une obligation. Le cas chant, la r`gle de compltion par des zros sapplique aussi aux sous-listes. e e e e e Par exemple, les dclarations e 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

Fig. 5 Tableaux initialiss e Remarque. Rappelons que ces reprsentations rectangulaires sont tr`s conventionnelles. Dans la mmoire e e e de lordinateur ces deux tableaux sont plutt arrangs comme sur la gure . o e

t1 1 2 3 0 4 5 6 0 7 8 9 0 0 0 0 0 t2 1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 0
Fig. 6 Tableaux  ` plat  a Si la taille dune composante est un diviseur de la taille dun entier, les tableaux sont  tasss  : chaque e 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`res e

Les cha nes de caract`res sont reprsentes comme des tableaux de caract`res. Un caract`re nul suit le dernier e e e e e caract`re utile de la cha et en indique la n. Cette convention est suivie par le compilateur (qui range les e ne cha nes constantes en leur ajoutant un caract`re nul), ainsi que par les fonctions de la librairie standard qui e construisent des cha nes. Elle est suppose vrie par les fonctions de la librairie standard qui exploitent des e e e cha nes. Par consquent, avant de passer une cha ` une telle fonction, il faut sassurer quelle comporte bien e ne a un caract`re nul ` la n. e a 50
c H. Garreta, 2003

5 OBJETS STRUCTURES

5.2

Structures et unions

Donnons un exemple classique de traitement de cha nes : la fonction strlen (extraite de la biblioth`que e standard) qui calcule le nombre de caract`res utiles dune cha : e ne int strlen(char s[]) { register int i = 0; while (s[i++] != \0) ; return i - 1; } Une constante-cha de caract`res apparaissant dans une expression a le mme type quun tableau de ne e e caract`res cest-`-dire, pour lessentiel, le type  adresse dun caract`re . Par exemple, si p a t dclar e a e ee e e char *p; laectation suivante est tout ` fait lgitime : a e p = "Bonjour. Comment allez-vous?"; Elle aecte ` p ladresse de la zone de la mmoire o` le compilateur a rang le texte en question (ladresse du B a e u e de "Bonjour..."). Dans ce cas, bien que lexpression *p soit une lvalue (ce qui est une proprit syntaxique), ee elle ne doit pas tre considre comme telle car *p reprsente un objet mmoris dans la zone des constantes, e ee e e e et toute occurrence de *p comme oprande gauche dune aectation : e *p = expression; constituerait une tentative de modication dun objet constant. Cette erreur est impossible ` dceler ` la a e a compilation. Selon le syst`me sous-jacent, soit une erreur sera signale ` lexcution, provoquant labandon e e a e immdiat du programme, soit aucune erreur ne sera signale mais la validit de la suite du traitement sera e e e compromise. Initialisation. Comme tous les tableaux, les variables-cha nes de caract`res peuvent tre initialises lors e e e de leur dclaration. De ce qui a t dit pour les tableaux en gnral il dcoule que les expressions suivantes sont e ee e e e correctes : char message1[80] = { S, a, l, u, t, \0 }; char message2[] = { S, a, l, u, t, \0 }; (la deuxi`me formule dnit un tableau de taille 6). Heureusement, C ore un raccourci : les deux expressions e e prcdentes peuvent scrire de mani`re tout ` fait quivalente : e e e e a e char message1[80] = "Salut"; char message2[] = "Salut"; Il faut bien noter cependant que lexpression "Salut" qui appara dans ces deux exemples nest pas du tout t trait par le compilateur comme les autres cha e nes de caract`res constantes rencontres dans les programmes. e e Ici, il ne sagit que dun moyen commode pour indiquer une collection de valeurs initiales ` ranger dans un a 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 composes de champs de types dirents. Elles se dclarent sous la syntaxe e e e suivante : struct nomopt { declaration ... declaration } rien nom , ... nom rien ;

A la suite du mot-cl struct on indique : e facultativement, le nom que lon souhaite donner ` la structure. Par la suite, lexpression  struct nom  a pourra tre employe comme une sorte de nom de type ; e e facultativement, entre accolades, la liste des dclarations des champs de la structure. Chaque champ peut e avoir un type quelconque (y compris struct, bien sr) ; u e e e e facultativement, la liste des variables que lon dnit ou dclare comme possdant la structure ici dnie. Exemple. struct fiche { int numero; char nom[32], prenom[32]; } a, b, c;
c H. Garreta, 2003

51

5.2 Structures et unions

OBJETS STRUCTURES

Cette dclaration introduit trois variables a, b et c, chacune constitue des trois champs numero, nom et e e prenom ; en mme temps elle donne ` cette structure le nom fiche. Plus loin, on pourra dclarer de nouvelles e a e variables analogues ` a, b et c en crivant simplement a e struct fiche x, y; (remarquez quil faut crire  struct fiche  et non pas  fiche ). Nous aurions pu aussi bien faire les e dclarations : e 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`re forme nous aurait empch par la suite de dclarer aussi facilement dautres variables de e e e e mme type. e Comme dans beaucoup de langages, on acc`de aux champs dune variable de type structure au moyen de e loprateur  . . Exemple : e a.numero = 1234; Les structures supportent les manipulations  globales 31 : on peut aecter une expression dun type structure ` une variable de type structure (pourvu que ces deux a types soient identiques) ; les structures sont passes par valeur lors de lappel des fonctions ; e le rsultat dune fonction peut tre une structure. e e La possibilit pour une fonction de rendre une structure comme rsultat est ocielle dans le C ANSI ; sur e e ce point les compilateurs plus anciens prsentent des dirences de comportement. Dautre part il est facile de e e voir que -sauf dans le cas de structures vraiment tr`s simples- les transmettre comme rsultats des fonctions est e e en gnral peu ecace. e e Voici quelques informations sur la disposition des champs des structures. Elles sont sans importance lorsque les structures sont exploites par le programme qui les cre, mais deviennent indispensables lorsque les structures e e sont partages entre sous-programmes crits dans divers langages ou lorsquelles servent ` dcrire les articles e e a e dun chier existant en dehors du programme. Position. C garantit que les champs dune structure sont allous dans lordre o` ils apparaissent dans la e u dclaration. Ainsi, avec la dnition e e 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.f32 . Contigu e. Les champs des structures subissent en gnral des contraintes dalignement dpendant du t e e e syst`me sous-jacent, qui engendrent des trous anonymes entre les champs. Il sagit souvent des mmes r`gles e e e que celles qui p`sent sur les variables simples. Deux cas relativement frquents sont les suivants : e e a a les champs de type char sont ` nimporte quelle adresse, tous les autres champs devant commencer ` une adresse paire ; les champs dun type simple doivent commencer ` une adresse multiple de leur taille (les short ` une a a adresse paire, les long ` une adresse multiple de quatre, etc.). a Initialisation. Une variable de type structure peut tre initialise au moment de sa dclaration, du moins e e e dans le cas dune variable globale. La syntaxe est la mme que pour un tableau. Exemple : e
31 Lexpression correcte de cette proprit consisterait ` dire quune structure, comme une expression dun type primitif, bncie e e a e e de la smantique des valeurs, par opposition ` un tableaux qui, lui, est assujetti ` la smantique des valeurs. e a a e 32 Alors que, avec la dclaration correspondante en Pascal (facile ` imaginer), beaucoup de compilateurs alloueraient les champs e a dans lordre : x.b, x.a, x.e, x.d, x.c et enn x.f.

52

c H. Garreta, 2003

5 OBJETS STRUCTURES

5.2

Structures et unions

struct fiche { int numero; char nom[32], prenom[32]; } u = { 1234, "DURAND", "Pierre" }; Comme pour les tableaux, si on indique moins de valeurs que la structure ne comporte de champs, alors les champs restants sont initialiss par des zros. Donner plus de valeurs quil ny a de champs constitue une erreur. e e 5.2.2 Unions

Tandis que les champs des structures se suivent sans se chevaucher, les champs dune union commencent tous au mme endroit et, donc, se superposent. Ainsi, une variable de type union peut contenir, ` des moments e a dirents, des objets de types et de tailles dirents. La taille dune union est celle du plus volumineux de ses e e champs. La syntaxe de la dclaration et de lutilisation dune union est exactement la mme que pour les structures, e e avec le mot union ` la place du mot struct. Exemple (la struct adresse virt est dnie ` la section 5.2.3) : a e a union mot_machine { unsigned long mot; char *ptr; struct adresse_virt adrv; } mix; La variable mix pourra tre vue tantt comme un entier long, tantt comme ladresse dun caract`re, tantt e o o e o comme la structure bizarre dnie ci-apr`s. Par exemple, le programme suivant teste si les deux bits hauts dune e e 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`mes, les oprateurs binaires de bits (&, |, ^) sav`rent galement tr`s e e e e e utiles. Linstruction prcdente pourrait aussi scrire (sur une machine 32 bits, o` int est synonyme de long) : e e e u if (m & 0xC0000000 == 0) etc. (lcriture binaire du chire hexa C est 1100). Bien sr, les programmes de cette sorte ntant pas portables, ces e u e deux mani`res de faire peuvent tre quivalentes sur une machine et ne pas ltre sur une autre. e e e e Remarque 2. Les unions permettent de considrer un mme objet comme possdant plusieurs types ; elles e e e semblent donc faire double emploi avec loprateur de conversion de type. Ce nest pas le cas. Dune part, cet e oprateur ne supporte pas les structures, tandis quun champ dune union peut en tre une. e e Dautre part, lorsque lon change le type dun objet au moyen de loprateur de changement de type, C e convertit la donne, an que lexpression ait, sous le nouveau type, une valeur vraisemblable et utile. Par e exemple, si x est une variable relle (float), lexpression  (int) x  convertit x en lentier dont la valeur est e la plus voisine de la valeur quavait le rel x. Cela implique une transformation, ventuellement assez coteuse, e e u de la valeur de x. A loppos, lorsquon fait rfrence ` un champ dune union, la donne ne subit aucune e ee a e transformation, mme si elle a t aecte en faisant rfrence ` un autre champ. Par exemple, si lon dclare e ee e ee a e union { float reel; int entier; } x; et quensuite on excute les aectations : e x.reel = 12.5; i = x.entier; lentier i obtenu sera celui dont la reprsentation binaire co e ncide avec la reprsentation binaire du nombre e ottant 12.5 ; cet entier na aucune raison dtre voisin de 12. e 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 tre ainsi dclars : e e e unsignedopt intopt identicateur rien
c H. Garreta, 2003

: constante-entiere 53

5.3 Enumrations e

OBJETS STRUCTURES

Chaque constante prcise le nombre de bits quoccupe le champ. Le type doit obligatoirement tre entier. e e Lorsque le type et le nom du champ sont absents, le nombre indiqu de bits est quand mme rserv : on suppose e e e e quil sagit de bits de  rembourrage  entre deux champs nomms. e Chacun de ces champs est log immdiatement apr`s le champ prcdent, sauf si cela le met ` cheval sur e e e e e a deux mots (de type int) ; dans ce cas, le champ est log au dbut du mot suivant. e e Exemple : la structure struct adresse_virt { unsigned depl : 9; unsigned numpagv : 16; : 5; unsigned tp : 2; }; dcoupe un mot de 32 bits en en quatre champs, comme indiqu33 sur la gure 7. e e
29 24 8 0

tp

numpag
Fig. 7 Champs de bits

depl

Attention. Dans tous les cas, les champs de bits sont tr`s dpendants de limplantation. Par exemple, e e certaines machines rangent les champs de gauche ` droite, dautres de droite ` gauche. Ainsi, la portabilit a a e des structures contenant des champs de bits nest pas assure ; cela limite leur usage ` une certaine catgorie e a e de programmes, comme ceux qui communiquent avec leur machine-hte ` un niveau tr`s bas (noyau dun o a e syst`me dexploitation, gestion des priphriques, etc.). Ces programmes sont rarement destins ` tre ports e e e e ae e dun syst`me ` un autre. e a

5.3

Enumrations e

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

o` chaque id-valeur est ` son tour de la forme u a identicateur Exemple : enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi } x, y; A la suite dune telle dclaration, le type enum jourouvrable est connu ; les variables x et y le poss`dent. e e Ce type est form dune famille nie de symboles qui reprsentent des constantes enti`res : lundi (gale ` 0), e e e e a mardi (gale ` 1), etc. Dans ce programme on pourra crire : e a e enum jourouvrable a, b, c; On aurait aussi bien pu introduire toutes ces variables par les dclarations e enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi }; ... enum jourouvrable x, y, a, b, c; ou encore enum { lundi, mardi, mercredi, jeudi, vendredi } x, y, a, b, c;
33 Sur

= expression-constante rien

un syst`me o` un mot est fait de 32 bits (sinon un champ serait ` cheval sur deux mots, ce qui est interdit). e u a

54

c H. Garreta, 2003

5 OBJETS STRUCTURES

5.4

Dclarateurs complexes e

mais cette derni`re forme nous aurait empch par la suite de dclarer aussi facilement dautres variables du e e e e mme type. Bien sr, les numrations peuvent se combiner avec la dclaration typedef (cf. section 5.4.3) : e u e e e apr`s la dclaration e e typedef enum jourouvrable { lundi, mardi, mercredi, jeudi, vendredi } JOUROUVRABLE; les expressions  enum jourouvrable  et  JOUROUVRABLE  sont quivalentes. e Les nombres entiers et les valeurs de tous les types numrs sont totalement compatibles entre eux. Par e ee consquent, laectation dun entier ` une variable dun type numr ou laectation rciproque ne provoquent e a e ee e en principe aucun avertissement de la part du compilateur . Ainsi, en C, les types numrs ne sont quune e ee deuxi`me mani`re de donner des noms ` des nombres entiers (la premi`re mani`re tant lemploi de la directive e e a e e e #define, voir section 8.1.2). Par dfaut, la valeur de chacune de ces constantes est gale au rang de celle-ci dans lnumration (lundi e e e e vaut 0, mardi vaut 1, etc.). On peut altrer cette squence en associant explicitement des valeurs ` certains e e a lments : ee 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

Dclarateurs complexes e

Jusquici nous avons utilis des formes simplies des dclarations. Tous les objets taient soit simples, soit e e e e des tableaux, des fonctions ou des adresses dobjets simples. Il nous reste ` voir comment dclarer des  tableaux a e 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 dcrire des types de complexit e e quelconque. La question concerne au premier chef les descripteurs de types, qui apparaissent dans trois sortes dnoncs : e e les dnitions de variables (globales et locales), les dclarations des param`tres formels des fonctions, les e e e dclarations des variables externes et les dclarations des champs des structures et des unions ; e e les dnitions des fonctions et les dclarations des fonctions externes ; e e les dclarations de types (typedef) e La mme question rappara dans des constructions qui dcrivent des types sans les appliquer ` des identie e t e a cateurs. Appelons cela des types dsincarns. Ils sont utiliss e e e par loprateur de changement de type lorsque le type de destination nest pas dni par un identicateur : e e  (char *) expression  ; comme argument de sizeof (rare) :  sizeof(int [100])  ; dans les prototypes des fonctions :  strcpy(char *, char *) ;  5.4.1 Cas des dclarations e

Un dclarateur complexe se compose de trois sortes dingrdients : e e e un type  terminal  qui est un type de base (donc numrique), enum, struct ou union ou bien un type auquel on a donn un nom par une dclaration typedef ; e e e e e e e un certain nombre doccurrences des oprateurs (), [] et * qui reprsentent des procds rcursifs de construction de types complexes ; lidenticateur quil sagit de dclarer. e Ces formules ont mauvaise rputation. Si elles sont faciles ` lire et ` traiter par le compilateur, elles sav`rent e a a e diciles ` composer et ` lire par le programmeur, car les symboles qui reprsentent les procds rcursifs de a a e e e e construction sont peu expressifs et se retrouvent placs ` lenvers de ce qui aurait sembl naturel. e a e Pour cette raison nous allons donner un procd mcanique qui, ` partir dune sorte de description  ` e e e a a lendroit , facile ` concevoir, fabrique la dclaration C souhaite. Appelons description naturelle dun type une a e e 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 par typedef ; e u la formule  tableau de T , o` T est la description naturelle dun type ; la formule  adresse dun T  (ou  pointeur sur un T ), o` T est la description naturelle dun type ; u la formule  fonction rendant un T , o` T est la description naturelle dun type. u
c H. Garreta, 2003

55

5.4 Dclarateurs complexes e

OBJETS STRUCTURES

Dans ces conditions, nous pouvons noncer la  recette de cuisine  du tableau 3. e Pour obtenir la dclaration dune variable ou dune fonction : e 1. Ecrivez : ` gauche, la description naturelle du type, a ` droite, lidenticateur ` dclarer (un seul). a a e 2. Tant que lexpression ` gauche nest pas un type de base, enum, struct ou union, a transformez les deux expressions de la mani`re suivante : e (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`gles (a) et (b), si exp d nest pas de la forme *exp, alors les parenth`ses e e autour de exp d peuvent tre omises. e Tab. 3 Construction dun dclarateur complexe e Exemple 1. Soit ` dclarer tabPtr comme un tableau (de dimension indtermine) de pointeurs dentiers. a e e e Appliquant les r`gles du tableau 3, nous crirons successivement : e e 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 rduisant ` un type simple, cette ligne constitue la dclaration C e a e cherche, comme il faudra lcrire dans un programme :  int *tabPtr[] ; . e e Exemple 2. Pour la comparer ` la prcdente, cherchons ` crire maintenant la dclaration de ptrTab, un a e e ae e pointeur sur un tableau34 dentiers : pointeur sur un tableau de int tableau de int int ptrTab *ptrTab (*ptrTab)[]

Les parenth`ses ci-dessus ne sont pas facultatives, car elles encadrent une expression qui commence par * e (dit autrement, elles sont ncessaires, car [] a une priorit suprieure ` *). e e e a Les dclarations pour lesquelles le membre gauche est le mme (` la n des transformations) peuvent tre e e a e regroupes. Ainsi, les dclarations de tabPtr et ptrTab peuvent tre crites ensemble : e e e e int *tabPtr[], (*ptrTab)[]; En toute rigueur les r`gles a et b de ce tableau devraient tre plus complexes, car la description naturelle e e du type dune fonction inclut en gnral la spcication des types des arguments (le prototype de la fonction), e e e et la description naturelle dun type tableau comporte lindication du nombre de composantes de celui-ci. En ralit ces indications sajoutent aux r`gles donnes ici, presque sans modier le processus que ces r`gles e e e e e tablissent : tout simplement, des informations annexes sont crites ` lintrieur des crochets des tableaux et e e a e des parenth`ses des fonctions. e Exemple 3. Soit ` dclarer une matrice de 10 lignes et 20 colonnes. Nos pouvons crire successivement : a e e 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]

Exemple 4. Quand on crit des programmes relevant du calcul numrique on a souvent besoin de variables e e de type  adresse dune fonction R R . Voici comment crire un tel type e
34 La notion de pointeur sur un tableau est assez acadmique, puisquune variable de type tableau reprsente dj` ladresse de e e ea celui-ci (dans la notion d adresse dun tableau  il y a une double indirection). En pratique on nen a presque jamais besoin.

56

c H. Garreta, 2003

5 OBJETS STRUCTURES

5.4

Dclarateurs complexes e

adresse de fonction (avec argument double) rendant double fonction (avec argument double) rendant double double La dclaration cherche est donc  double (*adrFon)(double) . e e

adrFon *adrFon (*adrFon)(double)

Exemple 5. Cet exemple abominable est extrait de la biblioth`que UNIX. La fonction signal renvoie e ladresse dune  procdure , cest-`-dire dune fonction rendant void. Voici comment il faudrait la dclarer en e a e syntaxe originale, cest-`-dire sans spcier les arguments de la fonction signal (cette dclaration est fournie a e e 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 dj` crire le type  pointeur sur une fonction ea e prenant un int et rendant un void  (ce nest pas tr`s dirent de lexemple prcdent) : e e e e void (*fon)(int); Attaquons-nous ` signal : a 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 dclaration cherche est e e void (*signal(int sig, int (*fon)(int)))(int) ; Comme annonc, les expressions nales obtenues sont assez lourdes ` digrer pour un humain. A leur dcharge e a e e on peut tout de mme dire quune fois crites elles se rv`lent bien utiles puisquelles font appara les types e e e e tre de leurs lments terminaux comme ils seront employs dans la partie excutable des programmes. Ainsi, ` la ee e e a vue de lexpression float matrice[10][20]; on comprend sans eort supplmentaire quun lment de la matrice scrira matrice[i][j] et que cette formule e ee e reprsentera un objet de type float. De mme, lnonc e e e e void (*signal(sig, func))(); nous fera reconna immdiatement lexpression (*signal(2,f))(n) comme un appel correct de la  procdure  tre e e rendue par lappel de signal. 5.4.2 Pointeurs et tableaux constants et volatils

Les qualieurs const et volatile peuvent aussi appara dans les dclarateurs complexes. Plutt que de tre e o rendre encore plus lourdes des explications qui le sont dj` beaucoup, contentons-nous de montrer des exemples ea signicatifs. Les cas les plus utiles sont les suivants : 1. La formule : const type *nom dclare nom comme un pointeur vers un objet constant ayant le type indiqu (la variable pointe, *nom, ne doit e e e pas tre modie). e e 2. La formule type *const nom
c H. Garreta, 2003

57

5.4 Dclarateurs complexes e

OBJETS STRUCTURES

dclare nom comme un pointeur constant vers un objet ayant le type indiqu (cest la variable nom qui ne doit e e pas tre modie). e e 3. Plus simplement, la formule const type nom[expr opt ] dclare nom comme un tableau dobjets constants35 (nom[i] ne doit pas tre modi). e e e Ainsi par exemple les dclarations e int e, *pe, *const pce = &e, te[100]; const int ec = 0, *pec, *const pcec = &ec, tec[100]; dclarent successivement : e e : un entier pe : un pointeur vers un entier pce : un pointeur constant vers un entier (qui doit tre obligatoirement initialis, puisquil est constant) e e 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 illgale. e En rsum, la seule vraie nouveaut introduite ici est la notion de pointeur constant et la syntaxe de e e e sa dclaration  type-point *const pointeur . Signalons enn que toute cette explication reste valable en e e remplaant le mot const et la notion dobjet constant par le mot volatile et le concept d objet volatil (cf. c section 1.5.7). On a donc la possibilit de dclarer des tableaux dobjets volatils, des pointeurs volatils vers des e e objets, volatils ou non, etc. 5.4.3 La dclaration typedef e

Cette construction est lhomologue de la dclaration type du langage Pascal. Elle permet de donner un nom e a ` un type dans le but dallger par la suite les expressions o` il gure. Syntaxe : e u typedef dclaration e Pour dclarer un nom de type, on fait suivre le mot rserv typedef dune expression identique ` une e e e a dclaration de variable, dans laquelle le rle du nom de la variable est jou par le nom du type quon veut e o e dnir. Exemple : les dclarations e e typedef float MATRICE[10][20]; ... MATRICE mat; sont quivalentes ` e a float mat[10][20]; Voici un autre exemple : typedef struct noeud { int info; struct noeud *fils_gauche, *fils_droit; } NOEUD; Cette expression dclare lidenticateur NOEUD comme le nom du type  la structure noeud  . Des variables e a, b, c de ce type pourront par la suite tre dclares aussi bien par lexpression e e e struct noeud a, b, c; que par NOEUD a, b, c; Remarquez lutilisation dirente dun nom de structure (prcd du mot struct) et dun nom de type. Par e e e e tradition, les noms des types dnis ` laide de la dclaration typedef sont crits en majuscules. e a e e Lensemble des r`gles de syntaxe qui sappliquent aux dclarations de variables sappliquent galement ici. e e e Par exemple, on peut introduire plusieurs noms de types dans un seul nonc typedef. Ainsi, ` la suite de e e a
35 Notez quil ny a pas besoin dune notation spciale pour dclarer un tableau constant dobjets (variables) puisque la signication e e ordinaire des tableaux est exactement celle-l`. a

58

c H. Garreta, 2003

5 OBJETS STRUCTURES

5.4

Dclarateurs complexes e

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 dclarateurs complexes, le nom dun type qui a fait lobjet dune dclaration e e typedef peut tre utilis comme type de base. Par exemple : e e typedef float LIGNE[20]; typedef LIGNE MATRICE[10]; est une autre mani`re dintroduire le type MATRICE dj` vu. De mme e ea e typedef void PROCEDURE(int); PROCEDURE *signal(int sig, PROCEDURE *func); est une autre mani`re (nettement plus facile ` lire, nest-ce pas ?) de dclarer la fonction signal vue prcdemment. e a e e e 5.4.4 Cas des types dsincarns e e

Tout cela devient encore plus sotrique lorsquon doit construire un type sans lappliquer ` un identicateur. e e a Le principe est alors le suivant : construisez la dclaration correcte dun nom quelconque X, puis eacez X. e De telles expressions sont utiles dans trois circonstances : 1. Pour construire un oprateur de changement de type. Exemple : la fonction malloc alloue un bloc de e mmoire de taille donne, dont elle renvoie ladresse. Dans les biblioth`ques du C original36 , cette fonction e e e est dclare comme rendant ladresse dun char, et lutilisateur doit convertir le rsultat de malloc dans le e e e type qui lintresse. Supposons avoir besoin despace pour ranger un tableau de N rels en double prcision. e e e Dclarations37 : e char *malloc(size_t); ... double *p; Utilisation : p = (double *) malloc(N * sizeof(double)); Le type dsincarn  double *  apparaissant ci-dessus a t dduit dune dclaration de pointeur,  double e e ee e e *ptr  en eaant le nom dclar, ptr. c e e 2. Comme argument de loprateur sizeof38 . Essayons par exemple dcrire autrement laectation prcdente. e e e e Dclarons un tableau T de N doubles : e double T[N] Supprimons T, il reste  double [N] . Par consquent, lappel de la fonction malloc montr ci-dessus peut e e scrire aussi : e p = (double *) malloc(sizeof(double [N])); 3. Dans les prototypes des fonctions, en C ANSI. Par exemple, la biblioth`que standard C comporte la e fonction qsort, qui implmente une mthode de tri rapide dun tableau (algorithme Quicksort) programm en e e e toute gnralit. Les arguments de cette fonction sont le tableau ` trier, le nombre de ses lments, la taille de e e e a ee chacun et une fonction qui reprsente la relation dordre utilise (le  crit`re de tri ) : elle reoit deux adresses e e e c dlments du tableau et rend un nombre ngatif, nul ou positif exprimant la comparaison. ee e La fonction qsort est donc ainsi dclare dans le chier stdlib.h : e e void qsort(const void*, size_t, size_t, int (*)(const void*, const void*)); 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));
36 Dans la biblioth`que ANSI (chier stdlib.h) la fonction malloc est dclare comme rendant une valeur de type  void * . e e e Ce type tant compatible avec tous les autres types pointeur, le probl`me expos ici ne se pose pas. e e e 37 Le type size t (dni dans <stddef.h>) reprsente un entier sans signe e e 38 Cette utilisation est plus rare. Loprateur sizeof est surtout utile dans le cadre des structures. e

c H. Garreta, 2003

59

6 POINTEURS

6
6.1

Pointeurs
Gnralits e e e

Un pointeur est une variable dont la valeur est ladresse dune cellule de la mmoire. e Les premiers langages volus ne permettaient pas les manipulations de pointeurs, de telles oprations tant e e e e obligatoirement connes dans les programmes crits en assembleur. Le langage Pascal a introduit les pointeurs e e parmi les types de donnes disponibles dans des langages gnralistes. Mais ils y sont prsents comme des e e e e e entits fort mystrieuses, dont lutilisateur nest cens conna ni la structure ni le comportement. Cela nen e e e tre facilite pas le maniement aux programmeurs dbutants. Beaucoup des dicults ` comprendre les pointeurs e e a proviennent plus du secret qui les entoure que de leur relle dicult demploi. e e Un pointeur nest pas un concept unique, sans ressemblance avec les autres types de donnes, car une adresse e nest en n de compte rien dautre quun nombre entier (lindice dun lment du tableau quest la mmoire de ee e lordinateur). Un pointeur devrait donc supporter toutes les oprations quon peut faire subir ` un nombre et e a qui gardent un sens lorsque ce nombre exprime un rang. Ce qui est tout ` fait spcique dun pointeur p est la possibilit de mentionner la variable dont la valeur a e e de p est ladresse. Un pointeur correctement initialis est toujours le renvoi ` une deuxi`me variable, la variable e a e pointe, accessible ` partir du contenu de p. e a Dans tous les langages o` ils existent, le principal intrt des pointeurs rside dans la possibilit de raliser u ee e e e des structures de donnes rcursives (listes et arbres) : des structures dans lesquelles un champ est virtuellement e e aussi complexe que la structure elle-mme. En plus de cet aspect, en C les pointeurs sont le moyen damliorer e e lacc`s aux lments des tableaux ordinaires, en incorporant au langage des techniques dadressage indirect e ee couramment utilises en assembleur. e 6.1.1 Dclaration et initialisation des pointeurs e

Nous avons tudi ` la section prcdente la syntaxe des dclarations des pointeurs. Dans le cas le plus e e a e e e simple, cette syntaxe est type *variable Si par la suite on aecte ` une telle variable ladresse dun certain objet, on fait rfrence ` ce dernier par a ee a lexpression : *variable Par exemple, lexpression char *p; dnit la variable p comme  pointeur vers un char . Lorsquelle aura t correctement initialise, elle contiendra e ee e ladresse dun caract`re, auquel on pourra faire rfrence par lexpression e ee *p Initialisation des pointeurs. Un pointeur contient en principe une adresse valide. Il existe trois mani`res e sres de donner une telle valeur ` un pointeur : u a 1. Prendre ladresse dune variable existant par ailleurs. Exemple, dclaration : e 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 dsigneront le mme objet (le contenu de x). e e 2. Provoquer lallocation dun nouvel espace, cest-`-dire allouer dynamiquement une variable anonyme. a Exemple, dclaration : e type *p; Initialisation : p = malloc(sizeof(type)); Apr`s cette instruction, une variable tout ` fait analogue ` la variable x de lexemple prcdent existe. e a a e e Comme dans lexemple prcdent, *p en dsigne le contenu. La dirence est quici elle na pas de nom propre : e e e e 60
c H. Garreta, 2003

6 POINTEURS

6.1

Gnralits e e e

si la valeur de p venait ` tre altre (sans avoir t auparavant sauvegarde dans un autre pointeur), la variable ae ee ee e pointe deviendrait inaccessible et serait dnitivement perdue. e e 3. Obtenir une valeur par des calculs lgitimes sur des pointeurs. Par exemple, comme on le verra bientt, e o avec les dclarations e type t[10], *p, *q; suivies de linitialisation p = &t[0]; lexpression q = p + 3; aecte ` q ladresse du quatri`me lment du tableau t, cest-`-dire &t[3]. a e ee a Il existe aussi une mani`re risque, en tout cas non portable, de donner une valeur ` un pointeur : lui aecter e e a une constante ou une expression numrique, dans le style de : e p = (struct machin *) 0x2A3E; De telles expressions, indispensables dans certaines fonctions de bas niveau (contrle dorganes matriels, etc.), o e rendent non portables les programmes o` elles gurent. Elles doivent donc tre vites chaque fois que cela est u e e e possible. A propos de la taille des pointeurs et celle des entiers. Une expression comme laectation ci-dessus soul`ve un autre probl`me : existe-t-il une relation entre la taille dun nombre entier (types int et e e unsigned int) et celle dun pointeur ? Le langage ne prescrit rien ` ce sujet ; il est donc parfois erron et toujours a e dangereux de supposer quun entier puisse contenir un pointeur. Cette question peut sembler tr`s secondaire, e lutilisation de variables enti`res pour conserver ou transmettre des pointeurs paraissant bien marginale. Or e cest ce quon fait, peut-tre sans sen rendre compte, lorsquon appelle une fonction qui renvoie un pointeur e sans avoir pralablement dclar la fonction. Par exemple, supposons que sans avoir dclar la fonction malloc e e e e e (ni inclus le chier <stdlib.h>) on crive laectation e p = (struct machin *) malloc(sizeof(struct machin)); Le compilateur rencontrant malloc pour la premi`re fois, il postule que cette fonction renvoie un int et ins`re e e a ` cet endroit le code qui convertit un entier en un pointeur. Cette conversion peut fournir un rsultat tout ` fait e a erron. Notamment, sur un syst`me o` les entiers occupent deux octets et les pointeurs quatre, les deux octets e e u hauts du rsultat de malloc seront purement et simplement remplacs par zro. La question est dautant plus e e e vicieuse que le programme ci-dessus se comportera correctement sur tous les syst`mes dans lesquels les entiers e et les pointeurs ont la mme taille. e 6.1.2 Les pointeurs gnriques et le pointeur NULL e e (ou tout simplement p = t; )

Certaines fonctions de la biblioth`que, comme malloc, rendent comme rsultat une  adresse quelconque , e e a ` charge pour lutilisateur de la convertir en ladresse spcique qui lui convient. La question est : comment e dclarer de tels pointeurs peu typs ou pas typs du tout ? e e e Le C original nayant rien prvu ` ce sujet, la tradition stait cre de prendre le type char * pour  pointeur e a e ee vers nimporte quoi . En eet, sur beaucoup de syst`mes les adresses des objets de taille non unitaire supportent e des contraintes dalignement (les short aux adresse paires, les long aux adresses multiples de quatre, etc.), alors que les adresses des caract`res sont les seules ` ne subir aucune restriction. e a Le C ANSI introduit un type spcique pour reprsenter de telles adresses gnriques : le type void *. Ce e e e e type est compatible avec tout autre type pointeur : nimporte quel pointeur peut tre aect ` une variable de e ea type void * et rciproquement39 . Par exemple, le chier standard <stdlib.h> contient la dclaration suivante e e de la fonction malloc : void *malloc(size_t size); Appel : p = malloc(sizeof(struct machin)); Bien entendu, on peut continuer ` utiliser une conversion de type (on peut mme trouver que cela augmente a e la lisibilit du texte source) : e p = (struct machin *) malloc(sizeof(struct machin));
39 Avec une protection supplmentaire : le type void tant incompatible avec tous les autres types, lobjet point par une variable e e e dclare de type void * ne pourra pas tre utilis tant que la variable naura pas t convertie en un type pointeur prcis. e e e e e e e

c H. Garreta, 2003

61

6.2 Les pointeurs et les tableaux

POINTEURS

Le pointeur NULL. Une des principales applications des pointeurs est la mise en uvre de structures rcursives de taille variable : listes cha ees de longueur inconnue, arbres de profondeur quelconque, etc. Pour e n raliser ces structures il est ncessaire de possder une valeur de pointeur, distincte de toute adresse valide, e e e reprsentant la n de liste, la structure vide, etc. e C garantit quaucun objet valide ne se trouve ` ladresse 0 et lentier 0 est compatible avec tout type pointeur. a Cette valeur peut donc tre utilise comme la valeur conventionnelle du  pointeur qui ne pointe vers rien  ; e e cest lquivalent en C de la constante nil de Pascal. En pratique on utilise plutt la constante NULL qui est e o dnie dans le chier <stddef.h> : e #define NULL ((void *) 0)

6.2
6.2.1

Les pointeurs et les tableaux


Arithmtique des adresses, indirection et indexation e

Arithmetique. Certaines des oprations arithmtiques dnies sur les nombres gardent un sens lorsque e e e lun des oprandes ou les deux sont des adresses : e laddition et la soustraction dun nombre entier ` une adresse ; a la soustraction de deux adresses (de types rigoureusement identiques). Lide de base de ces extensions est la suivante : si exp exprime ladresse dun certain objet O1 alors exp+1 e exprime ladresse de lobjet de mme type que O1 qui se trouverait dans la mmoire immdiatement apr`s O1 e e e e et exp1 ladresse de celui qui se trouverait immdiatement avant. e De mani`re analogue, si exp 1 et exp 2 sont les adresses respectives de deux objets O1 et O2 de mme type et e e contigus (O2 apr`s O1 ), il semble naturel de donner ` exp 2 exp 1 la valeur 1. e a Larithmtique des adresses dcoule de ces remarques. Dun point de vue technique, ajouter 1 ` un pointeur e e a cest le considrer momentanment comme un entier et lui ajouter une fois la taille de lobjet point. On peut e e e aussi dire cela de la mani`re suivante : exp 1 et exp 2 tant deux expressions de type  adresse dun objet de type e e T  et n une expression enti`re, nous avons : e exp 1 + n (T *) ((unsigned long) exp 1 + nsizeof(T )) et exp2 exp1 (unsigned long)exp2 (unsigned long)exp1 sizeof(T)

Lindexation. Il dcoule des explications prcdentes que larithmtique des adresses ne se justie que si e e e e on peut considrer un pointeur p comme ladresse dun lment t[i0 ] dun tableau, ventuellement ctif. On e ee e peut alors interprter p + i comme ladresse de t[i0 + i] et q p comme lcart entre les indices correspondant e e a ` ces deux lments. Les pointeurs avec leur arithmtique et lacc`s aux lments des tableaux sont donc des ee e e ee concepts tr`s voisins. En ralit il y a bien plus quun simple lien de voisinage entre ces deux concepts, car lun e e e est la dnition de lautre : e Soit exp une expression quelconque de type adresse et ind une expression enti`re. Par dnition, lexpression : e e *(exp + ind ) se note : exp[ind ] On en dduit que si on a lune ou lautre des dclarations e e type t[100]; ou type *t; alors dans un cas comme dans lautre on peut crire indiremment e e t[0] ou *t ou t[i] ou *(t + i) Bien entendu, un programme est plus facile ` lire si on montre une certaine constance dans le choix des a notations. Exemple. Le programme suivant parcourt un tableau en utilisant un pointeur : 62
c H. Garreta, 2003

6 POINTEURS

6.2

Les pointeurs et les tableaux

char s[10] = "Hello"; main() { char *p = s; while (*p != \0) fputc(*p++, stdout); } Remarque 1. La similitude entre un tableau et un pointeur est grande, notamment dans la partie excutable e des fonctions. Mais il subsiste des dirences, surtout dans les dclarations. Il ne faut pas oublier que les e e dclarations e int *p, t[100]; introduisent p comme une variable pointeur et t comme une constante pointeur. Ainsi, les expressions p = t et p++ sont lgitims, mais t = p et t++ sont illgales, car elles tentent de modier une  non variable  (un nom e e de tableau nest pas une lvalue). Remarque 2. Dans ce mme esprit, rappelons la dirence quil peut y avoir entre les deux dclarations e e e suivantes : char s[] = "Bonsoir"; et char *t = "Bonsoir"; s est (ladresse d) un tableau de caract`res (gure 8) e

s B o n s o i r\ 0
Fig. 8 Cha de caract`res  variable  ne e

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

t o n s o i r \ B o n s o i r \0 A u 0 re v o

Fig. 9 Constante cha de caract`res ne e

On pourra rfrencer ces caract`res indiremment par s[i], *(s + i), t[i] ou *(t + i). Cependant, ee e e dans le premier cas le compilateur a allou un vrai tableau de 8 caract`res, et y a copi la cha donne ; dans e e e ne e le second cas, la cha a t range avec les autres constantes littrales et le compilateur na allou quune ne e e e e e variable de type pointeur dans laquelle il a rang ladresse de cette constante. Par suite, s nest pas une variable e mais *s, s[i] et *(s + i) en sont, tandis que t est une variable mais *t, t[i] et *(t + i) nen sont pas40 . A ce propos voir aussi la section 5.1.3. Larithmtique des adresses permet damliorer lecacit de certains traitements de tableaux41 , en mettant e e e a ` prot le fait que lindirection est plus rapide que lindexation. Autrement dit, lacc`s ` une donne ` travers e a e a une expression de la forme *p est rpute (lg`rement) plus rapide que lacc`s ` la mme donne ` travers lexpression e e e e e a e e a *(q + i) (ou, ce qui est la mme chose, q[i]) e Dveloppons un exemple classique : la fonction strcpy(dest, srce) copie la cha de caract`res srce e ne e dans lespace point par dest. Premi`re forme : e e
40 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 ` fait lgitimes et le compilateur ne nous interdira pas de les faire appara a e tre ` gauche dune aectation. a 41 Bien s r, il sagit de petites conomies dont le bnce ne se fait sentir que lorsquelles sappliquent ` des parties des programmes u e e e a qui sont vraiment critiques, cest-`-dire excutes de mani`re tr`s frquente. a e e e e e

c H. Garreta, 2003

63

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`me forme : e char *strcpy(char *dest, char *srce) { register char *d = dest, *s = srce; while ((*d++ = *s++) != \0) ; return dest; } La deuxi`me forme est plus ecace, car on y a supprim lindexation. Pour sen convaincre, il sut de e e considrer que la boucle qui gure dans la premi`re forme quivaut ` : e e e a while ((*(dest + i) = *(srce + i)) != \0) i++; 6.2.2 Tableaux dynamiques

Lquivalence entre les tableaux et les pointeurs permet de raliser des tableaux dynamiques, cest-`-dire e e a des tableaux dont la taille nest pas connue au moment de la compilation mais uniquement lors de lexcution, e lorsque le tableau commence ` exister. Pour cela il sut de a remplacer la dclaration du tableau par celle dun pointeur ; e allouer lespace ` lexcution, avant toute utilisation du tableau, par un appel de malloc ; a e dans le cas dun tableau local, librer lespace ` la n de lutilisation. e a Pour tout le reste, lutilisation de ces tableaux dynamiques est identique ` celle des tableaux normaux (ce a qui, pour le programmeur, est une tr`s bonne nouvelle !). Exemple : la fonction ci-dessous calcule dans un e tableau la N eme ligne du triangle de Pascal42 et sen sert dans des calculs auxquels nous ne nous intressons e pas ici. On notera quune seule ligne di`re entre la version ` tableau statique et celle avec tableau dynamique. e a 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 ` la compilation */ a

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 ` la compilation */ a

tab = malloc((N + 1) * sizeof(int)); if (tab == NULL) return;


42 Rappelons que les coecients de la neme ligne du triangle de Pascal sont dnis rcursivement ` partir de ceux de la n 1eme e e a p p1 p ligne par : Cn = Cn1 + Cn1 .

64

c H. Garreta, 2003

6 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"); /* ` ne pas oublier ( tab est une variable locale) */ a

} free(tab); }

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

Reflexions sur la double indexation. Supposons que nous ayons ` crire un programme qui op`re sur ae e des matrices ` N L et N C colonnes. La dclaration dune telle matrice est facile ` construire (cf. section 5.4.1) : a e a tableau de NL tableau de NC float tableau de NC float tableau de NC float float float m1 (m1)[NL] m1[NL] (m1[NL])[NC] m1[NL][NC]

Un lment particulier de cette matrice sera not m1[i][j]. Dapr`s ce que nous savons dj`, on peut ee e e ea linterprter de la mani`re suivante43 : e e m1[i][j] = *(m1[i] + j) = *(float *)((unsigned) m1[i] + j sizeof(float)) Le probl`me est : avec la dclaration que nous avons donne, que signie m1[i] ? Il est intressant de constater e e e e que cette expression nest pas une lvalue, puisquelle est de type  tableau de... , mais que, ` part cela, elle est a traite comme un lment dun tableau ordinaire. Autrement dit, si on suppose avoir fait la dclaration e ee e typedef float LIGNE[NC]; /* LIGNE = tableau de NC float */ alors m1[i] est une expression de type LIGNE valant : m1[i] = *(m1 + i) = *(LIGNE *)((unsigned) m1 + i sizeof(LIGNE)) = *(LIGNE *)((unsigned) m1 + i NC sizeof(float)) En oubliant pour un instant les types des pointeurs, il nous reste m1[i][j] = *(m1 + (i NC + j) sizeof(float)) Nous retrouvons lexpression classique i NC + j caractrisant lacc`s ` un lment mi,j dune matrice N LN C. e e a ee Cette expression traduit la disposition  par lignes  des tableaux rectangulaires, comme le montre la gure 10.

m1 i x NC j

m1[i][j]
Fig. 10 Tableau bidimensionnel statique Etudions une autre mani`re daccder aux lments de cette mme matrice. Soient les dclarations e e ee e e
43 Pour

allger les formules, nous supposons ici que la taille dun int, et donc celle dun unsigned, sont gales ` celle dun pointeur. e e a

c H. Garreta, 2003

65

6.2 Les pointeurs et les tableaux

POINTEURS

float m1[NL][NC], *m2[NL]; La deuxi`me se lit, compte tenu des priorits : float *(m2[NL]). La variable m2 est donc dclare comme e e e e un tableau de NL pointeurs vers des nombres ottants. Pour raliser la matrice dont nous avons besoin, il nous e sut dinitialiser correctement ces pointeurs : chacun sera ladresse dune ligne de la matrice prcdente. Nous e e aurons la conguration de la gure 11.

m2 i m2[i]

m2[i][j]
Fig. 11 Tableau bidimensionnel ralise avec un tableau de pointeurs e Il est remarquable quun lment de la nouvelle  matrice  ainsi dclare se note encore ee e e m2[i][j] mais maintenant, cette expression se traduira (en continuant ` ngliger les conversions des types pointeurs) par : a e m2[i][j] = *(m2[i] + j sizeof(float)) = *(*(m2 + i sizeof(float *)) + j sizeof(float)) Les multiplications par sizeof(float) et sizeof(float *) sont ralises de mani`re tr`s rapide par les e e e e calculateurs (ce sont des multiplications par des puissances de 2), nous pouvons les ignorer. Il reste que nous avons remplac  m1 + i NC + j  par  *(m2 + i) + j . Nous avons donc bien limin une  vraie  e e e multiplication : notre deuxi`me mani`re est plus ecace. En contrepartie nous occupons un peu plus despace e e (les NL pointeurs supplmentaires). e De plus, il nous faut initialiser le tableau de pointeurs m2. Comme notre but est ici daccder aux lments e ee de la matrice statique m1 existant par ailleurs, cela se fait par une expression dune tonnante simplicit : e e for (i = 0; i < NL; i++) m2[i] = m1[i]; 6.2.4 Tableaux multidimensionnels dynamiques

Les considrations prcdentes montrent pourquoi dans la ralisation dune matrice dynamique on devra e e e e renoncer au mcanisme dindexation de base. En eet, NC doit tre connu ` la compilation pour que m[i][j] e e a ait un sens. Or nous sommes maintenant parfaitement quips pour expliquer la mani`re de raliser une telle matrice. e e e e Supposons que la fonction void unCalcul(int nl, int nc) implmente un algorithme qui requiert une mae trice locale ` nl lignes et nc colonnes. a Version statique : #define NL 20 #define NC 30 ... void unCalcul(int nl, int nc) { double mat[NL][NC]; /* en esprant que nl < NL et nc < NC */ e int i, j;

66

c H. Garreta, 2003

6 POINTEURS

6.2

Les pointeurs et les tableaux

/* utilisation de la matrice mat. Par exemple : */ for (i = 0; i < nl; i++) for (j = 0; j < nc; j++) mat[i][j] = 0; etc. } Version dynamique (par souci de clart nous y avons nglig la dtection des checs de malloc) : e e e e e void unCalcul(int nl, int nc) { double **mat; int i, j; /* initialisation des pointeurs */ mat = malloc(nl * sizeof(double *)); for (i = 0; i < nl; i++) mat[i] = malloc(nc * sizeof(double)); /* utilisation de la matrice mat. Par exemple : */ for (i = 0; i < nl; i++) for (j = 0; j < nc; j++) mat[i][j] = 0; etc. /* libration (indispensable dans le cas dune variablelocale) */ e for (i = 0; i < nl; i++) free(mat[i]); free(mat); } Dans cette mani`re de procder, les lignes de la matrice sont alloues ` loccasion de nl appels distincts de e e e a malloc. La matrice est ralise par des morceaux de mmoire parpille, comme le montre la gure 12. e e e e e

m2

Fig. 12 Matrice dynamique (lignes alloues sparment) e e e Une lg`re variante consiste ` allouer dun seul coup toute la mmoire ncessaire aux lignes de la matrice. e e a e e La structure de la matrice ressemble alors ` celle de la gure 11 : a int unCalcul(int nl, int nc) { double **mat, *espace; int i, j; /* initialisation des pointeurs */ mat = malloc(nl * sizeof(double *)); if (mat == NULL) return 0; /* chec */ e espace = malloc(nl * nc * sizeof(double)); if (espace == NULL) { free(mat); return 0; /* chec */ e } for (i = 0; i < nl; i++) mat[i] = espace + i * nc;
c H. Garreta, 2003

67

6.2 Les pointeurs et les tableaux

POINTEURS

/* utilisation de la matrice mat. Par exemple : */ for (i = 0; i < nl; i++) for (j = 0; j < nc; j++) mat[i][j] = 0; etc. /* libration (indispensable dans le cas dune variablelocale) */ e free(espace); free(mat); return 1; }

/* succ`s */ e

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

p a s

u n

t e xt e

g n i a l ! \0

Fig. 13 Matrice de caract`res e

6.2.5

Tableaux de cha nes de caract`res e

Le remplacement dun tableau ` deux indices par un tableau de pointeurs se rv`le encore plus utile dans a e e le traitement des tableaux de cha nes de caract`res. Ici, cette technique entra une conomie de place sube ne e stantielle, puisquelle permet de remplacer un tableau rectangulaire dont la largeur est dtermine par la plus e e grande longueur possible dune cha (avec un espace inoccup tr`s important) comme celui de la gure 13, ne e e par un espace linaire dans lequel chaque cha noccupe que la place qui lui est ncessaire, comme le montre e ne e la gure 14.

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

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

n ' e s ...

encombrement rel des caractres


Fig. 14 Tableau de cha nes de caract`res e Nous allons illustrer cette mthodologie par un exemple tr`s simple mais tout ` fait typique. Notre programme e e a lit des lignes de caract`res au terminal et les range dans une table, jusqu` la rencontre de la n de chier ou e a la lecture dune ligne vide. Ensuite, il ordonne les lignes lues ; nalement il ache les lignes ordonnes. e Les lignes lues sont ranges les unes ` la suite des autres dans un tableau unique, nomm espace, dont le e a e pointeur libre indique la premi`re place disponible. La table des mots, note mots, est une table de renvois e e dans espace. La gure 14 illustre ces structures de donnes. Notez que le tri se fait en ordonnant la table des e pointeurs ; les caract`res eux-mmes ne sont nullement dplacs. e e e e Les fonctions gets (lecture de cha ne), puts (criture de cha e ne), strcmp (comparaison de cha nes) et strlen (longueur dune cha ne) appartiennent ` la biblioth`que standard et sont expliques dans les sections suivantes. a e e #include <stdio.h> #include <string.h> #define MAXMOTS 100 #define MAXCARS 3000 68
c H. Garreta, 2003

6 POINTEURS

6.2

Les pointeurs et les tableaux

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 ` fait analogue ` notre table mots est cre par le a a ee compilateur lorsquon initialise un tableau de cha nes de caract`res, comme dans : e char *messages[] = { "Fichier inexistant", "Volume ou repertoire inexistant", "Nom de fichier incorrect", "Erreur physique dentree-sortie", "Autre erreur" }; La seule dirence entre notre tableau mots et le tableau messages que crerait le compilateur par suite e e de la dclaration ci-dessus est la suivante : les lments de mots sont des pointeurs vers un espace modiable e ee faisant partie de la mmoire attribue pour les variables de notre programme, tandis que messages est fait de e e pointeurs vers des cellules de la  zone de constantes , espace immodiable allou et garni une fois pour toutes e par le compilateur. 6.2.6 Tableaux multidimensionnels formels

Nous avons vu que si un argument formel dune fonction ou une variable externe est un tableau ` un seul a indice, alors lindication du nombre de ses lments est sans utilit puisquil ny a pas dallocation despace. ee e Mais que se passe-t-il pour les tableaux ` plusieurs indices ? Si un tableau a t dclar a ee e e T table[N1 ][N2 ] ... [Nk1 ][Nk ] ; (N1 , N2 , etc. sont des constantes) alors un acc`s comme e table[i1 ][i2 ] ... [ik1 ][ik ] se traduit par *( (T *)table + i1 N2 N3 Nk + + ik1 Nk + ik )
c H. Garreta, 2003

69

6.2 Les pointeurs et les tableaux

POINTEURS

Quil y ait ou non allocation despace, il faut que ce calcul puisse tre eectu. Do` la r`gle gnrale : e e u e e e lorsquun argument formel dune fonction ou une variable externe est un tableau ` plusieurs indices, lindication a de la premi`re dimension est sans utilit mais les autres dimensions doivent tre spcies. e e e e e Pour un tableau ` deux indices, cela donne : il est inutile dindiquer combien il y a de lignes, mais il faut a donner la taille dune ligne. Par exemple, la fonction suivante eectue la multiplication par un scalaire k de la matrice m dclare NL NC (deux constantes connues dans ce chier) : e e 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 ` faire soi-mme les calculs dindice : ea e 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 ` lire ! a

6.2.7

Tableaux non ncessairement indexs ` partir de zro e e a e

En principe, les tableaux de C sont indexs ` partir de zro : sans quil soit ncessaire de le spcier, un e a e e e tableau T dclar de taille N est fait des lments T0 , T1 . . . TN 1 . Nous avons vu que, dans la grande majorit e e ee e des applications, cette convention est commode et able. Il existe cependant quelques situations o` on souhaite pouvoir noter les lments dun tableau T1 , T2 . . . TN u ee ou, plus gnralement, Ta , Ta+1 . . . Tb , a et b tant deux entiers quelconques vriant a b. Nous allons voir e e e e que lallocation dynamique fournit un moyen simple et pratique44 pour utiliser de tels tableaux. ` Cas des tableaux a un indice. La fonction dTable45 cre un tableau dlments de type double dont e ee les indices sont les entiers iMin, iMin+1, . . . iMax : double *dTable(int iMin, int iMax) { double *res = malloc((iMax - iMin + 1) * sizeof(double)); if (res == NULL) { erreurTable = 1; return NULL; } erreurTable = 0; return res - iMin; } (Si on avait besoin de tableaux de float, de int, etc. on crirait des fonctions analogues fTable, iTable, e etc.). La variable globale erreurTable est rendue ncessaire par le fait quun rsultat NULL ne sut pas ici ` e e a caractriser lchec de lallocation : res - iMin peut tre nul alors que lallocation a russi et res = NULL. La e e e e gure 15 illustre le rsultat rendu par la fonction prcdente lors dun appel comme dTable(5, 10). e e e 0n acc`de aux lments dun tel tableau ` laide dun indice compris entre les valeurs indiques mais, ` part e ee a e a cela, de mani`re tout ` fait habituelle ; en particulier, sans perte decacit. Exemple dutilisation : e a e
44 Nous expliquons ici une technique massivement employe dans la biblioth`que de calcul scientique Numerical Recipes ( c e e Numerical Recipes Software) base sur le livre Numerical Recipes in C (W. Press, S. Teukolsky, W. Vetterling et B. Flannery, e Cambridge University Press, 1992). 45 Dobscures contraintes techniques, ayant perdu tout intrt de nos jours, font que cette technique peut ne pas fonctionner dans e e les implantations de C sur danciennes plate-formes, comme les PC ` base de processeurs Intel antrieurs au 80286 (un lointain a e anctre du Pentium), dans lesquelles larithmtique des adresses subit plusieurs limitations drastiques. e e

70

c H. Garreta, 2003

6 POINTEURS

6.2

Les pointeurs et les tableaux

adresse rendue comme rsultat par dTable

cellules effectivement alloues


Fig. 15 Adresse rendue par la fonction dTable

... double *t = dTable(5, 10); ... for (i = 5; i <= 10; i++) t[i] = 1000 + i; ... for (i = 5; i <= 10; i++) printf("%.1f\n", t[i]); ... Attention. Un tableau cre par un appel de la fonction dTable est dynamiquement allou, cependant il e e nest pas destin ` tre libr. En particulier, ` la suite du programme ci-dessus, un appel comme eae ee a free(t); gnrera certainement une erreur ` lexcution. Il faudrait dnir une structure plus complexe quun simple e e a e e tableau, mmorisant soit la valeur de iMin, soit celle de res (variables locales de la fonction dTable ci-dessus) e pour pouvoir grer la libration ultrieure dun tel tableau. e e e ` Cas des tableaux a plusieurs indices. Les remarques prcdentes, avec celles faites ` la section 6.2.4, e e a sugg`rent une mani`re dutiliser des matrices dont les lignes et les colonnes ne sont pas indexes ` partir de e e e a zro. e La fonction dMatrice cre une matrice dont les lignes sont indexes par les entiers lMin, lMin+1 . . . lMax e e et les colonnes sont indexes par les entiers cMin, cMin+1 . . . cMax : e double **dMatrice(int lMin, int lMax, int cMin, int cMax) { int nbLin, nbCol, i; double *espace, **lignes, *p; nbLin = lMax - lMin + 1; nbCol = cMax - cMin + 1; espace = malloc(nbLin * nbCol * sizeof(double)); if (espace == NULL) { erreurTable = 1; return NULL; } lignes = malloc(nbLin * sizeof(double *)); if (lignes == NULL) { erreurTable = 1; free(espace); return NULL; } p = espace - cMin; for (i = 0; i < nbLin; i++) { lignes[i] = p; p += nbCol; } erreurTable = 0; return lignes - lMin; } Comme prcdemment, lemploi de ces matrices ne saccompagne daucune perte decacit. Voici un e e e exemple dutilisation :
c H. Garreta, 2003

71

6.3 Les adresses des fonctions

POINTEURS

... double **m = dMatrice(5, 10, -3, 3); ... for (i = 5; i <= 10; i++) for (j = -3; j <= 3; j++) m[i][j] = 1000 * i + j; ... for (i = 5; i <= 10; i++) { for (j = -3; j <= 3; j++) printf("%10.1f ", m[i][j]); printf("\n"); } ...

6.3
6.3.1

Les adresses des fonctions


Les fonctions et leurs adresses

A loccasion de ltude des dclarateurs complexes, nous avons appris que la syntaxe de C permet de dclarer e e e des objets de type  fonction rendant un (objet de type) T . De plus, cette expression est un dclarateur qui e peut se composer rcursivement avec les autres,  adresse de...  et  tableau de... . Tout cela sugg`re que e e nous allons pouvoir travailler avec des variables de type fonction, des tableaux de fonctions, peut-tre mme e e des fonctions renvoyant des fonctions, etc. Lorsquun programme est actif, les fonctions qui le composent sont loges dans la mmoire de lordinateur, e e tout comme les donnes que ces fonctions manipulent. Le parall`le sarrte l`, car il y a entre les fonctions et e e e a les donnes une dirence rdhibitoire : alors que du type dune donne on peut dduire sa taille, le type dune e e e e e fonction ne spcie que le type du rsultat46 et ne donne aucune indication sur lencombrement de la fonction (la e e taille de la fonction une fois compile). Autrement dit, le type  fonction rendant un objet de type T  na pas e de taille dnie. De cette remarque dcoule limpossibilit technique de dclarer des variables de type fonction. e e e e Lobjet variable le plus simple permettant de dsigner une fonction devra avoir au moins le type  adresse dune e fonction rendant un objet de type T . Comment le programmeur obtient-il des adresses de fonctions ? De la mme mani`re que le nom dun tableau e e reprsente son adresse de base, le nom dune fonction reprsente ladresse de celle-ci. Ainsi, si on a la dnition e e e de fonction type nom(...) { etc. } alors lexpression rduite ` nom : e a e poss`de le type  adresse dune fonction rendant un type  ; a pour valeur ladresse de base de la fonction ; nest pas une lvalue. Nous allons examiner deux applications de cela parmi les plus importantes.

6.3.2

Fonctions formelles

Le probl`me des fonctions formelles, cest-`-dire des fonctions arguments dautres fonctions, est rsolu en C e a e par lutilisation darguments de type  pointeur vers une fonction . Exemple 1 (invitable). La fonction dicho rsout lquation f (x) = 0. Ses arguments sont : les bornes a et e e e b de lintervalle de recherche, la prcision voulue et surtout la fonction f dont on cherche un zro. Voici cette e e fonction :
46 Dans

le C ANSI ( avec prototypes ), la type dune fonction spcie aussi le nombre et les types des arguments, bien s r. e u

72

c H. Garreta, 2003

6 POINTEURS

6.3

Les adresses des fonctions

double dicho(double a, double b, double eps, double (*f)(double)) { double x; /* hypoth`se : f(a) * f(b) <= 0 */ e 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 dclar comme un pointeur vers une fonction ; par consquent *f est une fonction et e e e (*f)(x) un appel de cette fonction. Si une fonction est le nom dune fonction particuli`re, changeant de signe e sur lintervalle [0, 1], un appel correct de dicho serait x = dicho(0.0, 1.0, 1E-8, une_fonction); Remarque. En syntaxe originale la fonction dicho scrirait e double dicho(a, b, eps, f) double a, b, eps, (*f)(); { etc. (la suite est la mme) e } Exemple 2. Reprenons notre programme de la section 6.2.5 en remplaant la fonction de tri na par la c f fonction qsort de la biblioth`que standard. Cette fonction est explique ` la section 8.4.2, elle trie un tableau e e a par rapport ` un crit`re quelconque. Si elle nous intresse ici cest parce quelle prend ce crit`re, reprsent par a e e e e e 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 dclaration suivante se trouve aussi dans <stdlib.h> : */ e 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 ^tre dclars "void *" (voyez la */ e e e /* dclaration de qsort). En ralit, dans les appels de cette fonction que */ e e e /* nous faisons ici, a et b sont des adresses de "cha^nes", cest-`-dire des */ a /* 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]); }
c H. Garreta, 2003

73

6.3 Les adresses des fonctions

POINTEURS

Remarque. La dnition de comparer peut para bien complique. Pour la comprendre, il faut considrer e tre e e les diverses contraintes auxquelles elle doit satisfaire : comparer gurant comme argument dans lappel de qsort, son prototype doit correspondre exactement47 a ` celui indiqu dans le prototype de qsort ; e a et b, les arguments formels de comparer, tant de type void *, on ne peut accder aux objets quils e e pointent sans leur donner auparavant (` laide de loprateur de conversion de type) un type pointeur a e  utilisable  ; dapr`s la documentation de qsort, nous savons que comparer sera appele avec deux adresses de cha e e nes pour arguments eectifs ; une cha tant de type char *, le type  adresse de cha  est char **, ce ne e ne qui explique la faon dont a et b apparaissent dans lappel de strcmp. c

6.3.3

Tableaux de fonctions

Le programme suivant montre lutilisation dun tableau dadresses de fonctions : chaque lment du tableau ee table est form de deux champs : e le nom dune fonction standard, sous forme de cha de caract`res ; ne e ladresse de la fonction correspondante. Notre programme lit des lignes constitues dun nom de fonction, suivi dun ou plusieurs blancs, suivis dun e nombre rel ; il value et ache alors la valeur de la fonction mentionne applique au nombre donn. La frappe e e e e e dune ligne rduite au texte n arrte le programme. e e #include <stdio.h> double sin(double), cos(double), exp(double), log(double); struct { char *nom; double (*fon)(double); } table[] = { "sin", sin, "cos", cos, "exp", exp, "log", log }; #define NBF (sizeof table / sizeof table[0]) main() { char nom[80]; double x; int i; for(;;) { scanf("%s", nom); if (strcmp(nom, "fin") == 0) break; scanf("%lf", &x); for(i = 0; i < NBF && strcmp(table[i].nom, nom) != 0; i++) ; if (i < NBF) printf("%f\n", (*table[i].fon)(x)); else printf("%s ???\n", nom); } } Voici une excution de ce programme : e
47 En syntaxe  sans prototype , aucun contrle nest fait sur le type des arguments de comparer, et toute cette question est bien o plus simple. Mais attention : quelle que soit la syntaxe employe, on ne peut pas ignorer le fait que comparer sera appele (par e e qsort) avec pour arguments eectifs les adresses des cha nes ` comparer. a

74

c H. Garreta, 2003

6 POINTEURS

6.4

Structures rcursives e

sin 1 0.841471 cos 1 0.540302 atn 1 atn ??? fin Remarque. La dclaration du tableau table prsente une caractristique peu frquente : des fonctions e e e e sont rfrences mais elles ne sont pas en mme temps appeles. En eet, les noms des fonctions sin, cos, ee e e e exp, etc. apparaissent dans cette dclaration sans tre accompagns dune paire de parenth`ses ; il ny a aucun e e e e signe indiquant au compilateur quil sagit de fonctions. Cest pourquoi ce programme sera trouv erron par le e e compilateur ` moins quon lui donne les dclarations externes des fonctions, soit explicitement : a e double sin(double), cos(double), exp(double), log(double); soit en incluant un chier dans lequel sont crites ces dclarations : e e #include <math.h> 6.3.4 Flou artistique

A ce point de notre expos vous tes en droit de trouver que les rles respectifs des objets de type  fonce e o tion...  et des objets de type  adresse dune fonction...  sont confus. Examinons une fonction bien ordinaire, sinus. La dclaration de cette fonction, extraite du chier <math.h>, est : e double sin(double); Dautre part, la dnition dune variable de type  adresse dune fonction double ` un argument double  est : e a double (*f)(double); Voici linitialisation de f par ladresse de la fonction sin et deux appels quivalents de cette fonction (x, y e et z sont des variables double) : f = sin; y = (*f)(x); z = sin(x); Quelle que soit la valeur de x, y et z reoivent la mme valeur. Pourtant, les trois lignes ci-dessus ne sont c e pas cohrentes. La premi`re montre que f et sin sont de mme type (` savoir  adresse dune fonction... ) ; or e e e a f doit tre drfrenc pour appeler la fonction, tandis que sin na pas besoin de ltre. e eee e e On peut comprendre la raison de cette incohrence. Apr`s avoir constat limpossibilit de dclarer des e e e e e variables de type  fonction...  , car une fonction na pas de taille dnie, et donc lobligation de passer par des e variables  adresse de fonction... , on a voulu faire cohabiter la rigueur (un pointeur doit tre drfrenc pour e eee e accder ` lobjet quil pointe) et la tradition (le sinus de x sest toujours crit sin(x)). Cela a conduit ` dnir e a e a e loprateur dappel de fonction aussi bien pour une expression de type  fonction...  que pour une expression e de type  adresse dune fonction... . Ainsi, les deux expressions (*f)(x) et sin(x) sont correctes ; lexprience e montre que cette tolrance na pas de consquences nfastes. Il nen dcoule pas moins que les deux expressions e e e e u = f(x); v = (*sin)(x); sont acceptes elles aussi (elles ont la mme valeur que les prcdentes). e e e e

6.4
6.4.1

Structures rcursives e
Dclaration e

Les structures rcursives font rfrence ` elles-mmes. Elles sont principalement utilises pour implanter des e ee a e e structures de donnes dont la dnition formelle est un nonc rcursif. Parmi les plus frquemment rencontres : e e e e e e e les listes et les arbres, comme les arbres binaires. Ainsi, par exemple, on peut donner la dnition rcursive suivante : un arbre binaire est e e soit un symbole conventionnel signiant  la structure vide  ; soit un triplet ( valeur , ls g , ls d ) constitu dune information dont la nature dpend du probl`me e e e tudi et de deux descendants qui sont des arbres binaires. e e
c H. Garreta, 2003

75

6.4 Structures rcursives e

POINTEURS

Bien entendu, du point de vue technique il nest pas possible de dclarer un champ dune structure comme e ayant le mme type que la structure elle-mme (une telle structure serait ipso facto de taille innie !). On e e contourne cette dicult en utilisant les pointeurs. Un arbre binaire est reprsent par une adresse, qui est : e e e soit ladresse nulle ; e soit ladresse dune structure constitue de trois champs : une information et deux descendants qui sont des adresses darbres binaires. Typiquement, la dclaration dune telle structure sera donc calque sur la suivante : e e 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 points par e les champs fils gauche et fils droit. 6.4.2 Exemple

Le programme suivant eectue le mme travail que celui du 6.2.5, mais ` la place dune table on utilise un e a arbre binaire de recherche (voir, par exemple, R. Sedgewick, Algorithmes en langage C, chapitre 14) pour ranger les mots. La structure de donnes mise en uvre est reprsente sur la gure 16. e e e

libre

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

...

...

...

...

Fig. 16 Arbre binaire de mots

#include <stdlib.h> #include <stdio.h> #define MAXCARS 3000 char espace[MAXCARS], *libre = espace; typedef struct noeud { char *mot; struct noeud *fils_gauche, *fils_droit; } NOEUD, *POINTEUR; POINTEUR alloc(char *m, POINTEUR g, POINTEUR d) { POINTEUR p = malloc(sizeof(NOEUD)); p->mot = m; p->fils_gauche = g; p->fils_droit = d; return p; } 76
c H. Garreta, 2003

...

6 POINTEURS

6.4

Structures rcursives e

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; } void parcours(POINTEUR r) { if (r != NULL) { parcours(r->fils_gauche); printf("%s\n", r->mot); parcours(r->fils_droit); } } main() { POINTEUR racine = NULL; char *p; p = gets(libre); while(*p != \0) { libre += strlen(p) + 1; racine = insertion(racine, p); p = gets(libre); } parcours(racine); } 6.4.3 Structures mutuellement rcursives e

Donnons-nous le probl`me suivant : reprsenter une  liste de familles  ; chaque famille est une  liste e e dindividus , avec la particularit que chaque individu fait rfrence ` sa famille. Les structures famille et e ee a individu se pointent mutuellement, chacune intervenant dans la dnition de lautre. Comment les dclarer ? e e

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

...

...

Fig. 17 Structures mutuellement rcursives e Pour rsoudre ce probl`me le compilateur C tol`re quon dclare un pointeur vers une structure non encore e e e e dnie. Plus prcisment, si la structure ayant le nom indiqu na pas encore t dclare, lexpression  struct e e e e ee e e nom  est lgitime. Elle identie un type incomplet et on peut lutiliser ` tout endroit o` sa taille nest pas e a u requise, notamment dans la dclaration dun pointeur vers une telle structure. Cela nous permet de dclarer nos e e structures mutuellement rcursives sous la forme : e
c H. Garreta, 2003

...

77

6.4 Structures rcursives e

POINTEURS

typedef struct famille { char *nom; struct individu *membres; struct famille *famille_suivante; } FAMILLE; typedef struct individu { char *prenom; struct individu *membre_suivant; struct famille *famille; } INDIVIDU; Une autre solution aurait t : ee typedef struct famille FAMILLE; typedef struct individu INDIVIDU; struct famille { char *nom; INDIVIDU *membres; FAMILLE *famille_suivante; }; struct individu { char *prenom; INDIVIDU *membre_suivant; FAMILLE *famille; };

78

c H. Garreta, 2003

7 ENTREES-SORTIES

Entres-sorties e

A strictement parler, les oprations dentre-sortie ne font pas partie du langage C ; elles sont eectues par e e e des fonctions de la biblioth`que que chaque syst`me ore avec le compilateur. Or, C est n et a vcu longtemps e e e e en milieu UNIX avant dtre transport ailleurs ; la biblioth`que UNIX a donc souvent t prise pour rfrence, e e e ee ee semant quelque confusion car la ralisation de certaines fonctions dUNIX est inutile, voire impossible sur e certains syst`mes. La norme ANSI y a mis bon ordre ; en principe la liste des fonctions standard est maintenant e clairement tablie et un programme qui nutilise que ces fonctions doit pouvoir tre transport dun syst`me e e e e a ` un autre sans modication. Rien nempche que telle ou telle ralisation particuli`re du langage propose des e e e fonctions additionnelles ; il faudra alors vous rfrer ` la documentation spcique. ee a e

7.1

Flots

Quelles que soient les particularits du syst`me dexploitation sous-jacent, les fonctions de la biblioth`que e e e standard des entres-sorties font en sorte que les chiers soient vus par le programmeur comme des ots, ceste a `-dire des suites doctets qui reprsentent des donnes selon une des deux modalits suivantes : e e e soit le chier contient les caract`res qui constituent lexpression crite des donnes en question dapr`s une e e e e certaine syntaxe. Ces caract`res sont eux-mmes reprsents selon une convention largement rpandue, e e e e e presque toujours le code ASCII. De tels chiers sont appels chiers de texte. Leur principale qualit est e e dtre exploitables par un logiciel, un syst`me ou un quipement dirents de celui qui les a produits ; en e e e e particulier, ils peuvent tre dits, imprims, etc. ; e e e e soit le chier contient des donnes enregistres sous une forme qui est la copie exacte de leur codage dans e e la mmoire de lordinateur. On lappelle alors chier binaire. Les oprations de lecture ou dcriture sur e e e de tels chiers peuvent tre tr`s rapides, car elles ne requi`rent pas un travail danalyse ou de synth`se de e e e e lexpression crite des donnes. En revanche, les chiers binaires ne sont pas ditables ou imprimables ; ils e e e ne sont pas censs tre transportables dun logiciel ` un autre, encore moins dun syst`me ` un autre. e e a e a Du point de vue du langage C (il nen est peut tre pas de mme pour le syst`me sous-jacent) tre de texte e e e e ou binaire nest pas un attribut dun chier, mais de la mani`re dont il est trait par un programme. Ainsi la e e distinction entre ces deux types de chiers ne se fait pas lors de la dclaration ou de louverture48 du chier, e mais par le choix des fonctions de lecture ou dcriture qui sont utilises : certaines fonctions (fgets et fputs, e e lecture et criture dune ligne, et fscanf et fprintf, lecture et criture de donnes formates) nont de sens que e e e e sur un ot de texte. Dautres fonctions (fread, fwrite) eectuent la lecture et lcriture de paquets doctets e sans interprtation aucune, et correspondent donc plutt aux ots binaires49 . e o Les ots binaires sont de simples suites doctets. Les ots de texte ont une structure lg`rement plus riche, e e puisquils sont censs tre organiss comme des suites de lignes. Chaque ligne est faite dun nombre quelconque e e e de caract`res distincts de \n et se termine par le caract`re \n. Mme si sur un syst`me particulier les chiers e e e e de texte ne sont pas ainsi organiss, ou si le caract`re qui indique la n dune ligne est dirent, la biblioth`que e e e e standard fera en sorte quils puissent tre vus de cette mani`re par le programmeur50 . e e Tampons. Un chier est dit tamponn 51 lorsque les transferts physiques doctets entre le chier et un e programme qui le lit ou lcrit se font par paquets de taille xe, la taille du tampon, mme si les changes e e e logiques sont faits par paquets de taille variable ou mme octet par octet. Par dfaut un ot est toujours associ e e e a ` un tampon de taille prdtermine, mais le programmeur peut, ` laide de la fonction setvbuf (cf. section e e e a 7.1.1), modier la taille de ce dernier ou mme le supprimer compl`tement. e e Ce mode de gestion des chiers rduit le nombre doprations physiques dentre - sortie (beaucoup plus e e e lentes que les transferts dinformation purement internes) mais introduit un dcalage dans leur chronologie. e Ce que le programmeur croit tre une opration dcriture dans un chier nest en ralit quune opration de e e e e e e recopie dans un tampon situ en mmoire, dont le contenu sera transfr ultrieurement vers le chier. Par e e ee e consquent, il est dicile ou impossible de savoir dans quel tat se trouve le chier ` un moment donn ; de e e a e plus, si le programme vient ` subir une terminaison anormale, une partie des informations logiquement crites a e
48 Il y a quand-mme un point de dtail pour lequel il faut indiquer, lors de louverture, si le chier est de texte ou non : y a-t-il e e lieu de guetter les marques de ns-de-ligne pour les transformer dans le caract`re \n et rciproquement ? e e 49 Consquence : sauf si le syst`me sous-jacent linterdit, un chier crit comme un ot de texte peut tre lu comme un ot binaire. e e e e Cela revient ` ignorer le fait que les octets dont il se compose reprsentent des donnes dun ordre suprieur. La dmarche inverse, a e e e e 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 dbordera, etc.). e 50 En particulier, le couple (CR , LF ) utilis par certains syst`mes, comme MS-DOS, sera traduit en lecture par lunique caract`re e e e \n. Inversement, sur de tels syst`mes, lcriture  logique  du caract`re \n se traduira par lcriture eective du couple (CR , e e e e LF ). 51 En bon franglais, on dit plutt  bueris . o e

c H. Garreta, 2003

79

7.1 Flots

7 ENTREES-SORTIES

dans le chier seront perdues, parce quelles nont jamais t physiquement transfres52 . ee ee 7.1.1 Fonctions gnrales sur les ots e e

Les ots sont reprsents dans les programmes par des variables de type FILE *. La structure FILE est e e dnie dans le chier <stdio.h>. Pour utiliser un ou plusieurs ots il faut donc crire en tte de son programme e e e la directive #include <stdio.h> puis une dclaration de la forme e FILE *chier ; Parmi les principales fonctions qui eectuent les oprations gnrales (ouverture, fermeture, etc.) sur les ots e e e nous avons : FILE *fopen(const char *nom, const char *mode) Cette fonction ouvre le chier dont le nom est indiqu par la cha nom et rend un pointeur sur le ot e ne correspondant, ou NULL si lopration a chou (chier absent, etc.). Le nom doit tre correct pour le e e e e syst`me dexploitation sous-jacent ; cela ne regarde pas le langage C. e Les valeurs permises pour mode sont : "r" (read ) ouverture dun chier. Le chier doit exister ; son contenu nest pas dtruit. Le descripteur du e ot cr est positionn en lecture et au dbut du chier. En principe, seules les oprations de lecture ee e e e sont permises sur ce ot. "r+" comme "r", mais les oprations dcriture sont permises aussi. e e "w" (write) cration dun chier. Le chier peut exister ou non ; sil existe, son contenu est enti`rement e e eac. Le descripteur du ot cr est positionn en criture et au dbut du chier (qui est vide). En e ee e e e principe, seules les oprations dcriture sont permises sur ce ot. e e "w+" comme w, mais les oprations de lecture sont permises aussi. e "a" (append) allongement dun chier. Le chier existe ou non ; sil existe, son contenu nest pas eac. Le e descripteur du ot cr est positionn en criture et ` la n du chier. Seules les oprations dcriture ee e e a e e sont permises. "a+" comme a, mais les oprations de lecture sont permises aussi. e Si lon envisage dutiliser le chier en mode binaire, il faut en principe ajouter la lettre b au mode ("r+b" ou "rb+", "w+b" ou "wb+", etc.). Il nest pas exclu que sur un syst`me dexploitation particulier cela nait e aucune utilit. e Remarque. Sur lintrt quil peut y avoir ` eectuer des lectures et des critures sur le mme chier, ee a e e voir les remarques faites ` la section 7.4.3 ` propos des chiers en acc`s relatif. a a e int fflush(FILE *flot) Cette fonction provoque lcriture physique immdiate du tampon en cours de composition pour le ot e e indiqu. Elle rend EOF en cas derreur, zro dans les autres cas. e e Remarque. En r`gle gnrale, si le chier physique qui correspond au ot indiqu est un organe interactif, e e e e par exemple si cest le clavier ou lcran dun poste de travail, alors la frappe par lutilisateur du caract`re e e de n de ligne ou lcriture de ce caract`re par un programme provoque galement le transfert eectif du e e e contenu du tampon. int fclose(FILE *flot) Cette fonction ferme le ot indiqu (criture physique des tampons, restitution de lespace allou pour le e e e tampon et les descripteurs du ot, etc.). Elle rend zro en cas de succ`s, une autre valeur en cas derreur. e e Un appel comme fclose(flot) rend la valeur de la variable flot illgitime. e Oublier de fermer, par un appel de fclose, un chier qui a t ouvert en lecture na en gnral aucune ee e e consquence. Mais oublier de fermer un chier qui a t ouvert en criture (cest-`-dire un chier qui vient e ee e a dtre cr) peut tre fatal pour linformation qui y a t crite, car e ee e eee
52 Sil existe une probabilit non ngligeable pour quun programme ait une n anormale (par exemple ` cause de conditions e e a dexploitation diciles) il est prudent de programmer des appels rguliers de la fonction ush (cf. section 7.1.1). e

80

c H. Garreta, 2003

7 ENTREES-SORTIES

7.1 Flots

le contenu du tampon dcriture en cours de formation lors de la terminaison du programme ne sera e probablement pas sauvegard dans le chier ; e dans certains syst`mes les chiers nouvellement crs ne subissent les oprations qui les rendent connus e ee e du syst`me dexploitation (comme linclusion de leur nom dans un rpertoire) quau moment de leur e e fermeture. Un chier qui na jamais t ferm a donc une existence tr`s prcaire. ee e e e FILE *tmpfile(void) Cette fonction cre un chier temporaire en mode "w+b", sans nom externe, qui sera automatiquement e dtruit ` la terminaison du programme. Elle renvoie le ot associ ou NULL en cas derreur. e a e int setvbuf(FILE *flot, char *tampon, int mode, size t taille) Cette fonction rednit le tampon associ ` un flot ouvert. Elle doit tre appele apr`s louverture du e ea e e e ot mais avant toute lecture ou criture. Largument mode doit tre une des trois constantes suivantes, e e dnies dans <stdio.h> : e IOFBF Tampon de taille xe. Lcriture ou la lecture physique a lieu lorsque le tampon contient taille e caract`res. e IOLBF Tampon gal ` une ligne. Lcriture ou la lecture physique a lieu lors de la rencontre du caract`re e a e e \n qui dtermine la n des lignes. e IONBF Pas de tampon. Une criture ou une lecture physique a lieu ` chaque lecture ou criture logique. e a e Largument taille indique la taille voulue pour le tampon. Largument tampon, sil nest pas NULL, est cens pointer un espace (de taille au moins gale ` taille) qui sera utilis comme tampon. Si tampon est e e a e NULL, alors la fonction alloue un espace propre. int feof(FILE *flot) Cette fonction renvoie une valeur non nulle si et seulement si lindicateur de n de chier du flot indiqu e est  vrai . int ferror(FILE *flot) Cette fonction renvoie une valeur non nulle si et seulement si lindicateur derreur du flot indiqu est e  vrai . Elle doit tre appele immdiatement apr`s une entre-sortie sur ce ot pour savoir si lopration e e e e e e en question a chou. La variable globale enti`re errno (dclare dans <errno.h>) contient alors un code e e e e e renseignant sur la nature de lerreur. 7.1.2 Les units standard dentre-sortie e e

Sans que le programmeur ait ` prendre aucune disposition particuli`re, lexcution dun programme coma e e mence avec trois ots de texte automatiquement ouverts par le syst`me. Ils sont connects (on dit aects) aux e e e organes dentre-sortie les plus  naturels  par rapport ` la mani`re dont on utilise lordinateur. Ces chiers e a e sont dclars dans <stdio.h>, de la mani`re suivante : e e e FILE *stdin, *stdout, *stderr; stdin est lunit standard dentre. Elle est habituellement aecte au clavier du poste de travail. e e e stdout est lunit standard de sortie. Elle est habituellement aecte ` lcran du poste de travail. e e a e stderr est lunit standard dachage des erreurs. Elle est aussi aecte ` lcran du poste de travail. e e a e Les fonctions de lecture (resp. dcriture) qui ne spcient pas un autre ot utilisent stdin (resp. stdout). e e Remarque. Lutilisation des units standard appara encore plus intressante lorsque lon sait que dans e t e UNIX et plusieurs autres syst`mes dexploitation il est permis de raecter les units standard lors dune e e e excution du programme, sans quil faille recompiler ce dernier. En UNIX et MS-DOS cela fonctionne de la e mani`re suivante : supposons que prog1 soit un programme qui nutilise que les units standard stdin et e e stdout. Activ par la commande prog1, il lira ses donnes au clavier et achera ses rsultats ` lcran. Mais e e e a e sil est lanc par la commande e prog1 < fichier1 (resp. : prog1 > fichier2) alors le chier fichier1 (resp. fichier2) remplacera le clavier (resp. lcran). De mani`re similaire, supposons e e que prog2 soit un autre programme qui fait ses entres-sorties de la mme mani`re. Alors la commande e e e prog1 | prog2 active en parall`le ces deux programmes avec la sortie standard de prog1 connecte ` lentre standard de prog2 e e a e (les sorties de prog1 sont lues et exploites par prog2 au fur et ` mesure quelles sont produites par prog1). e a Bien entendu, tout cela se combine. Par exemple, la commande
c H. Garreta, 2003

81

7.2 Lecture et criture textuelles e

ENTREES-SORTIES

prog1 < fichier1 | prog2 > fichier2 active prog1 et prog2. Les donnes de prog1 sont lues dans fichier1, ses rsultats sont fournis ` titre de e e a donnes ` prog2, dont les rsultats sont crits dans fichier2. e a e e

7.2
7.2.1

Lecture et criture textuelles e


Lecture et criture de caract`res et de cha e e nes

Les fonctions de lecture-criture les plus simples eectuent la lecture ou lcriture dun caract`re isol ou e e e e bien la lecture ou lcriture dune ligne. Pour la lecture nous avons : e int fgetc(FILE *flot) Renvoie le caract`re suivant sur le ot indiqu, ou EOF si la n du chier est atteinte ou si une erreur e e survient. Cest une vraie fonction. int getc(FILE *flot) Fait la mme chose que fgetc mais cest peut tre macro. e e Lintrt dutiliser une macro rside dans lecacit suprieure des (petites) macros par rapport aux ee e e e (petites) fonctions53 . Il ne faut pas oublier cependant quil y a deux cas o` les macros ne peuvent pas tre u e utilises : e lorsque largument a un eet de bord. Une expression comme *s++ = fgetc(table_de_fichiers[i++]) est correcte (mais peu utilise), tandis que e *s++ = getc(table_de_fichiers[i++]) est certainement errone, car ayant un eet indni (on ne peut pas dire combien de fois i sera e e incrment) ; e e lorsquil faut obtenir ladresse de lopration en question, pour la passer comme argument dune autre e fonction. Par exemple, si getc est une macro, un appel comme appliquer(getc, fichier) sera certainement trouv incorrect par le compilateur. e int getchar(void) getchar() quivaut ` getc(stdin) ; e a char *fgets(char *s, int n, FILE *flot) Lecture dune ligne en veillant ` ne pas dborder. Plus prcisment, cette fonction lit des caract`res sur a e e e e le flot indiqu et les place dans lespace point par s. Elle sarrte lorsquelle a lu n1 caract`res ou e e e e lorsquelle a rencontr un caract`re \n de n de ligne (ce caract`re est copi dans le tableau). Un e e e e caract`re \0 est ajout ` la n du tableau. La fonction rend s, ou NULL si une erreur ou la n du chier e ea a t rencontre. ee e Lisez aussi la remarque faite ci-apr`s ` propos de la fonction gets. e a char *gets(char *s) Lecture dune ligne sur le ot stdin. Les caract`res lus sont rangs dans lespace point par s. Elle renvoie e e e le mme rsultat que fgets mais, contrairement ` cette derni`re e e a e il ny a pas de test de dbordement54 ; e e e le caract`re de n de ligne est remplac par \0. Atention. Largument de gets doit tre ladresse dun espace disponible (accessible en criture) et de e e taille susante, dans lequel gets puisse ranger les caract`res qui seront lus. e Exemples. Pour illustrer ces fonctions, voici deux exercices dcole. Dune part, lcriture dune fonction e e analogue ` gets en utilisant getchar : a char *myGets(char *s) { int c, i = 0; c = getchar();
53 Il sagit dentres-sorties tamponnes, cest-`-dire de simples transferts entre mmoires. Sil stait agi doprations physiques, le e e a e e e gain de temps obtenu en utilisant getc ` la place de fgetc aurait t ngligeable devant le temps requis par lentre-sortie elle-mme. a e e e e e 54 Ainsi, gets lit des caract`res jusqu` la rencontre de \n, sans se proccuper de savoir si lespace dans lequel ces caract`res e a e e sont rangs est de taille susante pour les recevoir ; autrement dit, tout appel de gets peut entra e ner un dbordement de mmoire. e e Cest la raison pour laquelle on dit que tout programme comportant ne serait-ce quun appel de gets est bogu. e

82

c H. Garreta, 2003

7 ENTREES-SORTIES

7.2

Lecture et criture textuelles e

while (c != \n) { if (c == EOF) return NULL; s[i++] = c; c = getchar(); } s[i] = \0; return s; } Dautre part, lcriture dune fonction analogue ` getchar (version fonction) en utilisant gets : e a 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 dcriture : e int fputc(int caractere, FILE *flot) Ecrit le caractere indiqu sur le flot indiqu. Renvoie le caract`re crit ou EOF si une erreur survient. e e e e Cest une vraie fonction. int putc(int caractere, FILE *flot) Fait la mme chose que fputc mais peut tre une macro. Voir ` ce propos les remarques faites ` loccasion e e a a de lintroduction de getc. int putchar(int caractere) putchar(c) quivaut ` putc(c, stdout). e a int fputs(const char *s, FILE *flot) Ecrit la cha s sur le flot indiqu. La fonction renvoie EOF en cas derreur, une valeur 0 dans les ne e autres cas. int puts(const char *s) Cette fonction crit la cha s sur le ot stdout. Elle renvoie le mme rsultat que fputs. Contrairement e ne e e a ` fputs un caract`re \n est ajout ` la suite de la cha e ea ne. int ungetc(int c, FILE *flot)  Dlecture  du caract`re c. Cette fonction remet le caract`re c dans le flot indiqu, o` il sera trouv e e e e u e lors de la prochaine lecture. Sur chaque ot, seule la place pour un caract`re  dlu  est garantie. On ne e e peut pas  dlire  le caract`re EOF. e e Cette opration incarne donc lide de restitution (` lunit dentre, qui en est le fournisseur) dun e e a e e caract`re lu ` tort. Cest une mani`re de rsoudre un probl`me que pose lcriture danalyseurs lexicaux, e a e e e e o` la n de certaines units lexicales est indique par un caract`re qui les suit sans en faire partie. Exemple : u e e e la lecture dun nombre entier non ngatif : e 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`re suivant le nombre */ e ungetc(c, flot); return x; }
c H. Garreta, 2003

83

7.2 Lecture et criture textuelles e

ENTREES-SORTIES

7.2.2

Ecriture avec format printf

La fonction dcriture sur lunit de sortie standard (en principe lcran du poste de travail) avec conversion e e e et mise en forme des donnes est : e printf( format , expr 1 , expr 2 , ... expr n ) format est une cha qui joue un rle particulier. Elle est forme de caract`res qui seront crits normalement, ne o e e e mlangs ` des indications sur les types des expressions expr 1 , expr 2 , ... expr n et sur laspect sous lequel il faut e e a crire les valeurs de ces expressions. e Cela fonctionne de la mani`re suivante : la fonction printf parcourt la cha format et reconna certains e ne t groupes de caract`res comme tant des spcications de format. Cest le caract`re % qui dclenche cette ree e e e e connaissance. La fonction printf recopie sur le ot de sortie tout caract`re qui ne fait pas partie dune telle e spcication. La ieme spcication dtermine la mani`re dont la valeur de expr i sera crite. e e e e e Les spcications de format reconnues par la fonction printf sont expliques dans la table 4. e e Notez que seuls les lments 1 (le caract`re %) et 6 (la lettre qui indique le type de la conversion) sont ee e obligatoires dans une spcication de format. e Exemple 1. Supposons quon ait donn la dclaration-initialisation e e 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 prcision pour les cha e nes. Supposons avoir donn les dclarations : e e 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 tre indique par le caract`re * ` la place dun nombre. Elle e e e a est alors dnie par la valeur de largument dont cest le tour, qui doit tre de type entier. Il en est de mme e e e pour la spcication de la prcision (cf. tableau 4, n 4). Par exemple, i et j tant des variables enti`res et x e e e e une variable ottante, lexpression printf("%*.*f", i, j, x); crit la valeur de x sur un nombre de caract`res gal ` la valeur de i, avec un nombre de chires apr`s la e e e a e virgule gal ` la valeur de j. e a

84

c H. Garreta, 2003

7 ENTREES-SORTIES

7.2

Lecture et criture textuelles e

Chaque spcication de format se compose, dans lordre indiqu, des lments suivants : e e ee 1. Obligatoirement, le caract`re %. e 2. Facultativement, un ou plusieurs modieurs, dans un ordre quelconque, parmi : - : cadrer ` gauche du champ (par dfaut le cadrage se fait ` droite) ; a e a + : imprimer le signe du nombre mme lorsque celui-ci est positif ; e espace : si le premier caract`re nest pas un signe, placer un espace au dbut dun nombre ; e e 0 : complter les nombres par des zros (` la place de blancs) ; e e a # : 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 augmente si elle se rv`le insusante. e e e 4. Facultativement, un point suivi dun nombre qui indique la prcision : e dans les conversions e, E ou f : le nombre de chires ` crire apr`s la virgule dcimale ; ae e e dans les conversions g ou G : le nombre de chires signicatifs ; pour une cha : le nombre maximum de caract`res ` acher ; ne e a pour un entier (conversions d et u) : le nombre minimum de chires. Le cas chant, des zros seront e e e ajouts ` gauche. e a 5. Facultativement, une lettre qui compl`te linformation sur la nature de largument et, le cas chant, modie e e e en consquence la largeur du champ : e h (associe ` d ou u) : largument est un short. Cette indication est sans eet si un int et un short sont e a la mme chose ; e l (associe ` d, u, x ou X) : largument est un long ou un unsigned long. Cette indication est sans eet e a si un int et un long sont la mme chose ; e L (associe ` f, e, E, g ou G) : largument est un long double. e a 6. Obligatoirement, un caract`re de conversion, parmi e d : argument : int ; impression : notation dcimale signe. e e o : argument : int ; impression : notation octale non signe. e Format alternatif (voir # au point 2) : un 0 est imprim au dbut du nombre. e e x : argument : int ; impression : notation hexadcimale non signe avec les chires 0...9abcdef. e e Format alternatif : le nombre est prcd de 0x. e e e X : comme x mais avec ABCDEF et 0X ` la place de abcdef et 0x. a u : argument : int ; impression : notation dcimale non signe. e e c : argument : unsigned char ; impression : un seul caract`re. e s : argument : char * ; impression : cha de caract`res. ne e f : argument : double ; impression : notation en  virgule xe  [-]zzz.f. Le nombre de f est donn par e la prcision. Prcision par dfaut : 6. Si la prcision vaut zro, le point est supprim. e e e e e 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 par la prcision. Prcision par dfaut : 6. Si la prcision vaut zro, le point est supprim. e e e e e e e Format alternatif : le point est toujours imprim. e E : comme %e avec E ` la place de e. a g : argument : double ; impression : comme %e si lexposant est infrieur ` -4 ou suprieur ` la prcision, e a e a e comme %f sinon. Format alternatif : le point est toujours imprim. e G : comme %g avec E ` la place de e. a p : argument : void * ; impression : dpend de limplantation (par exemple %X). e n : argument : int *. Aucune impression. Eet : largument correspondant doit tre ladresse dune e variable enti`re, qui recevra pour valeur le nombre de caract`res eectivement crits jusqu` prsent e e e a e par lappel en cours de la fonction printf. % : pas dargument correspondant ; impression : le caract`re %. e Tab. 4 Spcications de format pour printf, fprintf et sprintf e

c H. Garreta, 2003

85

7.2 Lecture et criture textuelles e

ENTREES-SORTIES

7.2.3

Lecture avec format scanf

La fonction de lecture avec format ` lunit dentre standard (en principe le clavier du poste de travail) est a e e scanf( format , adresse 1 , adresse 2 , ... adresse n ) Largument format est une cha qui indique la mani`re de convertir les caract`res qui seront lus ; adresse 1 , ne e e adresse 2 , ... adresse n indiquent les variables devant recevoir les donnes lues. Ce sont des  sorties  de la e fonction scanf, par consquent il est essentiel que ces arguments soient des adresses de variables ` lire. e a La fonction scanf constituant un vrai analyseur lexical, les donnes lues ont de nombreuses occasions dtre e e incorrectes et on peut sattendre ` ce que scanf soit dun maniement dicile. Lexprience ne dcoit pas cette a e e attente ! Voici un bon conseil : si un programme ne fonctionne pas comme il le devrait, commencez par vrier e les valeurs lues par la fonction scanf (par exemple avec des appels de printf suivant immdiatement les appels e de scanf). 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`re ordinaire du format, cest-`-dire qui nest ni un caract`re despacement (blanc, tabulation) e a e ni un caract`re faisant partie dune spcication de format (commenant par %), doit sidentier au cae e c ract`re courant du ot dentre. Si cette identication a lieu, le caract`re courant est lu, sans tre rang e e e e e dans aucune variable, et le parcours du format se poursuit. Si lidentication na pas lieu, lactivation de scanf se termine. Les spcications de format commencent par %. Elles indiquent la mani`re danalyser les caract`res lus e e e sur le ot dentre et de ranger les valeurs ainsi obtenues. Voir le tableau 5. e Dans le format, les caract`res ordinaires et les spcications peuvent tre spars entre eux par des e e e e e caract`res despacement. Le nombre de ces espacements est sans importance, mais leur prsence indique e e que les donnes correspondantes peuvent tre spares dans le ot dentre par un nombre quelconque de e e e e e caract`res despacement ou de n de ligne. e Sil ny a pas despacement, dans le format, entre les caract`res ordinaires ou les spcications, alors les e e donnes correspondantes dans le ot dentre doivent tre adjacentes. e e e Cependant, les espacements au dbut des nombres sont toujours sauts. e e On peut mlanger des appels de scanf et dautres fonctions de lecture. Chaque appel dune telle fonction e commence par lire le premier caract`re que lappel prcdent na pas  consomm . e e e e Exemple 1. Lecture dun entier : scanf("%d", &x); Exemple 2. Lecture de deux entiers spars par une virgule et encadrs par une paire de parenth`ses : e e e e scanf("(%d,%d)", &x, &y); Donnes correctes : e (22,33) ( 22 , ou 33 ) ( 22, 33) (des blancs, mais uniquement devant les nombres). Donnes incorrectes : e (trop de blancs). Exemple 3. Comme ci-dessus mais avec une syntaxe plus souple (les blancs sont tolrs partout) : ee scanf(" ( 22 ; (%d 33 ,%d ) )", &x, &y); Toutes les donnes de lexemple prcdent sont correctes. Exemple de donne incorrecte : e e e e Exemple 4. Lecture dun caract`re55 : e char calu; ... scanf("%c", &calu); Remarque. Nous avons indiqu par ailleurs quen C on range souvent les caract`res dans des variables de e e type int an de permettre la mmorisation dune valeur  intruse , EOF. Cependant, notez bien quici nous e sommes obligs de dclarer la variable calu de type char ; le programme e e int calu; ... scanf("%c", &calu);
55 Rappelons

que fgetc, getc et getchar font ce travail bien plus simplement.

86

c H. Garreta, 2003

7 ENTREES-SORTIES

7.2

Lecture et criture textuelles e

Chaque spcication de format se compose, dans lordre indiqu, des lments suivants : e e ee 1. Obligatoirement, le caract`re %. e 2. Facultativement, le caract`re * qui indique une suppression daectation : lire une donne comme indiqu et e e e l oublier  (cest-`-dire ne pas le ranger dans une variable). a 3. Facultativement, un nombre donnant la largeur maximum du champ (utile, par exemple, pour la lecture de nombres colls les uns aux autres). e 4. Facultativement, une lettre qui apporte des informations complmentaires sur la nature de largument core respondant : 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`re de conversion parmi e d Argument : int *. Donne : nombre entier en notation dcimale. e e i Argument : int *. Donne : entier en notation dcimale, octale (prcd de 0) ou hexadcimale e e e e e e (prcd de 0x ou 0X). e e e o Argument : int *. Donne : entier en octal (prcd ou non de 0). e e e e u Argument : unsigned int *. Donne : entier en notation dcimale. e e x Argument : int *. Donne : entier en notation hexadcimale (prcd ou non de 0x ou 0X). e e e e e c Argument : char *. Donne : autant de caract`res que la largeur du champ lindique (par dfaut : e e e 1). Ces caract`res sont copis dans lespace dont ladresse est donne en argument. Les blancs et e e e tabulations ne sont pas pris pour des sparateurs (ils sont copis comme les autres caract`res) et il e e e ny a pas de \0 automatiquement ajout ` la n de la cha ea ne. s Argument : char *. Donne : cha de caract`res termine par un caract`re despacement (blanc, e ne e e e tabulation, n de ligne) qui nen fait pas partie. Un \0 sera automatiquement ajout apr`s les e e caract`res lus. e e Argument : float *. Donne : nombre ottant, cest-`-dire dans lordre : signe facultatif, suite de e a chires, point dcimal et suite de chires facultatifs, exposant (e ou E, signe facultatif et suite de e chires) facultatif. f Comme e. g Comme e. p Argument : void ** (ou void *, pour ce que a change...). Donne : nombre exprimant un pointeur c e comme il serait imprim par printf avec le format %p. e n Argument : int *. Aucune lecture. Eet : largument correspondant doit tre ladresse dune variable e enti`re ; celle-ci reoit pour valeur le nombre de caract`res eectivement lus jusqu` ce point par le e c e a prsent appel de la fonction scanf. e [caract`re...caract`re] Argument : char *. Donne : la plus longue suite faite de caract`res appartenant e e e e ` lensemblea indiqu entre crochets. Un caract`re au moins sera lu. Un \0 est ajout ` la n de a e e ea la cha lue. ne [^caract`re...caract`re] Argument : char *. Donne : comme ci-dessus mais les caract`res permis sont e e e e maintenant ceux qui nappartiennent pas ` lensemble indiqu. a e % Pas dargument. Le caract`re % doit se prsenter sur le ot dentre. e e e
a Pour

spcier le caract`re ] lcrire immdiatement apr`s le [ initial. e e e e e

Tab. 5 Spcications de format pour scanf, fscanf et sscanf e

aurait t incorrect (bien quil puisse fonctionner correctement sur certains syst`mes), car on y fournit ` scanf ee e a ladresse dun entier (fait de plusieurs octets) pour y ranger un caract`re, alors que scanf, dapr`s lanalyse du e e format,  croit  recevoir ladresse dun char (un octet). Or on ne doit pas faire dhypoth`se sur la mani`re dont e e sont organiss les octets qui composent un entier, le langage C ne prcisant rien ` ce sujet. Une autre solution e e a correcte aurait t la suivante : ee
c H. Garreta, 2003

87

7.2 Lecture et criture textuelles e

ENTREES-SORTIES

int calu; char tmp; ... scanf("%c", &tmp); calu = tmp; Exemple 5. Lecture dun nombre entier et du caract`re le suivant immdiatement, quel quil soit : e e int nblu; char calu; ... scanf("%d%c", &nblu, &calu); Exemple 6. Lecture dun nombre entier et du premier caract`re non blanc le suivant : e 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 spcications qui constituent le format e peuvent tre spares entre elles par un ou plusieurs blancs, ce qui indique que les donnes ` lire correspondantes e e e e a peuvent alors tre spares par un nombre quelconque de blancs. e e e Pour la fonction scanf, les blancs dans le format se traduisent donc par lordre  ` prsent, sautez tous a e les caract`res blancs qui se prsentent . Or la seule mani`re pour lordinateur de sassurer quil a bien saut e e e e tous les caract`res blancs consiste ` lire (sans le consommer) le premier caract`re non blanc qui les suit. Si la e a e lecture se fait dans un chier, cela ne pose pas de probl`me, les caract`res ` lire existant tous d`s le dpart. e e a e e Mais cela peut crer des situations confuses dans le cas dune lecture interactive mettant en uvre le clavier et e un oprateur vivant. Exemple : e int x, y = 33; ... printf("donne x : "); scanf(" %d ", &x); /* ae... */ printf("donne y : "); scanf("%d", &y); printf("x = %d, y = %d\n", x, y); Parce quon a crit, peut-tre sans faire attention, des blancs apr`s le format %d, le premier appel de scanf e e e ci-dessus signie  lisez un entier et tous les blancs qui le suivent . Loprateur constatera avec tonnement que e e pour que son premier nombre sont pris en compte il est oblig de composer le deuxi`me ! Exemple de dialogue e e (le signe reprsentant la frappe de la touche  Entre ) : e e donne x : 11 la machine ne ragit pas... ! ? e 22 donne y : x = 11, y = 22 Notez que faire suivre le premier nombre dun caract`re non blanc conventionnel nest pas une bonne solution e car, ce caract`re ntant pas consomm, il fait chouer la lecture du second nombre : e e e e donne x : 11 donne y : x = 11, y = 33 Leon ` retenir : dans le format de scanf, les blancs ne sont pas de la dcoration, ils jouent un rle bien c a e o prcis. e ` A propos de la lecture dun caractere. Une autre situation gnratrice de confusion est celle o` des e e u lectures de nombres sont suivies par des lectures de caract`res ou de cha e nes. Par exemple, le programme na f suivant lit des nombres les uns ` la suite des autres et ache le carr de chacun : a e 88
c H. Garreta, 2003

7 ENTREES-SORTIES

7.2

Lecture et criture textuelles e

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`re e } while (c == O); printf("au revoir...");\end{verbatim} Voici un dialogue (la rplique de loprateur est souligne, le signe reprsente la frappe de la touche  Entre ) : e e e e e donne un nombre : 14 14 ^ 2 = 196 un autre calcul (O/N) ? au revoir... La machine na pas attendu la rponse (O ou N) de loprateur... ! Que sest-il pass ? Loprateur a compos e e e e e un nombre suivi immdiatement dun caract`re  retour-chariot  (produit par la touche  Entre ). Ce retoure e e chariot a indiqu la n de la lecture du nombre, mais il na pas t consomm et est rest disponible pour e ee e e des lectures ultrieures. Lappel de getchar (qui, dans lesprit du programmeur, aurait d provoquer une e u nouvelle lecture physique) trouve ce retour-chariot et laecte ` c ` titre de rponse. On voit que ce programme a a e ne sexcutera correctement que si loprateur compose, ` la suite de linvite donne un nombre, un nombre e e a immdiatement suivi dun caract`re O ou N en rponse ` une question qui ne lui a pas encore t pose ! e e e a ee e La solution de ce probl`me consiste ` vider le tampon dentre avant de procder ` une lecture de caract`re. e a e e a e Dans beaucoup de situations ltat du tampon est connu, et une mani`re simple de le vider consiste ` le lire par e e a gets : int x, c; char s[BUFSIZ]; ... do { printf("donne un nombre : "); scanf("%d", &x); ici, le tampon contient (au moins) un \n gets(s); maintenant le tampon est vide printf("%d ^ 2 = %d\n", x, x * x); printf("un autre calcul (O/N) ? "); c = getchar(); } while (c == O); printf("au revoir..."); 7.2.5 Les variantes de printf et scanf

Voici la famille au complet, comme elle est dclare (en syntaxe ANSI) dans le chier <stdio.h> : e e int printf(const char *format, ... ) Nous avons dcrit cette fonction. Ajoutons seulement quelle renvoie le nombre de caract`res eectivement e e crits (on utilise rarement cette information) ou un nombre ngatif en cas derreur. e e int fprintf(FILE *flot, const char *format, ... ) Comme printf mais sur le ot indiqu (` la place de stdout). Ainsi e a printf( format , exp 1 , ... , exp k ) quivaut ` e a fprintf(stdout, format , exp 1 , ... , exp k ) int sprintf(char *destination, const char *format, ... ) Cette fonction eectue les mmes mises en forme que ses deux surs, avec la dirence que les caract`res e e e produits ne sont pas ajouts ` un ot, mais sont concatns ensemble dans la cha destination. Elle e a e e ne
c H. Garreta, 2003

89

7.3 Oprations en mode binaire e

ENTREES-SORTIES

permet donc de dissocier les deux fonctionnalits de printf, transcodage et criture des donnes, an e e e dutiliser la premi`re sans la deuxi`me. e e int scanf(const char *format, ... ) Nous avons dcrit cette fonction. Ajoutons quelle renvoie EOF si une n de chier ou une erreur a empch e e e toute lecture ; autrement elle rend le nombre de variables lues avec succ`s. Cette indication fournit une e mani`re bien pratique pour dtecter, dans les programmes simples, la n dune srie de donnes. Exemple : e e e e for(;;) { printf("donne un nombre : "); if (scanf("%d", &x) < 1) break; exploitation du nombre lu } printf("au revoir..."); La boucle prcdente sera arrte par nimporte quel caract`re non blanc ne pouvant pas faire partie dun e e ee e nombre. int fscanf(FILE *flot, const char *format, ... ) Comme scanf mais sur le ot indiqu (` la place de stdin). Ainsi e a scanf( format , exp 1 , ... , exp k ) quivaut ` e a fscanf(stdin, format , exp 1 , ... , exp k ) int sscanf(const char *source, const char *format, ... ) Cette fonction eectue la mme analyse lexicale que ses surs, avec la particularit que le texte analys e e e nest pas pris dans un ot, mais dans la cha source. ne

7.3
7.3.1

Oprations en mode binaire e


Lecture-criture e

size t fread(void *destination, size t taille, size t nombre, FILE *flot) Cette fonction essaye de lire sur le flot indiqu nombre objets, chacun ayant la taille indique, et les e e copie les uns ` la suite des autres dans lespace point par destination. Elle renvoie le nombre dobjets a e eectivement lus, qui peut tre infrieur au nombre demand, ` cause de la rencontre de la n du chier56 , e e e a dune erreur, etc. size t fwrite(const void *source, size t taille, size t nombre, FILE *flot) Cette fonction crit les nombre objets, chacun ayant la taille indique, qui se trouvent les uns ` la suite e e a des autres ` ladresse indique par source. Elle renvoie le nombre dobjets crits, qui peut tre infrieur a e e e e au nombre demand (en cas derreur). e 7.3.2 Positionnement dans les chiers

int fseek(FILE *flot, long deplacement, int origine) Cette fonction positionne le pointeur du chier associ au flot indiqu. La premi`re lecture ou criture e e e e ultrieure se fera ` partir de la nouvelle position. Celle-ci est obtenue en ajoutant la valeur de dplacement e a e a ` une valeur de base qui peut tre la position courante, la n ou le dbut du chier. Cest largument e e origine qui indique de quelle base il sagit ; la valeur de cet argument doit tre une des constantes (dnies e e dans <stdio.h>) : e SEEK SET : base = le dbut du chier SEEK CUR : base = la position courante SEEK END : base = la n du chier La fonction renvoie zro en cas de succ`s, une valeur non nulle en cas derreur. Sur un chier de texte, il e e vaut mieux utiliser getpos et setpos. Un appel de cette fonction sur un chier en criture produit la vidange du tampon (fseek implique e fflush). Lire ci-apr`s les explications sur lacc`s relatif aux chiers. e e
56 Lorsquun chier est lu par des appels de la fonction fread, comparer avec zro le nombre dobjets eectivement lus est la e mani`re la plus pratique de dtecter la n du chier. e e

90

c H. Garreta, 2003

7 ENTREES-SORTIES

7.3

Oprations en mode binaire e

void rewind(FILE *flot) Positionnement au dbut du chier du pointeur de chier associ au ot indiqu. Un appel de cette fonction e e e quivaut ` fseek(flot, 0L, SEEK SET) suivi de la mise ` zro de tous les indicateurs derreur. e a a e void fgetpos(FILE *flot, fpos t *ptr) Place dans ptr la position courante dans le flot indiqu en vue de son utilisation par setpos. Le type e fpos t (dni dans <stdio.h>) a une taille susante pour contenir une telle information. e void fsetpos(FILE *flot, const fpos t *ptr) La valeur de ptr provenant dun appel de getpos, cette fonction positionne le ot indiqu ` lemplacement ea correspondant. ` Lacces relatif aux elements des fichiers. La principale application des fonctions de positionnement est la ralisation de ce quon appelle lacc`s relatif 57 ou parfois acc`s direct aux composantes dun chier : e e e lacc`s ` une composante (on dit un enregistrement) que ce soit pour le lire ou pour lcrire, ` partir de la e a e a donne de son rang dans le chier sans tre oblig daccder au pralable aux enregistrements qui le prc`dent. e e e e e e e Pour donner une signication utilisable ` la notion de  neme article , il faut que le chier puisse tre vu a e comme une suite darticles de mme taille (mais la biblioth`que C ignorera ce fait ; le chier restera une suite e e doctets). Cela sobtient gnralement en dclarant une structure qui reprsente un article, selon le mod`le : e e e e e typedef struct { ... champs dont se composent tous les articles ... } ARTICLE;... Un chier destin ` tre utilis en acc`s relatif sera dclar normalement, et ouvert de faon ` autoriser les eae e e e e c a lectures et les critures : e FILE *fichier; ... fichier = fopen(nom , "rb+"); Si les articles sont numrots ` partir de zro, lopration  lecture de lenregistrement de rang n  se e e a e e programme alors selon le schma suivant : e ARTICLE article; ... fseek(fichier, n * sizeof(ARTICLE), SEEK_SET); fread( & article, sizeof(ARTICLE), 1, fichier); ... exploitation des valeurs des champs de article ... Lopration  criture de la composante de rang n  obit au schma e e e e ARTICLE article; ... aectation des champs de article ... fseek(fichier, n * sizeof(ARTICLE), SEEK_SET); fwrite( & article, sizeof(ARTICLE), 1, fichier); ... Attention. La possibilit deectuer des lectures et des critures sur le mme chier pose des probl`mes e e e e dans la gestion des tampons associs au chier que les fonctions de la biblioth`que standard ne rsolvent pas. e e e Ainsi chaque fois quune srie de lectures sur un chier va tre suivie dune srie dcritures sur le mme chier, e e e e e ou rciproquement, il appartient au programmeur de prvoir lcriture eective du tampon, soit par un appel e e e 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`s relatifs selon les schmas indiqus ci-dessus, puisquil y a e e e toujours un appel de fseek entre deux acc`s au chier, quel que soit leur mode. e
57 Lacc`s relatif soppose ` lacc`s squentiel, dans lequel une composante ne peut tre lue ou crite autrement qu` la suite de e a e e e e a la lecture ou de lcriture de la composante qui la prc`de. e e e

c H. Garreta, 2003

91

7.4 Exemples

ENTREES-SORTIES

7.4

Exemples

Les programmes suivants sont assez na ; ils se proposent dillustrer les fonctions de traitement de chiers, fs 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, gnralement e e une fonction du syst`me dexploitation fait ce travail) : e #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 crire les nombre octets eectivement lus. Supposons que le e N e a chier ` copier comporte N octets ; ces deux oprations seront excutes 512 fois avec nombre gal ` 512 puis a e e e (sauf si N est multiple de 512) une derni`re fois avec nombre gal au reste de la division de N par 512. e e 7.4.2 Fichiers binaires et chiers de texte 0 1 2 /* codes conventionnels */ /* ` lusage du syst`me */ a e /* dexploitation */

Lobjet du programme suivant est la constitution dun chier darticles ` partir dun chier de texte qui se a prsente comme une rptition du groupe suivant : e e e un nom et un prnom sur la mme ligne ; e e une adresse sur la ligne suivante ; a e un entier seul sur une ligne, exprimant un certain  nombre de passages  (` un page autoroutier). Exemple TOMBAL Pierre Rue de la Gaiete de Vivre 10

92

c H. Garreta, 2003

7 ENTREES-SORTIES

7.4

Exemples

MENSOIF Gerard Allee Les verts 20 etc. Le programme est indpendant de la nature du chier de texte, qui peut tre aussi bien le clavier dun e e terminal quun chier existant par ailleurs. /* constitution dun fichier darticles */ /* ` partir dun fichier de texte */ a #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); 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 t utilises pour lire du texte. Le nombre de passages est lu par scanf, ee e car cette fonction est la seule qui convertit les nombres. Le nom et le prnom sont lus par scanf aussi, car cela e nous arrange bien que les blancs soient pris pour des sparateurs et enlevs. Mais ladresse est lue par gets, car e e elle peut contenir des blancs qui ne doivent pas tre supprims. e e
c H. Garreta, 2003

93

7.5 Les chiers de bas niveau dUNIX

ENTREES-SORTIES

7.4.3

Fichiers en acc`s relatif e

Dans le programme prcdent nous avons vu un chier binaire organis en articles et trait squentiellement e e e e e (chaque article tant crit ` la suite du prcdent). Voyons un exemple o` ce mme chier est trait en acc`s e e a e e u e e e relatif. Imaginons que le chier cr ` lexemple prcdent soit le chier des abonns ` un certain page, et que ee a e e e a e nous disposions par ailleurs dun chier binaire contenant les numros des abonns qui ont emprunt ce page e e e e durant une priode donne. On nous demande dcrire un programme pour incrmenter la valeur du champ e e e e nbr passages de chaque abonn concern. e 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); 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`me UNIX ne sont pas fondamentalement distincts des ots que lon vient e de dcrire. En fait, les ots et les chiers de bas niveau sont deux mani`res de voir depuis un programme les e e mmes entits de base (les chiers). Les chiers de bas niveau sont la mani`re habituelle de raliser les chiers e e e e binaires lorsquon ne poss`de pas un C ANSI. e Dans le syst`me UNIX chaque processus dispose, pour grer ses chiers, dune table de descripteurs de e e chiers. Lorsque les chiers sont grs au plus bas niveau, ils sont reprsents tout simplement par un indice dans ee e e cette table. Par exemple, les trois units standard stdin, stdout et stderr sont respectivement reprsentes e e e en tant que chiers par les descripteurs dindices 0, 1 et 2. 94
c H. Garreta, 2003

7 ENTREES-SORTIES

7.5

Les chiers de bas niveau dUNIX

Les fonctions disponibles sont : int open(char *nom, int mode, int permissions) Ouvre un chier existant ayant le nom externe indiqu. Largument mode doit avoir pour valeur une des e constantes symboliques (dnies dans <ioctl.h> ou dans <sys/file.h>) : e O RDONLY : ouverture en lecture seulement O WRONLY : ouverture en criture seulement e O RDWR : ouverture en lecture / criture e Cette fonction renvoie le numro du descripteur ouvert pour le chier, ou un nombre ngatif en cas derreur. e e Pour largument permissions voir ci-dessous. int creat(char *nom, int permissions) Cre un chier nouveau, ayant le nom indiqu, et louvre en criture. Comme pour la fonction open, e e e largument permissions indique que le droit de lecture, le droit dcriture ou le droit dexcution est e e accord ou non au propritaire du chier, aux membres de son groupe de travail ou ` tous les utilisateurs e e a du syst`me. Cela fait trois groupes de trois bits, soit un nombre qui scrit particuli`rement bien en octal. e e e Par exemple : fic = creat("tmp/monfic", 0751); cre un chier nouveau et lui accorde les permissions : e propritaire e groupe autres lecture 1 1 0 criture e 1 0 0 excution e 1 1 1 en octal 7 5 1

int read(int fichier, char *adresse, int nombre) Lit nombre octets depuis le fichier indiqu et les range ` partir de ladresse indique. Renvoie le nombre e a e doctets eectivement lus, qui peut tre infrieur ` nombre (n de chier, erreur de lecture, etc.). e e a int write(int fichier, void *adresse, int nombre) Ecrit les nombre octets qui se trouvent ` ladresse indique dans le fichier indiqu. Rend le nombre a e e doctets eectivement crits, qui peut tre infrieur ` nombre (erreur dcriture, etc.). e e e a e int close(int descfic) Ferme le chier indiqu. e long lseek(int fichier, long rang, int origine) Positionne le chier indiqu sur loctet ayant le rang indiqu. Ce rang exprime une position... e e ...relative au dbut du chier (si origine = 0) e ...relative ` la position courante (si origine = 1) a ...relative ` la n du chier (si origine = 2) a Exemple. Voici la version UNIX (et, de plus, non ANSI) du programme, donn plus haut, qui eectue une e 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, 2003

95

8 GENIE LOGICIEL

Gnie logiciel e

Sous un titre tr`s pompeux (et injusti) cette section regroupe un ensemble de notions ayant en commun e e que leur intrt appara surtout ` loccasion de lcriture de gros programmes. Ce sont des lments importants ee t a e ee 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 prprocesseur e

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

La possibilit dinclure des chiers sources dans dautres chiers sources sav`re tr`s utile, par exemple e e e pour insrer dans chacun des chiers spars qui constituent un programme lensemble de leurs dclarations e e e e communes (types, structures, etc.). Ainsi ces dnitions sont crites dans un seul chier, ce qui en facilite la e e maintenance. Un cas particulier tr`s important de ce partage des dclarations est celui des biblioth`ques livres avec le e e e e compilateur. Elles sont essentiellement composes de fonctions utilitaires dj` compiles dont seul le chier objet e ea e est fourni. Pour que les appels de ces fonctions puissent tre correctement compils58 dans des programmes qui e e les utilisent, un certain nombre de chiers en-tte sont livrs avec les biblioth`ques, comprenant e e e principalement, les prototypes des fonctions qui forment la biblioth`que ou qui incarnent une fonctionnalit e e particuli`re (les entres - sorties, la gestion des cha e e nes de caract`res, la biblioth`que mathmatique, etc.) ; e e e souvent, un ensemble de directives #define et de dclarations struct, union et typedef qui dnissent e e les constantes et les types ncessaires pour utiliser la biblioth`que en question ; e e parfois, quelques directives #include concernant dautres chiers en-tte lorsque les lments dnis dans e ee e le chier en-tte en question ne peuvent eux-mmes tre compris sans ces autres chiers ; e e e plus rarement, quelques dclarations de variables externes. e Cette directive poss`de deux formes : e #include "nom-de-chier " Cest la forme gnrale. Le chier spci est insr ` lendroit o` la directive gure avant que la compilation e e e e eea u ne commence ; ce doit tre un chier source crit en C. La cha de caract`res "nom-de-chier " doit tre un e e ne e e nom complet et correct pour le syst`me dexploitation utilis. e e La deuxi`me forme est : e #include <nom-de-chier > Ici, le nom est incomplet ; il ne mentionne pas le chemin dacc`s au chier, car il est convenu quil sagit dun e chier appartenant au syst`me, faisant partie de la biblioth`que standard : le prprocesseur  sait  dans quels e e e rpertoires ces chiers sont rangs. Ce sont des chiers dont le nom se termine par .h (pour header, en-tte) et e e e qui contiennent les dclarations ncessaires pour quun programme puisse utiliser correctement les fonctions de e e la biblioth`que standard. e Exemples (avec des noms de chier en syntaxe UNIX) :
58 Notez bien quil ne sagit pas de fournir les fonctions de la biblioth`que (cela est fait par lapport du chier objet au moment de e ldition de liens) mais uniquement de donner linformation requise pour que les appels de ces fonctions puissent tre correctement e e traduits par le compilateur. Pour cette raison on ne trouve jamais dans les chiers en-tte ni des variables non externes, ni le corps e des fonctions.

96

c H. Garreta, 2003

8 GENIE LOGICIEL

8.1

Le prprocesseur e

#include <stdio.h> #include "/users/henri/projet_grandiose/gadgets.h" Attention. Il ne faut pas confondre inclusion de chiers et compilation spare. Les chiers que lon inclut e e au moyen de la directive #include contiennent du texte source C qui sera compil chaque fois que linclusion e 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 Dnition et appel des macros e

Les macros sans argument59 se dnissent par la directive : e #define nom corps Le nom de la macro est un identicateur. Le corps de la macro stend jusqu` la n de la ligne60 . Partout e a dans le texte o` le nom de la macro appara en qualit didenticateur (ce qui exclut les commentaires, les u t e cha nes de caract`res et les occurrences o` le nom de la macro est coll ` un autre identicateur), il sera remplac e u ea e par le corps de la macro, lequel est un texte quelconque nayant ` obir ` aucune syntaxe. Par exemple, entre a e a lendroit o` gurent les deux directives suivantes : u #define NBLIGNES 15 #define NBCOLONNES (2 * NBLIGNES) et la n du chier o` elles apparaissent, chaque occurrence de lidenticateur NBLIGNES sera remplace par le u e 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, ` la place de a double matrice[NBLIGNES][NBCOLONNES]; le compilateur trouvera double matrice[15][(2 * 15)]; (il remplacera immdiatement 2 * 15 par 30). e Attention. Voici une erreur que lon peut faire : #define NBLIGNES 15; #define NBCOLONNES (2 * NBLIGNES); Cest une erreur dicile ` dceler, car le compilateur ne signalera ici rien de particulier. Mais plus loin il donnera a e pour errone une dclaration comme : e e double matrice[NBLIGNES][NBCOLONNES]; En eet, cette expression qui para sans dfaut aura t transforme par le prprocesseur en61 t e ee e e double matrice[15;][(2 * 15;);]; Macros avec arguments. Elles se dnissent par des expressions de la forme : e #define nom(ident 1 , ... ident k ) corps Il ne doit pas y avoir de blanc entre le nom de la macro et la parenth`se ouvrante. Comme prcdemment, e e e le corps de la macro stend jusqu` la n de la ligne. Les identicateurs entre parenth`ses sont les arguments e a e 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 remplace par le corps de la macro, dans lequel ident 1 aura t remplac par texte 1 , e ee e ident 2 par texte 2 , etc. Exemple62 : #define permuter(a, b, type) { type w_; w_ = a; a = b; b = w_; } Exemple dappel : permuter(t[i], t[j], short *)
59 Le mot macro est une abrviation de lexpression  macro-instruction  dsignant un mcanisme qui existe depuis longtemps e e e dans beaucoup dassembleurs. Les macros avec arguments sont appeles aussi pseudo-fonctions. e 60 Mais il dcoule de la section 1.2.1 que le corps dune macro peut occuper en fait plusieurs lignes : il sut que chacune delles, e sauf la derni`re, se termine par le caract`re \. e e 61 Pour permettre de trouver ce genre derreurs, certains compilateurs C poss`dent une option (sous UNIX loption -E) qui produit e lachage du programme source uniquement transform par le prprocesseur. e e 62 Dans cet exemple, les accolades { et } donnent au corps de la macro une structure de bloc qui sera comprise et exploite par e le compilateur, mais cette structure est parfaitement indirente au prprocesseur. e e

c H. Garreta, 2003

97

8.1 Le prprocesseur e

GENIE LOGICIEL

Rsultat de la substitution (on dit aussi dveloppement) de la macro : e e { short * w_; w_ = t[i]; t[i] = t[j]; t[j] = w_; } Les appels de macros ressemblent beaucoup aux appels de fonctions. La mme facilit, ne pas rcrire un e e e certain code source, peut tre obtenue avec une macro et avec une fonction. Mais il faut comprendre que ce nest e pas du tout la mme chose. Alors que le corps dune fonction gure en un seul exemplaire dans le programme e excutable dont elle fait partie, le corps dune macro est recopi puis compil ` chaque endroit o` gure lappel e e ea u de la macro. Il faut donc que le corps dune macro soit rduit, sans quoi son utilisation peut nir par tre tr`s onreuse e e e e en termes despace occup. Un autre inconvnient des macros est que leur dnition  hors syntaxe  les rend e e e tr`s diciles ` ma e a triser au-del` dun certain degr de complexit (pas de variables locales, etc.). a e e En faveur des macros : leur ecacit. Le texte de la macro tant recopi ` la place de lappel, on gagne ` e e ea a chaque fois le cot dun appel de fonction. Il et t bien peu ecace dappeler une fonction pour ne faire que la u u ee permutation de deux variables ! Autre intrt des macros, elles sont indpendantes des types de leurs arguments ee e ou mme, comme ci-dessus, elles peuvent avoir un type pour argument. e En dnitive, la principale utilit des macros est damliorer lexpressivit et la portabilit des programmes e e e e e en centralisant la dnition doprations compliques, proches du matriel ou sujettes ` modication. Voici un e e e e a exemple lmentaire : en C original (dans lequel on ne dispose pas du type void *) la fonction malloc doit tre ee e gnralement utilise en association avec un changement de type : e e e p = (machin *) malloc(sizeof(machin)); Des telles expressions peuvent gurer ` de nombreux endroits dun programme. Si nous dnissons la macro a e #define NOUVEAU(type) ((type *) malloc(sizeof(type))) alors les allocations despace mmoire scriront de mani`re bien plus simple et expressive e e e p = NOUVEAU(machin); De plus, un ventuel remplacement ultrieur de malloc par un autre procd dobtention de mmoire sera e e e e e facile et able, puisquil ny aura quun point du programme ` modier. a Attention. 1. Il est quasiment obligatoire dcrire les arguments et le corps des macros entre parenth`ses, e e pour viter des probl`mes lis aux priorits des oprateurs. Par exemple, la macro e e e e e #define TVA(p) 0.196 * p est tr`s imprudente, car une expression comme (int) TVA(x) sera dveloppe en (int) 0.196 * x, ce qui vaut e e e toujours 0. Premi`re correction : e #define TVA(p) (0.186 * p) Le probl`me prcdent est corrig, mais il y en a un autre : lexpression TVA(p1 + p2) est dveloppe en e e e e e e (0.186 * p1 + p2), ce qui est probablement erron. Do` la forme habituelle : e u #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 tre valus plus dune fois. Exemple classique : on dispose dune macro nomme toupper qui transforme e e e e toute lettre minuscule en la majuscule correspondante, et ne fait rien aux autres caract`res. Elle pourrait tre e e ainsi dnie (mais on nest pas cens le savoir) : e e #define toupper(c) (a <= (c) && (c) <= z ? (c) + (A - a) : (c)) On voit que lvaluation de toupper(c) comporte au moins deux valuations de c. Voici un tr`s mauvais appel e e e de cette macro : calu = toupper(getchar()) En eet, cette expression se dveloppe en e calu = (a <= (getchar()) && (getchar()) <= z ? (getchar()) + (A - a) : (getchar())); A chaque appel, au moins deux caract`res sont lus, ce qui nest srement pas leet recherch. On notera e u e que si toupper avait t une fonction, lexpression toupper(getchar()) aurait t tout ` fait correcte. Mais ee ee a puisque ce nen est pas une, il faut lappeler avec un argument sans eet de bord. Par exemple : { calu = getchar(); calu = toupper(calu); } 98
c H. Garreta, 2003

8 GENIE LOGICIEL

8.1

Le prprocesseur e

8.1.3

Compilation conditionnelle

Les directives de conditionnelle (compilation) se prsentent sous les formes suivantes : e #if expression #ifdef identicateur #ifndef identicateur ... texte compil si la condition est vraie, ignor si elle est fausse e e ... #else ... texte compil si la condition est fausse, ignor si elle est vraie e e ... #endif La partie else est facultative ; on a donc droit aussi aux formes : #if expression #ifdef identicateur #ifndef identicateur ... texte compil si la condition est vraie, ignor si elle est fausse e e ... #endif Lorsque le premier lment de cette construction est #if expression, expression doit pouvoir tre value ee e e e par le prprocesseur. Ce doit donc tre une expression constante ne contenant pas loprateur de changement e e e de type ni les oprateurs sizeof ou &. Elle sera value et e e e si elle est vraie, cest-`-dire non nulle, alors le texte qui se trouve entre #if et #else sera trait par le a e compilateur, tandis que celui qui gure entre #else et #endif sera purement et simplement tenu pour inexistant ; si elle est fausse, cest-`-dire nulle, cest le premier texte qui sera ignor tandis que le second sera lu par a e le compilateur. Dans la forme sans #else, si la condition est fausse aucun texte ne sera compil. e Les directives #ifdef identicateur #ifndef identicateur quivalent respectivement ` e a #if  identicateur est le nom dune macro actuellement dnie  e #if  identicateur nest pas le nom dune macro actuellement dnie  e La compilation conditionnelle se rv`le tre un outil prcieux pour contrler les parties des programmes e e e e o qui dpendent de la machine ou du syst`me sous-jacent. On obtient de la sorte des textes sources qui restent e e portables malgr le fait quils contiennent des lments non portables. Voici un exemple : les fonctions de lecturee ee criture sur des chiers binaires ne sont pas les mmes dans la biblioth`que ANSI et dans la biblioth`que UNIX e e e e classique. Une mani`re dcrire un programme indpendant de ce fait consiste ` enfermer les appels de ces e e e a fonctions et les dclarations qui sy rapportent dans des squences conditionnelles. Dclaration : e e e ... #ifdef BINAIRES_ANSI FILE *fic; #else int fic; #endif ... Ouverture :
c H. Garreta, 2003

99

8.2 La modularit de C e

GENIE LOGICIEL

... #ifdef BINAIRES_ANSI fic = fopen(nom, "r"); ok = (fic != NULL); #else fic = open(nom, 0); ok = (fic >= 0); #endif ... Lecture : ... #ifndef BINAIRES_ANSI ok = (fread(&art, sizeof(art), 1, fic) == 1); #else ok = (read(fic, &art, sizeof(art)) == sizeof(art)); #endif ... Lemploi de la compilation conditionnelle associe ` la dnition de noms de macros est rendu encore plus e a e commode par le fait quon peut utiliser des noms de macros normalement dnis (#define...) ; e e des noms de macros dnis dans la commande qui lance la compilation, ce qui permet de changer le texte qui sera compil sans toucher au chier source. De plus, puisque ces dnitions se font au niveau de la e e commande de compilation, elles pourront tre faites par des procdures de commandes (scripts) donc e e automatises ; e un ensemble de noms de macros prdnis dans chaque syst`me, pour le caractriser. Par exemple dans e e e e le compilateur des syst`mes UNIX la macro UNIX est dnie et vaut 1. e e Remarque. Le prprocesseur du C ANSI ore galement loprateur defined qui permet dcrire les die e e e rectives #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 sabrger en e #if expression1 ... #elif expression2 ... #endif

8.2

La modularit de C e

A ce point de notre expos nous pouvons nous poser une question tout ` fait dans lair du temps : le langage e a C est-il modulaire ? La modularit est une qualit que les bonnes mthodes de conception doivent possder. Elle e e e e nest pas facile ` dnir ; on peut toutefois la cerner ` travers quelques crit`res63 . Une mthode de conception a e a e e doit a e e e e e e aider ` diviser chaque nouveau probl`me en sous-probl`mes quon peut rsoudre sparment (crit`re de dcomposabilit ) ; e e favoriser la production de composants logiciels qui peuvent se combiner librement pour produire de nouveaux syst`mes (crit`re de composabilit ) ; e e e e e e permettre au concepteur dcrire des modules dont chacun peut tre compris isolment par un lecteur humain (crit`re de comprhensibilit ) ; e e e
63 Suivant lanalyse de Bertrand Meyer (Conception et programmation par objets, InterEditions, 1990) ` laquelle il semble dicile a dajouter ou de retrancher quelque chose.

100

c H. Garreta, 2003

8 GENIE LOGICIEL

8.2

La modularit de C e

permettre quune petite modication des spcications du probl`me entra uniquement la modication e e ne dun petit nombre des modules de la solution (crit`re de continuit ) ; e e assurer que leet dune condition anormale se produisant dans un module restera localis ` ce module ea ou, au pire, natteindra quun petit nombre de modules  voisins  ( crit`re de protection). e Thoriquement, tout langage de programmation peut servir ` mettre en uvre une mthode de conception e a e modulaire, mais dans certains cas il faut une telle dose de ruse et dautodiscipline, pour un rsultat si peu able, e 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 mthode de conception modulaire. Lexprience montre que les crit`res e e e prcdents induisent sur le langage en question quelques contraintes assez prcises, comme celles-ci : e e e les modules doivent correspondre ` des entits syntaxiques du langage ; a e chaque module doit partager des informations avec aussi peu dautres modules que possible, et quand un tel partage existe il doit concerner aussi peu dlments que possible ; ee quand deux modules partagent des informations, cela doit tre clairement indiqu dans leurs deux textes. e e A la lumi`re de ces principes la modularit de C appara fort rudimentaire, pour ne pas dire inexistante. e e t Si lon prend pour modules les chiers sources qui composent un programme, on constate quaucune structure syntaxique ne signale les modules ni nen dlimite la porte, dautant plus que le caract`re non syntaxique de la e e e directive #include brouille lorganisation du programme en chiers distincts. Aucune dclaration particuli`re e e nest requise pour indiquer que des objets sont partags entre plusieurs modules. Chaque module communique e avec tous les autres et, sauf spcication contraire, tous les objets de chaque module sont partags. e e Bien sr, la compilation spare est une des ides-cls du langage, et il est possible de rendre inaccessibles u e e e e les noms qui peuvent tre privs. Mais le langage ore peu doutils pour rendre able le partage des noms qui e e doivent tre publics, et ces outils restent dun emploi facultatif, subordonn ` lautodiscipline du programmeur. e ea Par exemple, si dans un module B on doit rfrencer une variable ou une fonction dnie dans un module A, il ee e sut dcrire dans B une dclaration comme extern int x ;. Cet nonc postule lexistence dun objet nomm e e e e e x, ce qui sera contrl par lditeur de liens. Mais il donne ` x des attributs (la classe variable, le type int) que oe e a lobjet dsign par x ne poss`de pas forcment ; aucune vrication cependant ne sera faite. Ainsi la compilation e e e e e de B se fait dans la plus totale inscurit. e e

8.2.1

Fichiers en-tte e

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 ` crire un chier enae tte (chier A.h) contenant toutes les dclarations publiques. Ce chier doit tre inclus par la directive #include e e e dans le module qui implante les objets publics (chier A.c) et dans chacun des modules qui les utilisent. De cette mani`re tous ces chiers  voient  les mmes dnitions de types, les mmes dclarations de variables e e e e e et les mmes prototypes de fonctions ; ces dclarations sont crites en un seul endroit, et toute modication de e e e lune dentre elles se rpercute sur tous les chiers qui en dpendent. e e La ncessit de ces chiers en-tte appara encore plus grande quand on consid`re le cas des biblioth`ques, e e e t e e cest-`-dire des modules que leurs fonctionnalits placent en position de prestataires de services vis-`-vis des a e a 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 en termes de serveurs et clients, car il y a toujours une hirarchie e e parmi les modules. Le propre des biblioth`ques est dtre conues de mani`re indpendante des clients, an de e e c e e pouvoir tre utilises dans un programme prsent et un nombre quelconque de programmes futurs. Lintrt de e e e ee leur associer le meilleur dispositif pour minimiser le risque de mauvaise utilisation est vident. e Typiquement, un chier en-tte comportera les lments suivants : e ee Des directives #include concernant les autres chiers en-tte ncessaires pour la comprhension (par le e e e compilateur) des lments qui apparaissent dans le chier en-tte en question. ee e e e ee Des dnitions de constantes, soit sous forme de directives #define soit sous forme de type numr, qui sont des informations symboliques changes entre le serveur et ses clients. Exemple : dans une biblioth`que e e e graphique, les noms conventionnels des couleurs. e e Des dnitions de structures (struct, union) et de types (typedef) qui dnissent la nature des objets manipuls par la biblioth`que. Typiquement, ces types permettent aux clients de dclarer les objets qui e e e sont les arguments et les rsultats des fonctions de la biblioth`que. Exemple : dans une biblioth`que e e e graphique, la dnition de types point, segment, etc. e e e Les dclarations extern des variables publiques du serveur. Les dnitions correspondantes (sans le qualieur extern) gureront dans le module serveur. Remarque. Lemploi de variables publiques est dconseill ; un module ne devrait orir que des fonce e
c H. Garreta, 2003

101

8.2 La modularit de C e

GENIE LOGICIEL

tions64 . Les dclarations des fonctions publiques du serveur. Les dnitions correspondantes gureront dans le e e module serveur. En syntaxe originale seules les dclarations des fonctions qui ne rendent pas un entier e sont ncessaires, mais mme dans ce cas cest une bonne habitude que dy mettre les dclarations de toutes e e e 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-tte doivent tre rendus privs (en les qualiant static). e e e 8.2.2 Exemple : stdio.h

A titre dexemple visitons rapidement le plus utilis des chiers en-tte de la biblioth`que standard : stdio.h. e e e Notre but nest pas de prolonger ici ltude des entres-sorties, mais dillustrer les indications du paragraphe e e prcdent sur lcriture des chiers en-tte. Le texte ci-apr`s est fait de morceaux de la version MPW (Macintosh e e e e e Programmer Workshop) du chier stdio.h auquel nous avons enlev ce qui ne sert pas notre propos : e /* * * * * * * */ stdIO.h -- Standard C I/O Package Modified for use with Macintosh C Apple Computer, Inc. 1985-1988 Copyright American Telephone & Telegraph Used with permission, Apple Computer Inc. (1985) All rights reserved.

#ifndef __STDIO__ #define __STDIO__ #include <stddef.h> #include <stdarg.h> /* * Miscellaneous constants */ #define EOF (-1) #define BUFSIZ 1024 #define SEEK_CUR 1 #define SEEK_END 2 #define SEEK_SET 0

/* * The basic data structure for a stream is the FILE. */ typedef struct { int _cnt; unsigned char *_ptr; unsigned char *_base; unsigned char *_end; unsigned short _size; unsigned short _flag; unsigned short _file; } FILE; /* * Things used internally: */ extern FILE _iob[]; int _filbuf (FILE *); int _flsbuf (int, FILE *);

64 Sil est utile quun client puisse consulter ou modier une variable du serveur, crivez une fonction qui ne fait que cela (mais e en contrlant la validit de la consultation ou de la modication). Cest bien plus s r que de donner libre acc`s ` la variable. o e u e a

102

c H. Garreta, 2003

8 GENIE LOGICIEL

8.2

La modularit de C e

/* * 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); /* * */ 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-tte sont dun intrt gnral ils nissent par tre inclus dans de nombreux autres e ee e e e chiers, eux-mmes inclus les uns dans les autres. Le compilateur risque alors de lire plusieurs fois un mme e e chier, ce qui entra un travail inutile et parfois des erreurs lies ` des rednitions. Par exemple, imane e a e ginons que MonBazar.h soit un chier comportant la directive #include <stdio.h>. Dans la compilation dun chier commenant par les deux directives c #include <stdio.h> #include "MonBazar.h" le compilateur risquerait de compiler deux fois le chier stdio.h. Les deux premi`res directives de ce e chier rsolvent ce probl`me : lorsque ce chier est pris en compte une premi`re fois, le nom __STDIO__ e e e (nom improbable arbitrairement associ ` stdio.h) devient dni. Durant la prsente compilation, la e a e e
c H. Garreta, 2003

103

8.3 Deux ou trois choses bien pratiques...

GENIE LOGICIEL

directive #ifndef __STDIO__ fera que lintrieur du chier ne sera plus jamais lu par le compilateur. Tous e les chiers en-tte peuvent tre protgs de cette mani`re contre les inclusions multiples. e e e e e On peut noter ici que la vue du type FILE quont les modules clients est tr`s dirente de celle quils e e auraient en utilisant un langage tr`s modulaire comme Modula II ou Ada. Dans ces langages les noms e et les types des champs dune structure comme FILE resteraient privs, ainsi que la structure elle-mme ; e e seul le type adresse dune telle structure serait rendu public. Cela sappelle un type opaque et augmente la abilit des programmes. e En C nous navons pas doutils pour procder de la sorte : ou bien un type est priv, connu uniquement e e du module serveur, ou bien il faut  tout dire  ` son sujet. Lutilisation dun pointeur gnrique, dans le a e e style de typedef void *FILE_ADDRESS; dissimulerait bien linformation sur la structure FILE, mais aurait la consquence de rendre impossibles ` e a dceler les mauvaises utilisations du type en question par le client. Notez dautre part que la connaissance e 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-tte deviennent de ce fait publics, mme ceux quon e e aurait aim garder privs alors quon ne le peut pas, par exemple parce quils apparaissent dans le corps e e dune macro, comme ici le nom de la table des descripteurs de chiers iob. Le langage C nayant rien prvu ` ce eet, la  privacit  nest assure que par une convention entre le syst`me et les utilisateurs : e a e e e tout nom commenant par un blanc soulign   appartient au syst`me et lutilisateur doit feindre den c e e ignorer lexistence. Ceci nest pas la meilleure mani`re de donner les prototypes des fonctions dans un chier en-tte. Dans une e e dclaration qui nest pas une dnition, la syntaxe nexige pas les noms des arguments mais uniquement e e leurs types. Cependant, sils sont bien choisis, ces noms apportent une information supplmentaire qui e augmente la scurit dutilisation de la biblioth`que. Par exemple, les prototypes e e e FILE *fopen(const char *, const char *); size_t fread(void *, size_t, size_t, FILE *); ne seraient daucune utilit ` un programmeur qui hsiterait ` propos du rle ou de lordre des arguments ea e a o de ces fonctions, contrairement ` leurs versions  quivalentes  : a e 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 bncient pas des vrications syntaxiques que e e e le C ANSI eectue lorsque la fonction a fait lobjet dune dnition de prototype. Sauf pour les arguments e nomms (le premier ou les deux premiers), les arguments que lon passe ` lune de ces six fonctions e a chappent donc ` tout contrle du compilateur. Linformation concernant la nature de ces arguments est e a o porte par le format ; elle ne pourra tre exploite qu` lexcution. e e e a e

Autres remarques. Le premier client de ce dispositif doit tre le fournisseur lui-mme. Pour que la e e protection contre lerreur recherche soit eective, il faut que chacun des chiers qui ralisent limplantation des e e variables et fonctions  promises  dans stdio.h comporte la directive #include <stdio.h> De cette mani`re on garantit que lauteur et lutilisateur de chaque variable ou fonction sont bien daccord e sur la dnition de lentit en question. e e Une autre r`gle ` respecter par lauteur du ou des modules serveurs : qualier static tous les noms qui ne e a sont pas dclars dans le chier en-tte, pour viter les collisions de noms. Le chier en-tte joue ainsi, pour ce e e e e e qui concerne les variables et les fonctions, le rle de liste ocielle des seuls noms publics. o

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 activs en composant une commande de la forme nom-du-programme argument 1 ... argument k . e Lexcution dun programme commence par la fonction main. Tout se passe comme si le syst`me dexploitae e tion avait appel cette fonction comme une fonction ordinaire. Il faut savoir que lors de cet appel, des arguments e sont fournis au programme. Voici len-tte complet de main, en syntaxe ANSI : e 104
c H. Garreta, 2003

8 GENIE LOGICIEL

8.3

Deux ou trois choses bien pratiques...

int main(int argc, char *argv[]) avec : argc : nombre darguments du programme argv : tableau de cha nes de caract`res, qui sont les arguments du programme. Par convention, le premier e argument est le nom du programme lui-mme. e Imaginons avoir crit un programme qui, une fois compil, se nomme echo ; supposons que ce programme e e soit lanc par la commande : e echo Pierre Paul alors, main reoit les arguments que montre la gure 18. c

e c h o \0 argc 3 argv

P i e r r e\ 0

P a u l\ 0

NULL
Fig. 18 Arguments de main

Supposons que tout ce que lon demande ` echo soit de recopier la liste de ses arguments. Voici comment a on pourrait crire ce programme : e main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) printf("%s\n", argv[i]); return 0; } La frappe de la commande  echo Pierre Paul  produit lachage de echo Pierre Paul La principale application de ce mcanisme est la fourniture ` un programme des param`tres dont il peut e a e dpendre, comme des noms de chiers, des tailles de tableaux, etc. Par exemple, voici une nouvelle version du e programme de copie de chiers donn ` la section 7.4.1, qui prend les noms des chiers source et destination ea comme arguments : #include <stdio.h> #define #define #define #define PAS_D_ERREUR ERREUR_OUVERTURE ERREUR_CREATION PAS_ASSEZ_D_ARGUMENTS 0 1 2 3

FILE *srce, *dest; main(int argc, char *argv[]) { char tampon[512]; int nombre; if (argc < 3) return PAS_ASSEZ_D_ARGUMENTS; if ((srce = fopen(argv[1], "rb")) == NULL) return ERREUR_OUVERTURE; if ((dest = fopen(argv[2], "wb")) == NULL) return ERREUR_CREATION;
c H. Garreta, 2003

105

8.3 Deux ou trois choses bien pratiques...

GENIE LOGICIEL

while ((nombre = fread(tampon, 1, 512, srce)) > 0) fwrite(tampon, 1, nombre, dest); fclose(dest); return PAS_D_ERREUR; } Si nous appelons copier le chier excutable produit en compilant le texte ci-dessus, alors nous pourrons e lexcuter en composant la commande e copier chier-source chier-destination 8.3.2 Branchements hors fonction : setjmp.h

Le mcanisme des longs branchements permet dobtenir la terminaison immdiate dun nombre quelconque e e de fonctions actives. Il est ralis ` laide des deux fonctions : e ea int setjmp(jmp_buf contexte); void longjmp(jmp_buf contexte, int code); Cela fonctionne de la mani`re suivante : tout dabord il faut dclarer une variable, gnralement globale, de e e e e type jmp buf (type dni dans le chier en-tte setjmp.h) : e e #include <setjmp.h> ... jmp_buf contexte; Lappel setjmp(contexte); enregistre dans contexte certaines informations traduisant ltat du syst`me, puis renvoie zro. Ensuite, lappel e e e longjmp(contexte, valeur); remet le syst`me dans ltat qui a t enregistr dans la variable contexte. Plus prcisment, le syst`me se e e ee e e e e trouve comme si lappel de setjmp(contexte) venait tout juste de se terminer, en rendant cette fois non pas zro mais la valeur indique dans lappel de longjmp. e e 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 appeles. Examinons un e exemple classique : supposons quune certaine fonction expression soit un analyseur syntaxique prsentant e deux caractristiques tr`s frquentes : e e e la fonction expression est ` lorigine dune imbrication dynamique fort complexe (expression appelle a une fonction terme, qui appelle une fonction facteur qui ` son tour appelle une fonction primaire laquelle a rappelle expression, etc.) ; chacune des fonctions expression, terme, facteur, etc., peut ` tout moment rencontrer une erreur dans a ses donnes, qui rend la poursuite du traitement inutile ou impossible. e

main analyse()

analyse

setjmp

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

terme()

Fig. 19 Branchement hors fonction

106

c H. Garreta, 2003

8 GENIE LOGICIEL

8.3

Deux ou trois choses bien pratiques...

Voici comment lappel dexpression pourrait tre emball dans une fonction  enveloppe  nomme analyse e e e (voir la gure 19) : #include <setjmp.h> ... jmp_buf contexte; ... int analyse(void) { if (setjmp(contexte) == 0) { expression(); return SUCCES; } else return ERREUR; } Fonctionnement : lorsque la fonction analyse est active, setjmp est appele et rend 0 ; elle est donc e e immdiatement suivie par lappel de expression. Si le travail de expression se termine normalement, alors e analyse rendra SUCCES et elle aura t transparente. Dautre part, dans la fonction expression et toutes celles ee appeles  au-dessus  delle (terme, facteur, etc.), il est possible deectuer lappel e longjmp(contexte, 1); qui ram`ne le contrle dans la fonction analyse, exactement sur la partie condition de linstruction e o 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 prsente comme une remise du syst`me dans lun des tats par lesquels e e e il est pass. Cela est vrai, sauf pour ce qui concerne la valeur des variables, notamment les variables globales : e elles ne reprennent pas la valeur quelles avaient lorsque longjmp a t appele. ee e 2. On ne peut eectuer un appel de longjmp que pour ramener le syst`me dans lune des fonctions appeles e e et non encore termines, pour laquelle lespace local est encore allou dans la pile. Appeler longjmp avec un e e contexte qui a t mmoris dans une fonction dont lactivation est termine est une erreur aux consquences ee e e e e indnies, mme si cette fonction a t rappele depuis. Par exemple, un appel longjmp(c,v) qui serait plac e e ee e e dans la fonction main apr`s lappel analyse() (voir la gure 19) serait erron, car il utiliserait le contexte dune e e activation de fonction (la fonction analyse) qui nexiste plus. 8.3.3 Interruptions : signal.h

Lobjet de la fonction signal est la dtection dvnements asynchrones qui peuvent se produire pendant e e e lexcution dun programme.  Asynchrones  signie quon ne peut pas prvoir le moment o` ils se produiront, e e u car ils ne rsultent pas dinstructions normalement insres dans la squence qui forme le programme. e ee e Un certain ensemble de types dvnements, dpendant de chaque syst`me, est rcuprable ` travers le e e e e e e a mcanisme dcrit ici. En gnral on traite de la mme mani`re le vrai et le faux asynchronisme. Une coupure e e e e e e de courant ou une interruption provoque par lutilisateur sont des vnements vraiment imprvisibles. Une e e e e rfrence ` travers un pointeur invalide ou une division par zro ne sont pas rellement asynchrones (si on avait ee a e e connu parfaitement le programme et les donnes on aurait pu prdire tr`s exactement de tels vnements), mais e e e e e il est commode de les considrer comme tels et de les rcuprer de la mme mani`re. e e e e e La fonction signal est dclare dans le chier signal.h de la faon suivante : e e c void (*signal(int numero, void (*manip)(int)))(int); Ce prototype nest pas tr`s facile ` lire. Dnissons le type PROCEDURE comme celui dune fonction sans e a e rsultat dni ayant un argument de type int : e e typedef void PROCEDURE(int); Avec cela, la dclaration prcdente se rcrit plus simplement : e e e e PROCEDURE *signal(int numero, PROCEDURE *manip); et elle nous apprend que la fonction signal prend deux arguments, ` savoir un int et ladresse dune PROCEDURE, a et renvoie ladresse dune PROCEDURE. Lors dun appel de signal, largument numero doit dsigner un des vnements quil est possible de rcuprer. e e e e e Leet de signal est denregistrer la PROCEDURE donne en argument, de telle mani`re que si lvnement en e e e e
c H. Garreta, 2003

107

8.3 Deux ou trois choses bien pratiques...

GENIE LOGICIEL

question se produit, alors elle sera automatiquement appele par le syst`me avec pour argument le numro de e e e lvnement. La fonction signal rend ladresse de la PROCEDURE qui tait jusqualors enregistre pour ce mme e e e e e vnement. e e Six vnements sont prvus par le standard ANSI (ils ne sont pas tous implants dans tous les syst`mes ; en e e e e e outre, chaque syst`me peut ajouter ses propres particularits) : e e SIGABRT : n anormale du programme (appel de la fonction abort) SIGINT : interruption provoque par lutilisateur (touche Ctrl-C, etc.) e SIGTERM : demande darrt de lutilisateur (interruption forte ) e SIGFPE : erreur arithmtique (division par zro, etc.) e e SIGSEGV : acc`s mmoire illgal (souvent : mauvais pointeur dans lacc`s ` une donne, dbordement de e e e e a e e tableau) SIGILL : instruction illgale (souvent : mauvais pointeur dans lappel dune fonction) e La biblioth`que fournit en outre deux mcanismes de rcupration dune interruption prdnis : e e e e e e e e e e e e SIG DFL : le mcanisme par dfaut utilis par le syst`me pour lvnement en question lorsque la fonction signal na pas t appele ee e SIG IGN : le mcanisme trivial qui consiste ` ignorer lvnement e a e e Exemple. Supposons quune section dun certain programme eectue des calculs tr`s complexes dont la dure e e risque dinquiter lutilisateur nal. On souhaite donc orir ` ce dernier la possibilit dinterrompre le programme e a e lorsquil estime que celui-ci devient excessivement long, en appuyant sur une touche particuli`re65 . Lutilisateur e apprendra alors ltat davancement de son calcul (traduit par la valeur de la variable iteration numero) et e aura ` choisir entre la continuation et la terminaison du programme : a #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 oprations e ... /* entre dans la section onreuse */ e e maniprec = signal(SIGINT, voirSkissPass); ... calculs terriblement complexes ... /* sortie de la section onreuse */ e signal(SIGINT, maniprec); ... autres oprations e ... }

65 Sur

plusieurs syst`mes, comme UNIX et VMS, il sagit de la combinaison des deux touches  Ctrl  et  C . e

108

c H. Garreta, 2003

8 GENIE LOGICIEL

8.4

La biblioth`que standard e

8.4

La biblioth`que standard e

La biblioth`que standard ANSI se compose dun ensemble de fonctions, dont le code se trouve dans un ou e plusieurs chiers objets qui participent ` ldition de liens, et dun ensemble de chiers en-tte contenant les a e e dclarations ncessaires pour que les appels de ces fonctions puissent tre correctement compils. Ces chiers e e e e en-tte sont organiss par th`mes ; nous reprenons cette organisation pour expliquer les principaux lments de e e e ee cette biblioth`que. e Des parties importantes de la biblioth`que standard ont dj` t expliques ; notamment : e eaee e la biblioth`que des entres - sorties (associe au chier stdio.h), ` la section 7 ; e e e a les listes variables darguments (introduites dans le chier stdarg.h) ` la section 4.3.4 ; a les branchements hors-fonction (dclars dans le chier setjmp.h) ` la section 8.3.2 ; e e a la rcupration des interruptions (avec le chier signal.h) ` la section 8.3.3. e e a Nous passerons sous silence certains modules mineurs (comme la gestion de la date et lheure). Au besoin, reportez-vous ` la documentation de votre syst`me. a e 8.4.1 Aide ` la mise au point : assert.h a

Cette  biblioth`que  ne contient aucune fonction. Elle se compose dune macro unique : e void assert(int expression) qui fonctionne de la mani`re suivante. Si lexpression indique est fausse (cest-`-dire si elle vaut zro) au moment e e a e o` la macro est value, un message est imprim sur stderr, de la forme u e e e Assertion failed: expression, file chier , line numero ensuite lexcution du programme est avorte. Exemple na : e e f #include <assert.h> ... #define TAILLE 100 int table[TAILLE]; ... for (i = 0; j < TAILLE; i++) { ... assert(0 <= i && i < TAILLE); table[i] = 0; ... } lexcution de ce programme donnera (puisquune faute de frappe a rendu la boucle for innie) : e Assertion failed: 0 <= i && i < TAILLE, file ex.c, line 241 La macro assert se rv`le un outil prcieux pour lcriture et la mise au point des programmes (dans des e e e e situations plus complexes que lexemple trivial montr ici !). Beaucoup de temps peut tre gagn en  postant  e e e de telles assertions aux endroits o` les variables du programme doivent satisfaire des contraintes importantes. u Cependant, cette macro nest utile que pendant le dveloppement et ses appels doivent tre retirs du programme e e e destin ` lutilisateur nal, dabord parce que ce dernier na pas ` conna e a a tre le nom dun chier source ou le numro dune ligne o` une incongruit sest produite, ensuite et surtout parce que ces appels de assert e u e ralentissent les programmes. Lorsque la mise au point dun programme est termine, on doit donc eacer tous les appels de assert qui y e gurent. Cela peut se faire automatiquement, tout simplement en dnissant lidenticateur NDEBUG : e #define NDEBUG peu importe la valeur juste avant deectuer la compilation nale du programme. Celle-ci se fera alors comme si tous les appels de assert avaient t gomms. ee e 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 s est lexpression crite. ne e int rand(void) Dans lexcution dun programme, le ieme appel de cette fonction rend le ieme terme dune suite dentiers e pseudo-alatoires compris entre 0 et RAND MAX (qui vaut au moins 32767). Cette suite ne dpend que de e e
c H. Garreta, 2003

109

8.4 La biblioth`que standard e

GENIE LOGICIEL

la valeur de la semence donne lors de lappel de srand, voir ci-dessous. Si srand na pas t appele, la e ee e suite obtenue est celle qui correspond ` srand(1). a void srand(unsigned int semence) Initialise une nouvelle suite pseudo-alatoire (voir rand ci-dessus). e void *malloc(size t taille) Alloue un espace pouvant mmoriser un objet ayant la taille indique, ou NULL en cas dchec. Cet espace e e e 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 indique, ou e NULL en cas dchec. Lespace allou est initialis par des zros. e e e e void free(void *adresse) Indique au syst`me que lespace mmoire ayant ladresse indique nest plus utile au programme. Cette e e e adresse doit ncessairement provenir dun appel de malloc, calloc ou realloc. e void *realloc(void *adr, size t taille) R-allocation despace. La valeur de adr doit provenir dun appel de malloc, calloc ou realloc. Cette e fonction essaie dagrandir ou de rtrcir (usage rare) lespace point par adr an quil ait la la taille e e e demande. Si cela peut se faire sur place alors la fonction renvoie la mme adresse adr. Sinon, elle obtient e e un nouvel espace ayant la taille voulue, y copie le contenu de lespace point par adr, lib`re cet espace, e e enn renvoie ladresse de lespace nouvellement allou. e void exit(int code) Produit larrt normal (fermeture des chiers ouverts, etc.) du programme. La valeur du code indiqu est e e transmise au syst`me dexploitation, cette valeur est utilise lorsque le programme a t appel dans le e e ee e cadre dun procdure de commandes (ou script). La signication de ce code dpend du syst`me ; on peut e e e utiliser les constantes : EXIT SUCCESS : la valeur conventionnelle qui indique, pour le syst`me sous-jacent, que le e programme a russi ` accomplir sa mission ; e a EXIT FAILURE : le programme na pas russi. e void abort(void) Provoque un arrt anormal du programme (cest un vnement dtect par la fonction signal). e e e e e int system(const char *commande) Suspend momentanment lexcution du programme en cours, demande ` linterpr`te des commandes du e e a e syst`me dexploitation dexcuter la commande indique et rend le code donn par cette commande (cette e e e e valeur dpend du syst`me). La cha commande doit tre lexpression compl`te, avec options et arguments, e e ne e e dune commande lgale pour le syst`me utilis. e e 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`te de commandes peut eectivement tre appel depuis un proe e e gramme, zro sinon. e char *getenv(char *nom) Rend la cha qui est la valeur de la variable denvironnement ayant le nom indiqu, ou NULL si une ne e telle variable nest pas dnie. La notion de variable denvironnement est dnie au niveau du syst`me e e e sous-jacent. void qsort(void *tab, size t nbr, size t taille, int (*comp)(const void *, const void *)) (Quick sort, tri rapide) Cette fonction trie  sur place , par ordre croissant, le tableau dont tab donne ladresse de base, form de nbr objets chacun ayant la taille indique. A cet eet, qsort utilise la e e fonction de comparaison 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 ngative, nulle ou positive selon que e le premier objet est respectivement infrieur, gal ou suprieur au second. Voir la section 6.3.2 pour un e e e exemple dutilisation de cette fonction. int bsearch(const void *ptr, const void *tab, size t nbr, size t taille, int (*comp)(const void *, const void *)) 110
c H. Garreta, 2003

8 GENIE LOGICIEL

8.4

La biblioth`que standard e

(Binary search, recherche dichotomique) Cette fonction recherche dans le tableau tri ayant ladresse de e base donne par tab, form de nbr objets chacun ayant la taille indique, un objet gal ` celui ayant ptr e e e e a pour adresse. Pour cela, elle utilise la fonction de comparaison donne par comp, qui est dnie comme e e pour qsort, ci-dessus. int abs(int x), long labs(long x) Valeur absolue, avec un argument int (resp. long)66 .

8.4.3

Traitement de cha nes : string.h

char *strcpy(char *destin, const char *source) Copie la cha source ` ladresse destin. Rend destin. ne a char *strcat(char *destin, const char *source) Copie la cha source ` la suite de la cha destin. Rend destin. ne a ne int strcmp(const char *a, const char *b) Compare les cha nes a et b pour lordre lexicographique et rend une valeur ngative, nulle ou positive e selon que a est, respectivement, infrieur, gal ou suprieur ` b. e e e a size t strlen(const char *s) Rend le nombre de caract`res de la cha s. Il sagit du nombre de caract`res utiles : le caract`re \0 e ne e e qui se trouve ` la n de toutes les cha a nes nest pas compt. Ainsi, strlen("ABC") vaut 3. e void *memcpy(char *destin, const char *source, size t nombre) Copie la zone mmoire dadresse source de de taille nombre dans la zone de mme taille et dadresse e e destin . Ces deux zones ne doivent pas se rencontrer. void *memmove(char *destin, const char *source, size t nombre) Copie la zone mmoire dadresse source de de taille nombre dans la zone de mme taille et dadresse e e destin . Fonctionne correctement mme si ces deux zones se rencontrent ou se chevauchent. e

8.4.4

Classication des caract`res : ctype.h e

Les lments de cette biblioth`que peuvent tre implants soit par des fonctions, soit par des macros. Les ee e e e prdicats rendent, lorsquils sont vrais, une valeur non nulle qui nest pas forcment gale ` 1 : e e e a 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 dcimal. e int isalnum(int c) c est une lettre ou un chire. int isspace(int c) c est un caract`re despacement : , \t, \n, \r ou \f. e int iscntrl(int c) c est un caract`re de contrle (cest-`-dire un caract`re dont le code ASCII est e o a e compris entre 0 et 31). int isprint(int c) c est un caract`re imprimable, cest-`-dire quil nest pas un caract`re de contrle. e a e o e e int isgraph(int c) c est un caract`re imprimable autre quun caract`re despacement. int ispunct(int c) c est une ponctuation, cest-`-dire un caract`re imprimable qui nest ni un caract`re a e e 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`re c lui-mme. e e
66 La

fonction correspondante pour les rels sappelle fabs (cf. section 8.4.5). e

c H. Garreta, 2003

111

8.4 La biblioth`que standard e

GENIE LOGICIEL

8.4.5 double double double double double double

Fonctions mathmatiques : math.h e sin(double x) rend la valeur de sin x. cos(double x) rend la valeur de cos x. tan(double x) rend la valeur de tan x. asin(double x) rend la valeur de arcsin x, dans , . On doit avoir x [1, 1]. 2 2 acos(double x) rend la valeur de arccos x, dans [0, ]. On doit avoir x [1, 1]. 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 , lorsque u x+ 2 2 y et v y. Pour x = 0 cest la mme chose que arctan x e

double sinh(double x) rend la valeur du sinus hyperbolique de x, soit sh x = double cosh(double x) rend la valeur du double double double double double et double double double double 8.4.6

tanh(double x) rend la valeur de exp(double x) rend la valeur de lexponentielle des, e . log(double x) rend la valeur du logarithme nprien de x, log x. On doit avoir x > 0. e e log10(double x) rend la valeur du logarithme dcimal de x, log10 x. On doit avoir x > 0. e y pow(double x, double y) rend la valeur de x . Il se produit une erreur si x = 0 et y = 0 ou si x < 0 y nest pas entier. sqrt(double x) rend la valeur de x. On doit avoir x 0. ceil(double x) rend la valeur du plus petit entier suprieur ou gal ` x, transform en double. e e a e floor(double x) rend la valeur du plus grand entier infrieur ou gal ` x, transform en double. e e a e fabs(double x) la valeur absolue de x. Limites propres ` limplantation : limits.h, float.h a

ex ex 2 ex +ex cosinus hyperbolique de x, ch x = 2 la tangente hyperbolique de x, th x = chx shx x

Ces chiers en-tte dnissent les tailles et les domaines de variation pour les types numriques. On y trouve : e e e CHAR BIT nombre de bits par caract`re. e CHAR MAX, CHAR MIN valeurs extrmes dun char. e SCHAR MAX, SCHAR MIN valeurs extrmes dun signed char. e UCHAR MAX valeur maximum dun unsigned char. SHRT MAX, SHRT MIN valeurs extrmes dun short. e USHRT MAX valeur maximum dun unsigned short. INT MAX, INT MIN valeurs extrmes dun int. e UINT MAX valeur maximum dun unsigned int. LONG MAX, LONG MIN valeurs extrmes dun long. e ULONG MAX valeur maximum dun unsigned long. Le chier float.h dnit : e FLT DIG prcision (nombre de chires dcimaux de la mantisse). e e FLT EPSILON plus petit nombre e tel que 1.0 + e = 1.0. FLT MANT DIG nombre de chires de la mantisse. e FLT MAX plus grand nombre reprsentable. e FLT MAX 10 EXP plus grand n tel que 10n soit reprsentable. FLT MAX EXP plus grand exposant n tel que FLT RADIXn 1 soit reprsentable. e e e FLT MIN plus petit nombre positif reprsentable (sous forme normalise). n e e FLT MIN 10 EXP plus petit exposant n tel que 10 soit reprsentable (sous forme normalise). FLT MIN EXP plus petit exposant n tel que FLT RADIXn soit reprsentable (sous forme normalise). e e FLT RADIX base de la reprsentation exponentielle. e FLT ROUNDS type de larrondi pour laddition. Ces chiers dnissent galement les constantes analogues pour les double (noms en  DBL ...  ` la place e e a de  FLT ... ) et les long double (noms en  LDBL ... ). 112
c H. Garreta, 2003

Index
( ) (oprateur), 18 e * (oprateur), 22 e *= (oprateur), 30 e ++ (oprateur), 21 e += (oprateur), 30 e , (oprateur), 30 e - (oprateur), 22 e -= (oprateur), 30 e - - (oprateur), 21 e ->(oprateur), 20 e . (oprateur), 20 e /= (oprateur), 30 e = (oprateur), 29 e == (oprateur), 26 e ? : (oprateur), 28 e [ ] (oprateur), 19 e %= (oprateur), 30 e & (oprateur), 22, 27 e &= (oprateur), 30 e && (oprateur), 28 e (oprateur), 27 e = (oprateur), 30 e (oprateur), 21 e |(oprateur), 27 e |= (oprateur), 30 e ||(oprateur), 28 e >(oprateur), 26 e >= (oprateur), 26 e >>(oprateur), 25 e >>= (oprateur), 30 e <(oprateur), 26 e <= (oprateur), 26 e <<(oprateur), 25 e <<= (oprateur), 30 e abort, 110 abs, 111 acc`s ` un champ (oprateur), 20 e a e acc`s relatif, 91 e acos, 112 adresse (oprateur), 22 e adresse dun objet, 22, 60 adresse dune fonction, 72 aectation (oprateur), 29 e appel de fonction, 18, 42, 44 argc, 105 argument formel, 12 arguments des fonctions (passage), 45 arguments en nombre variable, 47 arguments par adresse, 46 argv, 105 arithmtique des adresses, 62 e arithmtiques (oprateurs), 25 e e asin, 112 assert, 109 assert.h, 109 atan, 112 atan2, 112 atof, 109 atoi, 109 atol, 109 automatique (variable), 12 bits (champs de bits), 53 bloc (instruction), 33, 34 break, 33, 39 bsearch, 110 calloc, 110 caract`re, 6 e case, 33, 38 ceil, 112 cha de caract`res, 6, 50 ne e cha de caract`res (initialisation), 51 ne e champs de bits, 53 char, 9 CHAR BIT, 112 CHAR MAX, 112 CHAR MIN, 112 close, 95 commentaire, 4 comparaison (oprateur), 26 e complment ` 1 (oprateur), 21 e a e conditionnelle (compilation), 99 conjonction (oprateur), 28 e conjonction bit-`-bit (oprateur), 27 a e connecteurs logiques (oprateurs), 28 e const, 14, 57 continue, 33, 39 conversion de type, cast (oprateur), 23, 59 e conversions arithmtiques usuelles, 31 e cos, 112 cosh, 112 creat, 95 ctype.h, 111 dcalage de bits (oprateur), 25 e e dclarateur complexe, 55 e default, 33, 38 dene, 97 disjonction (oprateur), 28 e disjonction bit-`-bit (oprateur), 27 a e do, 33, 36 dure de vie (dune variable), 12 e eet de bord, 17 else, 35, 99 en-tte (chier), 101 e endif, 99 enumration, 54 e etiquette, 35 exit, 110 EXIT FAILURE, 110 EXIT SUCCESS, 110 exp, 112 expression conditionnelle (oprateur), 28 e extern, 15

INDEX

INDEX

externe (identicateur), 15 F, f (suxe dune constante), 6 fabs, 112 fclose, 80 feof, 81 ferror, 81 ush, 80 fgetc, 82 fgetpos, 91 fgets, 82 chier binaire, 79 chier de texte, 79 FILE, 80 oat.h, 112 oor, 112 ots, 79 ottante (constante), 6 FLT DIG, 112 FLT EPSILON, 112 FLT MANT DIG, 112 FLT MAX, 112 FLT MAX 10 EXP, 112 FLT MAX EXP, 112 FLT MIN, 112 FLT MIN 10 EXP, 112 FLT MIN EXP, 112 FLT RADIX, 112 FLT ROUNDS, 112 fonction formelle, 72 fopen, 80 for, 33, 37 fprintf, 89 fputc, 83 fputs, 83 fread, 90 free, 110 fscanf, 90 fseek, 90 fsetpos, 91 fwrite, 90 getc, 82 getchar, 82 getenv, 110 gets, 82 globale (variable), 12 goto, 33, 35 hexadcimale (criture dun nombre), 6 e e identicateur, 5 if, 33, 35, 99 ifdef, 99 ifndef, 99 include, 96 indexation (oprateur), 19, 62 e indirection (oprateur), 22 e initialisation dun tableau, 49 initialisation dune cha de caract`res, 51 ne e initialisation dune structure, 52 initialisation dune variable, 12 114

INT MAX, 112 INT MIN, 112 isalnum, 111 isalpha, 111 iscntrl, 111 isdigit, 111 isgraph, 111 islower, 111 isprint, 111 ispunct, 111 isspace, 111 isupper, 111 jmp buf, 106 L, l (suxe dune constante), 6 labs, 111 limits.h, 112 locale (variable), 11 log, 112 log10, 112 logiques (oprateurs), 28 e LONG MAX, 112 LONG MIN, 112 longjmp, 106 lseek, 95 lvalue, 17 macros, 97 main, 105 malloc, 110 math.h, 112 memcpy, 111 memmove, 111 moins unaire (oprateur), 22 e mot-cl, 5 e ngation (oprateur), 20 e e NDEBUG, 109 nombre entier, 6, 8 nombre ottant, 6, 10 NULL, 62 O RDONLY, 95 O RDWR, 95 O WRONLY, 95 octale (criture dun nombre), 6 e oprateur, 5 e open, 95 ordre dvaluation des expressions, 32 e ou exclusif bit-`-bit (oprateur), 27 a e passage des arguments des fonctions, 45 pointeur, 60 post-decrmentation (oprateur), 21 e e post-incrmentation (oprateur), 21 e e pow, 112 pr-decrmentation (oprateur), 21 e e e pr-incrmentation (oprateur), 21 e e e prprocesseur, 96 e printf, 84, 89 priorit des oprateurs, 18 e e
c H. Garreta, 2003

INDEX

INDEX

priv (denticateur), 15 e prototype dune fonction, 41 public (denticateur), 15 putc, 83 putchar, 83 puts, 83 qsort, 110 rand, 109 read, 95 realloc, 110 register, 14 return, 33, 40 rewind, 91 rvalue, 17 squence dchappement, 7 e e scanf, 86, 90 SCHAR MAX, 112 SCHAR MIN, 112 SEEK CUR, 90 SEEK END, 90 SEEK SET, 90 setjmp, 106 setjmp.h, 106 setvbuf, 81 SHRT MAX, 112 SHRT MIN, 112 signal, 107 signal.h, 107 sin, 112 sinh, 112 sizeof, 23 sprintf, 89 sqrt, 112 srand, 110 sscanf, 90 static, 13 statique (variable), 13 stderr, 81 stdin, 81 stdio.h, 80 stdlib.h, 109 stdout, 81 strcat, 111 strcmp, 111 strcpy, 111 string.h, 111 strlen, 111 struct, 51 structure (dclaration), 51 e structure (initialisation), 52 structure (signication dune variable structure), 52 structures rcursives, 75, 77 e switch, 33, 38 system, 110 tableau tableau tableau tableau (dclaration), 49 e (initialisation), 49 (signication dune variable tableau), 49 [dynamique] multidimensionnel, 65

tableau de cha nes de caract`res, 68 e tableau de fonctions, 74 tableau dynamique, 64 taille dun objet (oprateur), 23 e tampon (dentre ou de sortie), 79 e tan, 112 tanh, 112 tmple, 81 tolower, 111 typ numr, 54 ee ee type numr, 10 e ee typedef, 58 types, 8 types dsincarns, 59 e e U, u (suxe dune constante), 6 UCHAR MAX, 112 UINT MAX, 112 ULONG MAX, 112 ungetc, 83 union, 53 USHRT MAX, 112 variable, 10 virgule (oprateur), 30 e void, 42 void *, 61 volatile, 14, 57 while, 33, 36 write, 95

c H. Garreta, 2003

115