cole polytechnique de luniversit de Nice Sophia Antipolis Dpartement de sciences informatiques 930 route des Colles 06903 S OPHIA A NTIPOLIS Cedex, France Tl: +33 4 92 96 50 50 Fax: +33 4 92 96 50 55 URL : http://www.polytech.unice.fr/informatique
Avant-propos
n par Dennis langage C sest impos Conu laune des annesen1960bien entenduRitchie, lepartie au succs dUnix, comme rfrence matire de programmation-systme et de ralisation de logiciel de base. Ceci est li pour systme dexploitation pour lequel C a t conu. Mais cest aussi le rsultat des vertus intrinsques du langage : sufsamment simple pour tre efcace et portable, sufsamment moderne pour tre expressif. Cette origine de langage de programmation-systme a longtemps confr C la rputation dun langage laxiste , dans lequel le typage ntait quune contrainte surajoute, presque dcorative. Les habitudes et les exigences de programmation aidant, il tait ncessaire de rendre plus rigoureux loutil. Cest cette tche que sest consacr le comit de normalisation x3j11 de lansi [2]. Le rsultat fut un langage plus sr, dot du typage fort garant dune certaine scurit de programmation, sans que cela nuise lefcacit des programmes gnrs. Cest ce langage, dit ansi C, adopt par lansi en 1989 puis par liso en 1990 (iso/iec 9899:1990) qui est actuellement la norme de fait. Nous le dsignerons simplement par C90. Cette norme a subi quelques rvisions mineures jusqu ce que lvolution parallle de la normalisation du langage C++ oblige une reprise profonde de la norme. Ceci conduit en 1999, la norme iso/iec 9899:1999 reprise par lansi en 2000. Le nouveau langage, dit C99, nest pas encore ce jour (janvier 2010) compltement implment par tous les compilateurs, bien quun large sous-ensemble le soit gnralement. Le prsent texte dcrit la fois iso C90 et iso C99, en signalant les diffrences entre les deux versions. Ces diffrences sont rsumes dans lannexe 12. Sauf mention explicite du contraire, les programmes proposs sont compatibles avec les deux versions. Pour les dtails, voir la section 1.5. Tous les exemples de cet ouvrage ont t dvelopps et tests dans un environnement tout fait habituel dans les tablissements denseignement et de recherche : stations de travail sous Linux, environnement graphique X Window. Le compilateur ansi C utlis comme rfrence est celui de gnu d initialement Richard Stallman (et la Free Software Foundation), savoir gcc [1, 23]. Les programmes devraient tre compilables avec les versions de gcc partir de 2.95 au moins mais cest seulement partir des versions 3.x que lon a la compatibilit avec C99 (option -std=c99). La compilation seffectue sous le contrle de make (version de gnu l encore). La mise au point utilise gdb [19], autre produit de la Free Software Foundation, utilis travers lune de ses interfaces avec X Window (principalement ddd [20]). Ont galement t utiliss les analyseurs dexcution gprof [21] et
c Jean-Paul R IGAULT
Avant-propos
sont galement introduits certains environnements intgrs de dveloppement pour C, sous Unix ou MS Windows. A Le document lui-mme a t compos laide de L TEX [10, 26] (distribution TEXLive 2009) avec les extensions Babel [7] et frenchb [5] pour traiter la typographie franaise, ainsi que xindy [14] pour gnrer lindex comportant des caractres accentus. Cet ouvrage est le fruit de prs de trente annes denseignement de C et dUnix, lEcole des mines de Paris, au cerics (Centre dEnseignement et de Recherche en Informatique, Communication et Systmes), lESSI (cole Suprieure de Sciences Informatiques de luniversit de Nice Sophia Antipolis, devenue Polytech,Nice-Sophia) et au cours de nombreuses sessions de formation continue inter- et intra-entreprises que jai animes. Je tiens donc remercier tous les collgues, tudiants et participants ces formations pour leur intrt, leur patience, et leur motivation.
Jean-Paul Rigault Professeur dinformatique Universit de Nice Sophia Antipolis Sophia Antipolis et La Roquette sur Siagne Fvrier 1993Janvier 2010
c Jean-Paul R IGAULT
c Jean-Paul R IGAULT
3 Bases du langage 3.1 Elments lexicaux . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Jeu de caractres . . . . . . . . . . . . . . . . . . 3.1.2 Structure lexicale dun chier-source . . . . . . 3.1.3 Commentaires . . . . . . . . . . . . . . . . . . . . 3.1.4 Identicateurs . . . . . . . . . . . . . . . . . . . . 3.1.5 Mots-cls . . . . . . . . . . . . . . . . . . . . . . . 3.1.6 Constantes littrales arithmtiques . . . . . . . . 3.1.7 Chanes de caractres littrales . . . . . . . . . . 3.2 Types scalaires et dclarations simples . . . . . . . . . . 3.2.1 Panorama des types de C . . . . . . . . . . . . . 3.2.2 Type vide (void) . . . . . . . . . . . . . . . . . . 3.2.3 Types de base entiers . . . . . . . . . . . . . . . . 3.2.4 Types rels . . . . . . . . . . . . . . . . . . . . . . 3.2.5 Dnitions dobjets de type de base scalaire . . 3.2.6 Types numrs . . . . . . . . . . . . . . . . . . . 3.2.7 Synonymie de types : typedef . . . . . . . . . 3.3 Oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Oprateurs arithmtiques . . . . . . . . . . . . . 3.3.2 Oprateurs relationnels et logiques . . . . . . . 3.3.3 Oprateurs bit bit . . . . . . . . . . . . . . . . . 3.3.4 Affectations . . . . . . . . . . . . . . . . . . . . . 3.3.5 Oprateurs sur les pointeurs . . . . . . . . . . . 3.3.6 Oprateurs sur les types . . . . . . . . . . . . . . 3.3.7 Oprateurs divers . . . . . . . . . . . . . . . . . 3.4 Evaluation des expressions . . . . . . . . . . . . . . . . 3.4.1 Ordre dvaluation : prcdence et associativit 3.4.2 Conversions . . . . . . . . . . . . . . . . . . . . . 3.4.3 Mlange de types dans les expressions . . . . . 3.5 Instructions et ot de contrle . . . . . . . . . . . . . . . 3.5.1 Instruction simple et bloc . . . . . . . . . . . . . 3.5.2 Instructions de slection . . . . . . . . . . . . . . 3.5.3 Instructions de boucle . . . . . . . . . . . . . . . 3.5.4 Instructions de rupture de squence . . . . . . . 3.6 Exercices du chapitre 3 . . . . . . . . . . . . . . . . . . . 4 Tableaux, structures et unions 4.1 Tableaux . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Tableaux mono-dimensionnels . . . . . 4.1.2 Tableaux multi-dimensionnels . . . . . 4.1.3 Chanes de caractres . . . . . . . . . . 4.1.4 Tableaux de taille variables de C99 . . . 4.2 Structures . . . . . . . . . . . . . . . . . . . . . 4.2.1 Dnition du type et dclarations . . . 4.2.2 Structures compactes et champs de bits 4.2.3 Oprations sur les structures . . . . . . 4.2.4 Tableaux de structures . . . . . . . . . . 4.3 Unions . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Dnition du type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43 43 43 44 45 45 46 46 48 50 50 50 51 53 53 54 55 56 56 58 60 63 64 64 65 66 66 68 70 71 71 72 75 77 79 83 83 83 86 88 88 90 90 93 94 95 96 96
c Jean-Paul R IGAULT
4.4
4.3.2 Oprations sur les unions . . . . . . . . . . . . . . . . . 4.3.3 Structures avec variantes . . . . . . . . . . . . . . . . . Exercices du chapitre 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
97 97 100 103 103 103 105 106 108 109 110 110 111 118 118 119 122 129 129 130 130 130 131 131 131 132 134 134 135 135 135 135 137 137 138 138 138 139 141 141 141 142 142 143 143 144
5 Pointeurs 5.1 Dclarations et oprations sur pointeurs . . . . . . . . . . . . 5.1.1 Pointeurs simples . . . . . . . . . . . . . . . . . . . . . 5.1.2 Pointeurs multiples . . . . . . . . . . . . . . . . . . . . 5.1.3 Oprations arithmtiques sur pointeurs . . . . . . . . 5.1.4 Conversions de pointeurs . . . . . . . . . . . . . . . . 5.1.5 Pointeurs et constantes . . . . . . . . . . . . . . . . . 5.2 Pointeurs sur structures et unions . . . . . . . . . . . . . . . 5.2.1 Oprateur de slection che . . . . . . . . . . . . 5.2.2 Types rcursifs . . . . . . . . . . . . . . . . . . . . . . 5.3 Pointeurs et tableaux . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Tableaux de pointeurs ; lecture des dclarations de C 5.3.2 Relations entre tableaux et pointeurs . . . . . . . . . 5.4 Exercices du chapitre 5 . . . . . . . . . . . . . . . . . . . . . . 6 Le prprocesseur ANSI C 6.1 Prtraitement des programmes C . . . . . . . . . . . . . . 6.2 Inclusion de chier . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Effet de linclusion de chier . . . . . . . . . . . . 6.2.2 Recherche du chier inclure . . . . . . . . . . . 6.2.3 Utilisation de linclusion de chier . . . . . . . . . 6.3 Dnition de macros . . . . . . . . . . . . . . . . . . . . . 6.3.1 Macros sans arguments : dnition de constantes 6.3.2 Macros arguments : fonctions en ligne . . . . . 6.3.3 Dnition rcursive de macros . . . . . . . . . . . 6.3.4 Concatnation et stringication . . . . . . . . 6.3.5 Annulation de dnition : #undef . . . . . . . . . 6.3.6 Macros rserves . . . . . . . . . . . . . . . . . . . 6.4 Compilation conditionnelle . . . . . . . . . . . . . . . . . 6.4.1 Les directives de compilation conditionnelle . . . 6.4.2 Inclusion unique de chiers . . . . . . . . . . . . . 6.5 Autres directives du prprocesseur . . . . . . . . . . . . . 6.5.1 Numro de ligne : #line . . . . . . . . . . . . . . 6.5.2 Messages derreur : #error . . . . . . . . . . . . 6.5.3 Commentaire excutable : #pragma . . . . . . . . 6.6 Exercices du chapitre 6 . . . . . . . . . . . . . . . . . . . . 7 Fonctions 7.1 Arguments et valeur de retour . . . . . . . . . . . . . . 7.1.1 Type de retour dune fonction . . . . . . . . . . 7.1.2 Type des arguments dune fonction . . . . . . . 7.1.3 Mode de passage des arguments . . . . . . . . . 7.2 Dclaration, dnition et appel de fonction . . . . . . . 7.2.1 Dclaration du type dune fonction : prototype 7.2.2 Dnition dune fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
c Jean-Paul R IGAULT
7.3
7.4 7.5
7.2.3 Appel dune fonction . . . . . . 7.2.4 Fonctions en ligne de C99 . . Fonctions et pointeurs . . . . . . . . . . 7.3.1 Fonction retournant un pointeur 7.3.2 Pointeurs en argument . . . . . . 7.3.3 Pointeurs sur fonctions . . . . . La fonction main . . . . . . . . . . . . . Exercices du chapitre 7 . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
145 146 146 146 147 148 151 153 155 155 156 156 158 158 158 159 161 161 162 163 164 164 165 166 167
8 Structure des programmes 8.1 Espaces de nommage . . . . . . . . . . . . . . . . . . . . . . . . 8.2 Structure des programmes C . . . . . . . . . . . . . . . . . . . 8.2.1 Compilation spare . . . . . . . . . . . . . . . . . . . . 8.2.2 Unit de compilation . . . . . . . . . . . . . . . . . . . . 8.3 Dure de vie et rgles de visibilit . . . . . . . . . . . . . . . . 8.3.1 Dure de vie . . . . . . . . . . . . . . . . . . . . . . . . . 8.3.2 Visibilit des objets . . . . . . . . . . . . . . . . . . . . . 8.3.3 Initialisation des objets . . . . . . . . . . . . . . . . . . . 8.3.4 Visibilit des fonctions . . . . . . . . . . . . . . . . . . . 8.3.5 Synthse : relation entre dure de vie et visibilit . . . 8.3.6 Autres rgles de visibilit . . . . . . . . . . . . . . . . . 8.4 Programmation modulaire en C . . . . . . . . . . . . . . . . . 8.4.1 Notion de module . . . . . . . . . . . . . . . . . . . . . 8.4.2 Objets globaux trouvs dans une interface de module 8.4.3 Exemple dorganisation modulaire . . . . . . . . . . . . 8.5 Exercices du chapitre 8 . . . . . . . . . . . . . . . . . . . . . . .
9 La bibliothque standard 171 9.1 lments gnraux . . . . . . . . . . . . . . . . . . . . . . . . . 171 9.1.1 Assertions : <assert.h> . . . . . . . . . . . . . . . . . 171 9.1.2 Codes derreur : <errno.h> . . . . . . . . . . . . . . . 171 9.1.3 Dnitions communes : <stddef.h> . . . . . . . . . . 171 9.1.4 Utilitaires gnraux : <stdlib.h> . . . . . . . . . . . 172 9.2 lments numriques . . . . . . . . . . . . . . . . . . . . . . . . 172 9.2.1 Boolens : <stdbool.h> . . . . . . . . . . . . . . . . . 172 9.2.2 Types entiers : <stdint.h> . . . . . . . . . . . . . . . 172 9.2.3 Conversion des types entiers : <inttypes.h> . . . . 172 9.2.4 Environnement pour calcul en nombres rels : <fenv.h> 172 9.2.5 Nombres complexes : <complex.h> . . . . . . . . . . 173 9.3 Fonctions mathmatiques . . . . . . . . . . . . . . . . . . . . . 173 9.3.1 Bibliothque mathmatique de base : <math.h> . . . 173 9.3.2 Bibliothque mathmatique gnrique : <tgmath.h> 173 9.4 Caractres et chanes de caractres . . . . . . . . . . . . . . . . 173 9.4.1 Manipulation de caractres : <ctype.h> . . . . . . . . 173 9.4.2 Manipulation de chanes de caractres : <string.h> 173 9.4.3 Manipulation de caractres tendus: <wctype.h> . . 174 9.4.4 Manipulation de chanes de caractres tendus : <wchar.h> 174 9.4.5 Localisation : <locale.h> . . . . . . . . . . . . . . . . 174 9.5 Entres-sorties : <stdio.h> . . . . . . . . . . . . . . . . . . . 174
c Jean-Paul R IGAULT
9.6
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fonctions nombre variable darguments : <stdarg.h> Date et heure : <time.h> . . . . . . . . . . . . . . . . . Traitement dvnements : <signal.h> . . . . . . . . Points de reprise : <setjmp.h> . . . . . . . . . . . . . Notation alternative de certains oprateurs : <iso646.h> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174 175 175 175 175 176 177 177 178 178 179 180 182 182 183 184 184 185 189 189 189 189 189 189 189 189 189 189 189 189 189 189 189 191 191 191 191 193 195 203
10 Environnement de dveloppement 10.1 Compilateurs . . . . . . . . . . . . . . . . . . . . . . 10.2 Dveloppement traditionnel sous Unix . . . . . . . 10.2.1 dition de source . . . . . . . . . . . . . . . . 10.2.2 Loutil make . . . . . . . . . . . . . . . . . . . 10.2.3 Mise au point avec gdb et compagnie . . . . 10.2.4 Outils de documentation . . . . . . . . . . . 10.3 Environnements intgrs de dveloppement . . . . 10.3.1 Environnements multi-plateformes . . . . . 10.3.2 Environnements spciques Unix . . . . . 10.3.3 Environnements spciques MS Windows 10.4 Remarque nale . . . . . . . . . . . . . . . . . . . . . 11 Extensions avances de C99 11.1 Prprocesseur . . . . . . . . . . . . . . . . . . . . . 11.1.1 Macros prdnies . . . . . . . . . . . . . . 11.1.2 Macros nombre variable darguments . . 11.2 langage de base . . . . . . . . . . . . . . . . . . . . 11.2.1 Nom de la fonction courante : __func__ . 11.2.2 Pointeurs restreints : restrict . . . . . . 11.2.3 Tableaux . . . . . . . . . . . . . . . . . . . . 11.3 Bibliothque standard . . . . . . . . . . . . . . . . 11.3.1 Fichiers dentte . . . . . . . . . . . . . . . 11.3.2 Type boolen . . . . . . . . . . . . . . . . . 11.3.3 Types entiers tendus . . . . . . . . . . . . 11.3.4 Nombres complexes . . . . . . . . . . . . . 11.3.5 Manipulation des caractres multiples . . 11.3.6 Rels en virgule ottante . . . . . . . . . . . . . . . . . . . . . . . .
12 C traditionnel, iso C90, iso C99 et C++ 12.1 Diso C90 C traditionnel . . . . . . . . . . . . . . . . . . . . . 12.2 Diso C90 iso C99 . . . . . . . . . . . . . . . . . . . . . . . . . 12.3 Diso C90 ou iso C99 C++ . . . . . . . . . . . . . . . . . . . . Bibliographie Glossaire Index
c Jean-Paul R IGAULT
c Jean-Paul R IGAULT
Chane de compilation C . . . . . . . . . . . . . . . . . . . . . .
c Jean-Paul R IGAULT
10
c Jean-Paul R IGAULT
. . . . . . . . . . . .
11
c Jean-Paul R IGAULT
12
c Jean-Paul R IGAULT
13
c Jean-Paul R IGAULT
14
c Jean-Paul R IGAULT
15
c Jean-Paul R IGAULT
16
c Jean-Paul R IGAULT
Chapitre 1
Introduction
1.1 Historique
Le langage C trouve son origine au dbut des annes 1970, lorsque Dennis Ritchie rejoint Ken Thomson aux Bell Laboratories. Ce dernier a dj dni en 1970 le langage B, driv de BCPL, pour implmenter de manire portable le systme dexploitation quil vient dinventer et quil a dnomm Unix. En 1972, C voit le jour et est utilis pour rcrire compltement le noyau dunix (en un t!). Le langage recevra une dnition de rfrence par la publication du livre de Brian Kernighan et Dennis Ritchie en 1978 [16]. Le langage dcrit dans ce livre, avec quelques extensions ultrieures (affectation et passage en arguments de structures et dunions, introduction du type void), constitue ce quil est convenu dappeler le C traditionnel . Le dveloppement considrable de C, dabord entrain par celui dUnix puis relativement autonome, rendit ncessaire une normalisation qui fut entreprise partir de 1983 par le comit x3j11 de lansi 1 . Les travaux se terminrent n 1988, par une norme ansi reprise par ieee puis par liso (norme iso-9899). La deuxime dition du livre de Kernighan et Ritchie [17] dcrit le langage ainsi normalis. La compatibilit arrire a t lun des soucis de lansi, et la plupart des programmes crits en C traditionnel peuvent tre recompils avec ansi C. Mais, sans rien changer la philosophie du langage, ansi C a apport de nombreuses amliorations allant la fois dans le sens de la scurit de programmation (typage fort, meilleure rigueur smantique) et dans celui de la portabilit (par exemple dnition de la bibliothque dexcution minimale). La version C99 est galement largement compatible avec iso C90. Malheureusement de nombreux compilateurs C ne la supporte pas encore compltement. Ainsi, parmi les compilateurs majeurs, gcc 3 et 4 supportent lessentiel de C99, mais pas la totalit et Microsoft Visual Studio 2005 (C++ version 8) et 2008 (version 9) ne prtendent mme pas supporter cette norme mme sils en ont des parties du fait de leur compatibilit C++. Bref, C99 nest pas encore trs rpandu dix ans aprs sa normalisation ! Cest pourquoi tous les programmes donns ici, sauf mention spciale, sont compatibles avec C90 aussi bien quavec C99.
1. American National Standards Institute
17
c Jean-Paul R IGAULT
18
1. Introduction
1.2 Caractristiques de C
1.2.1 Un langage contemporain de PASCAL
C a t conu peu prs en mme temps que des langages comme Pascal et il rete donc ltat de lart de lpoque. Cest donc un langage qui supporte la programmation structure (on dit aussi procdurale). Il dispose en particulier de types structurs dnissables par lutilisateur, analogues aux records de Pascal.
c Jean-Paul R IGAULT
1.3. Plan
19
rednissant de manire indpendante de la plate-forme les fonctions du systmes dexploitation (entres-sorties, gestion de chiers, de mmoire, gestion des threads , etc.), et jusquau processeur lui-mme (grce la machine virtuelle) ! Cependant, noublions pas que C est lorigine un langage conu pour raliser des systmes dexploitation ce qui, nous lavons vu, lamne proposer des fonctionnalits de bas niveau . Conjuger portabilit et bas niveau conduit donc ce minimalisme de C. Par ailleurs, lpoque, un des slogans favoris tait small is beautiful ! Ces fonctionnalits indispensables sont dlgues un bibliothque dexcution standard (la clbre standard library). ansi C a fait un gros effort pour normaliser cette bibliothque, au moins dans ses fonctionnalits minimales.
1.3 Plan
Le chapitre 2 permet, travers une srie dexemples simples, de faire un tour rapide du langage C. Les chapitres 3, 4 et 5 reprennent en dtail respectivement la description du langage de base (types, oprateurs et expressions, instructions de contrle), celle des agrgats (tableaux, structures et unions) et enn celle des pointeurs. Les chapitres 7 et 8 dcrivent en dtail la notion de fonction et la structure des programmes. Y est galement aborde la programmation modulaire en C. Le prprocesseur ansi C est tudi en dtail au chapitre 6. Lenvironnement dexcution avec une description succinte des bibliothques standards est lobjet du chapitre 9, et lenvironnement de dveloppement celui du chapitre 10. Enn le chapitre 11 rsume les extensions introduites par C99 et qui nont pas t traites dans le reste du texte. Quant au chapitre 12 il synthtise les diffrences entre C traditionnel , C90, C99 et C++.
Dans ces extraits, les identicateurs composs en police oblique, comme instruction et condition dans lextrait suivant, sont remplacer par des expressions ou instructions terminales :
while (condition) instruction
c Jean-Paul R IGAULT
20
1. Introduction
Les programmes complets (ceux qui gurent dans la Liste des programmes, A page 13) sont afchs par le paquettage L TEX listings. Ils sont donc dcors syntaxiquement pour mettre en vidence les mots-cls, chanes de caractres littrales, constantes, etc. En outre, les lignes sont numrotes. Le programme 2.1 du chapitre suivant en fournit un exemple. On pourra stonner de ne pas trouver de caractres accentus dans ces programmes. Cela est d au fait que jutilise le codage des caractres Unicode (utf-8 pour tre prcis) qui nest correctement A support ni par le paquettage listings de L TEX ni par les compilateurs C90. En revanche, nous verrons en 11.3.5 quun certain support est prvu en C99. Lorsque lon prsente un exemple dinteraction entre lutilisateur et le systme, on suppose toujours que cette interaction a lieu sous un des shell dUnix (ou de Linux, de Cygwin. . .). La syntaxe suppose le shell du Steve Bourne sh ou lun de ses descendants comme bash, ksh, ou zsh. . . (personnellement jutilise zsh). Linteraction est prsente comme suit :
% ls -F Stack CVS/ Makefile % Stack* Stack.c Stack.h Stack_main.c
Ici % reprsente la chane de sollicitation (le prompt) envoy par le shell, le texte en oblique gras (comme ls -F Stack) est celui entr par lutilisateur, et le texte droit (comme CVS/) celui mis par le systme. Les caractres de contrle comme ctrl-d (simulation de n de chier au terminal sous Unix) sont reprsents sous la forme usuelle utilisant le prxe ^ (D). Par ailleurs lorsque ces exemples utilisent des accents, ils supposent un systme Unix ou Linux correctement congur. Ainsi la variable denvironnment LANG doit prciser un encodage 8 bits comme iso88591, iso88591@euro, ou utf8. Par exemple, la valeur de ma propre variable LANG est en_US.UTF-8 (je naime pas avoir les messages du systme en franais !).
c Jean-Paul R IGAULT
21
C99
Ensuite, pour compiler un programme tenant tout entier dans un seul chier source, disons prog.c, il suft dexcuter la commande make prog qui va produire (sil ny a pas derreur) un chier excutable de mme nom que le source mais sans lextension .c (donc ici, prog), quil sufra dexcuter son tour :
% make prog % prog ... affichage des resultats de prog ...
Lorsquune Makefile est ncessaire, elle est dcrite dans le texte. En outre on trouvera des complments sur make en 10.2.2.
Avertissements (warnings) lors de la compilation
Noter que nous compilons toujours avec tous les warnings activs (option de compilation -Wall). Un programme C doit tre exempt de tels warnings. Certains peuvent vous sembler sans consquence, mais il faut beaucoup de discernement et dexprience pour en tre sr.
c Jean-Paul R IGAULT
22
1. Introduction
autres et qui va directement lessentiel. Il contribue aussi promouvoir un certain style de programmation bien dans la philosophie du small is beautiful chre Unix. Bref, cet ouvrage est hautement recommand. Bien entendu, sil couvre la norme ansi 89 (iso C90), il naborde absolument pas les extensions de C99. Pour cette dernire version de la norme, il existe quelques ouvrages rcents comme celui de Kochan [8] ou encore, si vous aimez les gros livres trs dtaills, de Prata [3]. Parmi les (nombreux) ouvrages directement crits en franais, citons louvrage de Claude Delannoy [12], auteur renomm pour sa pdagogie, ainsi que le livre trs complet dAchille Braquelaire [27]. Ce dernier dborde le cadre de ce cours puisquil prsente aussi la programmation-systme avec lapi Posix (cest--dire avec Unix ou ses clones comme Linux). Un grand nombre de sites sur lInternet ont des sections consacrs au langage C. En particulier le trs intressant site franais developpez.com [29] et son forum [28] permettent dchanger des informations couvrant de nombreux langages et systmes, dont C et C++. Invitables dans tous ces sites interactifs, les interventions incomptentes, les jugements premptoires et non motivs, mmes les dbuts dengueulades, sans parler des orthographes douteuses sy rencontrent beaucoup moins quailleurs, favorisant des informations prcises et utiles. Hberg par developpez.com, le site de Nicolas Joseph [6] fournit dexcellents rsums sur la compatibilit entre C90 et C99, ainsi que des conseils de programmation, des piges viter et des liens sur dautres ressources concernant C. Si, au del de C, vous dcidez de vous intresser C++, le livre de linventeur de ce langage, Bjarne Stroustrup, dans sa troisme dition [24] peut tre un dbut moins que vous prfriez une approche plus progressive avec le rcent livre du mme Stroustrup, vritable introduction la programmation en gnral [25] ou encore avec [11, 4]. En ce qui concerne la compatibilit entre C(90 ou 99) et C++, on peut consulter lexcellent rsum d David Tribble [9].
Choisissez votre diteur. vriez quil est correctement congur, quil sait mettre en valeur (par changement de polices ou de couleurs) la syntaxe du langage. Explorez la manire dont il collabore avec make, vous permettant de lancer vos compilations et surtout de rcuprez les messages derreurs sans quitter lditeur. Si votre diteur na pas cette dernire fonctionnalit, changezen avant quil ne soit trop tard ! Si vous dcidez dutiliser un environnement intgr de dveloppement (ide) comme kdevelop ou eclipse, apprenez lutiliser correctement, dnir et grer des projets, interagir avec make et les dbogeurs.
c Jean-Paul R IGAULT
23
Si vous tes sous MS Windows et que vous dcidez dutilisez Microsoft Visual Studio, sachez que vous disposerez dun excellent ide condition de savoir lutiliser. Laide en ligne sur les bibliothques C est galement de haute qualit 3 . Malheureusement, avec Visual Studio, vous naurez pas un compilateur rellement compatible avec la norme C99. Par ailleurs il vous faudra au moins la version 2005. Une autre possibilit sous MS Windows est dapprendre utiliser lun des multiples ide disponibles et qui sont souvent connects un compilateurs gcc: voir 10.
Exercice 1.2 (Accs la documentation) Elle doit se faire en ligne. Tout systme de type Unix dispose de la clbre commande man qui permet daccder la descriptions des commandes shell, des fonctions de bibliothque, des
chiers systme, etc. Apprenez lutiliser correctement (faites donc la commande man man), elle ou lune de ses interfaces comme info ou autre. Notez que les fonctions des bibliothques standards de C se trouvent dans la section 2 ou 3 du manuel. Vous pouvez galement recourir un site Web fournissant une description dtaille de la bibliothque standard. Il y en a un grand nombre que Google se fera un plaisir de vous indiquer. Un bon point de dpart, comme souvent, est Wikipdia 4 .
3. http://msdn2.microsoft.com/en-us/library/ 4. http://en.wikipedia.org/wiki/C_standard_library. Cest la version en anglais, beaucoup plus complte que celle en franais.
c Jean-Paul R IGAULT
24
1. Introduction
c Jean-Paul R IGAULT
Chapitre 2
Premiers pas
chapitre rapidement en unes des Ceprincipalespasselangagelangage etrevue quelques en dtail caractristiques du C qui seront reprises par la suite. Il constitue un tour rapide du introduit au passage quelques fonctions dentre-sortie utiles. Le but est de permettre au lecteur de rdiger rapidement quelques programmes simples. La prsentation utilise une suite dexemples assez classiques : lun des programmes les plus simples possibles, avec le clbre "hello, world!" ; un programme de copie de chier, introduisant la notion de boucle (ici une boucle while) et des fonctions simples dentre-sortie ; un programme introduisant les notions de fonction, dexpression arithmtique et dentre-sortie formatte (calcul de la racine carre dun nombre rel) ; un programme de tri par insertion an dintroduire la boucle for et la notion de tableau ; enn le calcul du terme de rang n de la suite de Fibonacci, prsent comme un exemple de fonction rcursive.
25
c Jean-Paul R IGAULT
26
2. Premiers pas
On peut en prfrer la version franaise en 2.2. Programme 2.2 Le programme hello, world localis en franais
/***** Fichier: salut.c *****/ #include <stdio.h>
5
Ce programme (prenons la version amricaine 2.1 par exemple) est compos de trois entits : 1. un commentaire (entre /* et */), 2. une directive au prprocesseur (la ligne dbutant par #), 3. une dnition de fonction constitue de toutes les lignes suivantes. La directive au prprocesseur
#include <stdio.h>
permet dimporter les dclarations des fonctions dentre-sortie de la bibliothque standard, cest--dire les prototypes de ces fonctions qui prcisent les types des arguments et le type de retour. Ici, une seule fonction de bibliothque est directement utilise : printf. Les directives au prprocesseur sont reconnaissables au dise (#) qui doit tre le premier caractre de la ligne (autre quun blanc ou une tabulation horizontale). Le nom de cette directive (include) voque son mcanisme : il sagit dune inclusion textuelle du chier stdio.h. Les piquants (<...>) indiquent que ce chier est chercher dans des rpertoires par dfaut dpendant de linstallation (ici sans doute /usr/include). Le programme ne comporte quune seule fonction, nomme main. Par convention expresse, la fonction main est celle par laquelle commence lexcution dun programme C. La dnition de la fonction main comporte lentte de la fonction
int main()
qui en prcise le nom et surtout la signature : ici une fonction sans argument type de retour entier 1 (int pour integer). Le corps de la fonction main est un bloc, reconnaisable au fait quil est encadr par une paire daccolades :
{ printf("hello, world!\n"); }
Ici, ce bloc ne comporte quune instruction simple, linvocation (ou lappel) de la fonction printf avec un argument effectif qui est une chane de caractres littrale "hello, world!n". Cette fonction afche la chane de caractres sur la sortie standard (stdout), suivie dune n de ligne reprsente par la
1. La signature de la fonction main est impose parmi un nombre de choix limit (voir 7.4).
c Jean-Paul R IGAULT
27
squence \n (newline). Noter le point-virgule nal : toute instruction simple et toute dclaration de C se termine par un point-virgule. Supposons que ce programme ait t saisi, laide dun diteur de texte quelconque, dans le chier hello.c. Il ne reste plus qu le compiler, par exemple en utilisant la commande make (que nous considrons comme la seule commande de compilation sous Unix 2 ) :
% make hello gcc -g -std=c99 -Wall -o hello hello.c %
puis excuter le chier binaire produit (qui a t nomm hello par loption -o de la commande prcdente) :
% hello hello, world! %
10
La premire ligne du corps de la fonction main est une dnition de variable locale :
int c;
Le nom de la variable est c et son type est int pour integer, cest--dire entier (sign). Le reste du corps de main est une boucle while qui a la structure suivante :
while (condition) corps
2. Pour la conguration de make, voir le chapitre 10. Ici, les rgles par dfaut doivent sufre si les variables denvironnement ont t correctement positionnes comme indiqu en 1.5.2; il ny a pas besoin de dnir de Makefile.
c Jean-Paul R IGAULT
28
2. Premiers pas
Une telle boucle excute son corps, constitu par une instruction simple ou un bloc, tant que la condition, qui est une expression entire quelconque, a une valeur non nulle. La condition est value et teste avant chaque itration, y compris la premire. La boucle while est une des trois formes de boucles dont dispose C (avec for et do ... while voir 3.5.3). Ici la condition
(c = getchar()) != EOF
est une expression relationnelle car != est loprateur dingalit. Dans a != b les deux expressions a et b sont values et compares : si elle sont diffrentes, la valeur de lexpression a != b est 1, sinon cest 0 3 . C possde aussi loprateur dgalit == (attention ==, deux signes = colls !), ainsi que les oprateurs de comparaison (<, <=, >, >=). Loprande droit de != est la valeur prdnie EOF (voir ci-aprs). Loprande gauche
c = getchar()
est une expression daffectation, = tant loprateur daffectation. La valeur de lexpression de droite devient la nouvelle valeur de lobjet rfrenc par lexpression de gauche (ici la variable c). La valeur de lexpression daffectation elle-mme est la nouvelle valeur de lobjet en partie gauche. Donc
(c = getchar()) != EOF
invoque la fonction getchar, copie sa valeur de retour dans la variable c, et compare cette nouvelle valeur de c la constante EOF. La boucle while sera excute tant que cette comparaison donnera une valeur non nulle (cest--dire tant que la nouvelle valeur de c nest pas EOF). Noter que les parenthses sont indispensables pour grouper laffectation, sinon
c = getchar() != EOF
sinterprte comme
c = (getchar() != EOF)
illustre bien ce type de programmation avec effet de bord puisque cette expression non seulement compare c EOF, mais encore le fait aprs avoir modi c. En liminant leffet de bord, on aurait pu crire la boucle while sous une forme plus habituelle pour un programmeur Pascal, mais certainement moins idiomatique pour un programmeur C :
c = getchar(); while (c != EOF) { putchar(c); c = getchar(); }
3. Contrairement C99 et C++, C90 ne possde pas de type de donne boolen mais que ce sont les valeurs entires 0 et 1 qui sont utilises pour jouer les rles respectifs de faux et vrai voir 3.3.2 et 11.3.2.
c Jean-Paul R IGAULT
29
Notez la ncessit de rpter linstruction c = getchar(), une (lgre) nuisance. La fonction getchar lit le caractre suivant sur lentre standard et retourne sa valeur. En cas n de chier dtecte par le systme dexploitation, getchar retourne la valeur EOF, prdnie dans stdio.h et qui ne correspond aucune valeur possible de caractre 4 . La boucle while se terminera donc lorsque la n de chier sera atteinte sur lentre standard. Enn, le corps de la boucle est constitu par une instruction simple qui est linvocation de la fonction putchar. Celle-ci afche le caractre quelle reoit en argument (ici c) sur la sortie standard. Le programme, dont le source est dans le chier copy.c, peut donc servir recopier un chier dans un autre grce au mcanisme de redirection du shell :
% make copy gcc -g -std=c99 -Wall % copy > fic ceci est le texte qui recopi sur la sortie D % cat fic ceci est le texte qui recopi sur la sortie % copy < fic > fic1 % cat fic1 ceci est un texte qui recopi sur la sortie % cmp fic fic1 %
sera standard
sera standard
On rappelle que le caractre ctrl-d (eot) reprsent par D simule une n de chier au terminal. Par ailleurs, la commande cmp compare 2 chiers : labsence dafchage indique que les deux chiers sont identiques loctet prs.
avec la condition initiale u0 = t, par exemple. Le programme comporte, au plus haut niveau, sept lments. Trois directives au prprocesseur qui, comme prcdemment, permettent dimporter les prototypes des fonctions de la bibliothque standard :
4. Ceci explique que getchar retourne un entier (int) et non pas un caractre (char) et que la variable locale c doive tre dclare comme un entier.
c Jean-Paul R IGAULT
30
2. Premiers pas
10
15
/* precision maximale */
30
35
if (t < 0.0) { fprintf(stderr, "argument negatif pour square_root\n"); exit(1); } while (fabs(previous - current) > EPS) { previous = current; current = 0.5 * (current + t / current); } return current; }
Nous avons dj remarqu que linclusion de stdio.h permet dimporter les prototypes des fonctions dentre-sortie de la bibliothque standard ; celle de stdlib.h concerne une bonne partie du reste des prototypes de cette bibliothque (par exemple celui de la fonction exit) ; quant celle de math.h, elle importe les fonctions de la bibliothque mathmatique comme fabs. Un prototype dclarant la fonction square_root, car celle-ci est utilise avant dtre dnie (on parle de dclaration en avant (forward declaration) :
c Jean-Paul R IGAULT
31
double square_root(double);
Cette fonction a un argument qui est un rel double prcision (type double) et retourne une valeur de mme type. La dnition de la constante globale EPS, galement de type double, initialise 106 . La dnition des deux fonctions main et square_root. On note que le format est libre et que lindentation et le changement de ligne sont largement utiliss pour rendre le programme lisible (ils nont pas dautre signication pour le compilateur que celle de sparateurs). La fonction main utilise une variable locale x, de type double. La valeur de x est lue grce la fonction de bibliothque scanf. Le premier argument de scanf est une chane de caractres qui spcie le format de lecture : ici, la spcication %lg signie quon cherche lire un rel dans toute prsentation raisonnable (le g), et que ce rel est double prcision (le l, comme long ). Le deuxime argument de scanf est ladresse de la variable que lon cherche lire, ici celle de x. En effet loprateur & sert prendre ladresse dun objet (il retourne un pointeur). De manire gnrale, les arguments de scanf doivent tre des pointeurs 5 . Lavant-dernire instruction de main afche le rsultat de linvocation de la fonction square_root avec largument x. L encore le premier argument de printf est un format o la spcication %g indique que lon veut afcher un rel double prcision dans un format raisonnable pour sa valeur. On constate au passage que printf et scanf sont des fonctions nombre variable darguments. Bien entendu, linvocation de exit termine lexcution du programme, en renvoyant au shell la valeur de son argument comme code de retour. Par convention, la valeur 0 indique que tout sest bien pass 6 . Il nest pas ncessaire dappeler systmatiquement exit la n de main, mais ce nest pas une mauvaise pratique puisque cela permet de matriser le code de retour transmis au shell. Une autre possibilit quivalente est de remplacer exit(code) par return code (par exemple ici, return 0) mais videmment ceci nest quivalent exit que pour le retour de main, pas dune autre fonction ! Notez aussi que le chier dentte <stdio.h> dnit les deux constantes entires EXIT_SUCCESS (avec la valeur 0) et EXIT_FAILURE (1) ce qui permet de remplacer les utilisations de exit ou return prcdentes par exit(EXIT_SUCCESS) ou return EXIT_SUCCESS. Aprs la fonction main est dnie une constante globale EPS initialise la valeur 106 . Enn, on a dj remarqu que les commentaires se placent entre /* et */. Puis vient la dnition de la fonction square_root elle-mme. Elle vrie dabord que largument t nest pas ngatif. Linstruction if est lune des deux instructions de slection de C (voir 3.5.2). Elle peut prendre la forme simple suivante
5. Essayez donc dexcuter ce programme en oubliant de prendre ladresse de x, cest--dire en crivant : texttscanf("%lg", x) 6. Rappelons que ce code de retour peut tre consult par les instructions de contrle conditionnelles du shell : if, while, until...
c Jean-Paul R IGAULT
32
2. Premiers pas
if (condition) instruction-si-vrai
Sa signication est vidente. Les deux instructions sont soit des instructions simples soit des blocs 7 . La fonction fprintf est analogue printf, mais son premier argument prcise le ux de sortie, ici lerreur standard stderr 8 . Le cur de square_root est un boucle while o current est la valeur courante de un et previous la valeur prcdente (i.e., un1 ). La fonction de bibliothque fabs calcule la valeur absolue relle double prcision (son prototype est dans math.h). La boucle sexcute tant que lcart en valeur absolue entre un et un1 est suprieur EPS. Le corps de la boucle tant constitu de deux instructions simples (deux instructions daffectation en fait), il faut donc l encore les englober dans un bloc. Lexpression arithmtique
0.5 * (current + t / current)
se comprend sans problme. C dispose entrautres, des oprateurs arithmtiques habituels : addition (+), soustraction (-), multiplication (*), division (/). Ces oprateurs sappliquent des valeurs entires ou relles : attention, si ses deux oprandes sont entiers, / est la division entire. A la n de la boucle, la valeur de current est le rsultat cherch et est donc transmise lappelant grce linstruction return qui termine la fonction. Il ne reste plus qu compiler et excuter ce chier (supposons quil soit nomm square_root.c) :
% make square_root gcc -g -std=c99 -Wall -o square_root square_root.c % square_root entrez un nombre? 64 result = 8 % square_root entrez un nombre? 2 result = 1.41421 % square_root entrez un nombre? 0 result = 0 % square_root entrez un nombre? 1.E+18 resultat = 1e+09 % square_root entrez un nombre? -12 argument negatif pour square_root %
7. Remarquer labsence du mot-cl then habituel dans dautres langages. 8. printf(...) est donc quivalent fprintf(stdout, ...).
c Jean-Paul R IGAULT
33
Dans le cas de printf (ou de fprintf), tout ce qui nest pas une spcication de conversion est imprim tel quel. Ainsi
printf("hello, world!\n");
suivi dune n de ligne. Il existe un grand nombre de possibilits de spcications de conversion; voici quelques unes des plus frquentes :
%d %o %x %c %s %lf %le %lg %% conversion dun entier en dcimal conversion dun entier en octal conversion dun entier en hexadcimal afchage dun caractre individuel afchage dune chaine de caractres conversion dun rel double en format xe conversion dun rel double en format avec exposant conversion dun rel double en format gnral le caractre % lui-mme
Le format, comme toute chane, peut contenir des squences dchappement qui reprsentent certains caractres non imprimables. Ces squences se composent toutes du caractre \ suivi dun seul caractre. Nous avons dj rencontr \n, voici les plus frquentes :
9. La discussion qui suit sapplique aussi au deuxime argument de fprintf ou de fscanf dont le premier argument dsigne le ux dentre-sortie.
c Jean-Paul R IGAULT
34
2. Premiers pas
\n \t \f \0
n de ligne tabulation horizontale (tab) saut de page (form feed) le caractre nul (nul)
Le caractre nul joue un rle fondamental comme terminateur de chane de caractres (voir 3.1.7). Ne pas confondre ce caractre nul avec la constante NULL dsignant le pointeur nul (voir 5.1.1).
Conversions en entre
Nous avons dj vu que les arguments de scanf sont des pointeurs. Les formats et les spcications de conversion ont pratiquement la mme syntaxe que pour printf, tout au moins en premire approximation. Le texte autre que les spcications de conversion et les espaces doit tre prsent dans lentre effective. Les espaces eux ne servent que de sparateurs et pour le reste sont ignors (le blanc, \n, \t, \f sont des espaces). Lexcution de scanf se termine lorsque toutes les spcications de conversion ont t satisfaites ; elle est abandonne prmaturment ds quune conversion ne peut tre effectue. Dans tous les cas, la valeur de retour est le nombre de conversions effectivement ralises. A titre dexemple, considrons le programme 2.5 (chier scanf.c) qui lit un entier (i) et un rel (x) et les afche avec le nombre dobjets lus (n). Programme 2.5 Programme simple de lecture de scalaires
/***** Fichier: scanf.c *****/ #include <stdio.h>
5
int main() { int i; double x; int n; n = scanf("%d toto %lg", &i, &x); printf("n = %d, i = %d, x = %lg\n", n, i, x); }
10
c Jean-Paul R IGAULT
35
12 toto 3 n = 2, i = 12, x = 3 % scanf 12toto3 n = 2, i = 12, x = 3 % scanf 12toto 3 n = 2, i = 12, x = 3 % scanf 12 3 n = 1, i = 12, x = 1.9064e-310 % scanf 12 to n = 1, i = 12, x = 1.9064e-310 % scanf aaaaaa 12 toto 3 n = 0, i = -1081747800, x = 1.9064e-310 %
Les valeurs tranges de x et i (en rouge) sont bien videmment le symptome que ces variables nont pas t initialises (dailleurs, on remarque que n est infrieur 2 dans ces cas). Les exemples prcdents montrent, sil en est besoin, que scanf est une fonction extrmement dlicate manipuler! lavenir, nous lviterons le plus possible!
la dnition de la fonction main. La fonction insertion_sort ralise lagorithme dcrit prcdemment. Si le nombre dlments trier n vaut 1 (ou moins) il ny rien faire. Linstruction return termine alors lexcution de la fonction. Cette instruction peut
10. Les meilleurs algorithmes de tri squentiels sont en O(n log n) comparaisons.
c Jean-Paul R IGAULT
36
2. Premiers pas
15
20
if (n <= 1) return; for (i = 1; i < n; i++) { current = tab[i]; for (j = i - 1; j >= 0; j--) { if (tab[j] <= current) break; else tab[j + 1] = tab[j]; } tab[j + 1] = current; } }
25
#define NMAX 1000 int main() { int n, i; int t[NMAX]; printf("entrez une liste dentiers terminee par EOF? "); for (n = 0; n < NMAX && scanf("%d", &t[n]) != EOF; n++) /* Rien */; insertion_sort(t, n); /* Fait tout le travail */
30
35
40
printf("liste triee = "); for (i = 0; i < n; i++) printf("%d ", t[i]); putchar(\n); return 0; }
c Jean-Paul R IGAULT
37
tre suivie dune expression dont lvaluation fournit la valeur retourne par la fonction. Mais ici, la fonction square_root a pour type de retour void ce qui signie quelle ne retourne aucune valeur (plutt quune fonction, cest une procdure). La boucle for permet de parcourir le tableau tab, pass en argument, dont la dimension (le nombre de composantes) est n. Malgr sa syntaxe trange, la boucle for est assez simple comprendre. En fait
for (initialisation; entretien; rebouclage) corps
Linstruction i++ est lincrmentation de i, ici quivalente i = i + 1. Cette boucle for excute son corps pour les valeurs de i variant de 1 n-1 inclus.
c Jean-Paul R IGAULT
38
2. Premiers pas
Lexpression tab[i] dsigne bien sr la composante dindice i du tableau tab. A noter quen C les indices dbutent 0. La boucle interne (indexe par j) parcourt la liste vers la gauche, partir de la position courante, en dcalant au fur et mesure les lments pour faire de la place pour insrer current. Elle se termine soit sur linstruction break qui linterrompt prmaturment, soit lorsque j vaut -1. Dans les deux cas, la valeur courante de j est, 1 prs, celle de lindice o il convient dinsrer current.
Une forme plus idiomatique de la boucle de tri
A cause de lquivalence mentionne avec la boucle while, la boucle interne (en j) aurait pu prendre une forme plus idiomatique en utilisant loprateur et logique && : for (j = i - 1; j >= 0 && tab[j] > current; j--) { tab[j + 1] = tab[j]; } Par ailleurs, le corps de la boucle ne comportant quune seule instruction, les accolades sont inutiles : for (j = i - 1; j >= 0 && tab[j] > current; j--) tab[j + 1] = tab[j]; C possde aussi loprateur ou logique reprsent par ||. Ces deux oprateurs (|| et &&) sont valus de gauche droite, et lvaluation sinterrompt ds que le rsultat est dtermin. La fonction main dnit un tableau local t de dimension maximale NMAX. Ce tableau est initialis composante par composante grce scanf. On remarque que le corps de la boucle est vide, tout le travail ayant t fait par effet de bord dans la condition dentretien. A la n de la boucle, n est le nombre dlments lus. La liste trie par invocation de insertion_sort est imprime lment par lment grce printf. On noublie pas dajouter une n de ligne la n de la liste. Noter la diffrence entre les chanes de caractres littrales (entre ") et les caractres littraux (entre ).
39
Le programme 2.7 calcule un . La fonction fibo est une implmentation directe de la formule prcdente. Elle sinvoque rcursivement. La rcursion se termine car n dcroit chaque appel et nit donc par tre infrieur 2. Programme 2.7 Calcul du terme de rang n de la suite de Fibonacci
/***** Fichier: fibo.c *****/ #include <stdio.h> #include <stdlib.h>
5
10
int fibo(int n) { if (n < 2) return n; else return fibo(n-1) + fibo(n-2); } int main(int argc, char *argv[]) { int i; if (argc == 2) { i = atoi(argv[1]); } else { fprintf(stderr, "usage: fibo n\n"); exit(1); } printf("fibo(%d) = %d\n", i, fibo(i)); exit(0); }
15
20
25
La valeur de n est obtenue grce aux arguments de la ligne de commande qui correspondent aux arguments de la fonction main. On sait que sous le shell une commande est invoque par son nom avec une ventuelle liste darguments qui sont des chanes de caractres, comme dans
% ls -lg -t -d /usr/bin /usr/local/bin
A lexcution de la commande correspond celle dun programme et donc dune fonction main si ce programme a t crit en C. Les arguments de main permettent de rcuprer les arguments positionnels de la commande shell : lentier argc est le nombre darguments positionnels y compris le nom de la commande (dans lexemple de ls, argc vaut 6) ; le tableau de chanes de caractres argv, dont la dclaration sera explicite en 5.3.2, contient lui les arguments positionnels, cest-dire ici les six chanes "ls", "-lg", "-t", "-d", "/usr/bin" et "/usr/local/bin".
c Jean-Paul R IGAULT
40
2. Premiers pas
La fonction de bibliothque standard atoi 11 transforme une chane de caractres (ici le premier argument positionnel de la commande, soit argv[1]) en entier, si toutefois cela a un sens. La fonction fibo aurait pu tre crite de manire plus idiomatique en utilisant une expression conditionnelle :
int fibo(int n) { return (n < 2) ? n : fibo(n-1) + fibo(n-2);\\ }
se lit: si a alors b sinon c . Elle svalue comme cette lecture le laisse supposer. Voici un exemple dutilisation :
% make fibo gcc -g -std=c99 -Wall -o fibo fibo.c % fibo 20 fibo(20) = 6765 % fibo 25 fibo(25) = 75025 % fibo 1 fibo(1) = 1 %
Une modication lmentaire de la fonction fibo permet de voir la rcursivit luvre (chier fibo1.c) :
int fibo(int n) { int r; printf("entre dans fibo --- n = %d\n", n); if (n < 2) r = n; else r = fibo(n-1) + fibo(n-2); printf("sort de fibo --- n = %d, fibo = %d\n", n, r); return r; }
Voici le rsultat :
% fibo1 4 entre dans fibo -entre dans fibo -entre dans fibo -entre dans fibo -sortie de fibo -- n entre dans fibo -sortie de fibo -- n
11. pour ASCII to integer
n n n n = n =
= 4 = 3 = 2 = 1 1, fibo = 1 = 0 0, fibo = 0
c Jean-Paul R IGAULT
41
sortie de fibo -- n entre dans fibo -sortie de fibo -- n sortie de fibo -- n entre dans fibo -entre dans fibo -sortie de fibo -- n entre dans fibo -sortie de fibo -- n sortie de fibo -- n sortie de fibo -- n fibo(4) = 3 %
= n = = n n = n = = =
= 1 = 1 = 2
= 1 = 0 = 1 = 3
On peut reprsenter ce calcul rcursif sous forme dun arbre comme dans la gure 2.2. La fonction fibo value cet arbre de la gauche vers la droite, en profondeur dabord , cest -dire en valuant effectivement les feuilles (indiques en bleu) dabord.
lun des exemples de ce chapitre. Compiler et excuter cet exemple. Noter la coloration syntaxique (syntax highlighting) utilise par votre diteur (la plupart des diteurs actuels en disposent). Si votre diteur le permet ou si vous utilisez un environnement intgr comme kdevelop, eclipse, visual studio ou dev-c++, habituez-vous compiler et corriger vos erreurs sans quitter cet diteur ou cet environnement : cela fait gagner beaucoup de temps et vite les manipulations stupides 12 !
Exercice 2.2 (Copie de chiers) Modier copy (programme 2.3) pour quil im-
c Jean-Paul R IGAULT
42
2. Premiers pas
Exercice 2.3 (La commande iota) crire le programme iota qui, lorsquil est invoqu depuis le shell avec largument n produit la suite des entiers de 1 n ;
par exemple
% iota 7 1 2 3 4 5 6 7 %
prime la table de conversion des degrs Celsius en degrs Farentheit entre -50C et +110C par pas de 5C , ainsi que la table inverse de -60F +220 F par pas de 10F. On rappelle la formule de conversion : si C est la temprature en C et F celle en F, on a F= 9 C + 32 5
ver fibo, cest--dire remplacer les appels rcursifs par des boucles.
Exercice 2.7 (Amlioration du calcul de la racine carre) Considrez le pro-
gramme de calcul de la racine carre par la mthode de Newton (programme 2.4). Excutez-le pour de petites valeurs de x, par exemple de lordre de 106 . Que se passe-t-il ? Identiez le problme. Quelle solution peut-on trouver pour amliorer la situation ? Vriez le rsultat.
Suggestion
Si un est la suite utilise dans la mthode de Newton, que vaut la limite suivante ?
n
lim
u n +1 un
Exercice 2.8 (Mauvais format) crire un programme qui utilise printf pour
imprimer un rel (float ou double) en format %d, un entier (int) en format %f et un caractre (char) en format %d. Faites le mme exercice mais en lecture, avec scanf. Que concluez-vous ?
Remarque
Pour cet exercice, ignorez les messages davertissement (warnings) ventuels du compilateur. Pouvez-vous imaginer un moyen de faire taire ces messages ?
c Jean-Paul R IGAULT
Chapitre 3
Bases du langage
dcrit du langage lexiOnscalaires, ici les lments fondamentaux instructions. C. .),: lments doncaux (identicateurs, sparateurs, constantes diverses. types de nes oprateurs et expressions, enn
Il importe de remarquer que tout programme C qui fait une hypothse implicite sur le jeu de caractres utilis est par essence non portable. Un programme qui nutilise que le code ascii est en principe sr. Les codes dans lesquels un caractre est cod sur 8 bits peuvent galement tre manipuls sans trop de problme dans les chanes de caractres et seront rendus correctement si votre environnment est adquat (variables LANG par exemple). Ds que lon veut utiliser des jeux de caractres multi-octets comme Unicode, il vaut mieux se mettre en mode C99. Les fonctions correspondantes sont dcrites en 11.3.5.
1. mais pas ncessairement dans le codage ascii!
C99
43
c Jean-Paul R IGAULT
44
3. Bases du langage
Table 3.1 Jeu de caractres ASCII 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 nul soh stx etx eot enq ack bel bs tab nl vt ff cr so si 1 dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us 2 sp ! " # $ % & ( ) * + , . / 3 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 4 @ A B C D E F G H I J K L M N O 5 P Q R S T U V W X Y Z [ \ ] ^ _ 6 a b c d e f g h i j k l m n o 7 p q r s t u v w x y z { | } ~ del
C99
Au cas o le jeu de caractres disponible serait insufsant (!), la norme ansi C dnit une reprsentation particulire de certains caractres spciaux, dnomme trigraph . Les trigraphs comportent 3 caractres (!) dont les deux premiers sont ?? et sont remplacs par leur caractre quivalent dans tout contexte (y compris les chanes de caractres et les constantes litrales). On se porte aussi bien en ignorant cette bizarrerie 2 mais il convient dviter le double point dintrrogation en particulier dans les commaentaires (voir 3.1.3) et constantes littrales reprsentant des caractres individuels (voir3.1.6) ou des chanes (voir 3.1.7) ! Ajoutant le saugrenu la bizarrerie, C++a aussi dni des squences de deux caractres (donc des digraphs ) permettant de reprsenter aussi une partie de caractres spciaux. C99 sest prcipit pour adopter cette ide gniale. Ainsi les crochets carrs ([ et ]) peuvent-ils tre reprsents par <: et :>, les accolades ({ et }) par <% et %>, le dise (#) par %: et le double dise (##) videmment par %:%:. Cela namliore certes pas la lisibilit ! Contraitrement aux trigraphs, les digraphs ne sont pas substitus lintrieur dun commentaire ni dune constante littrale (chane ou caractre) ; en fait leur substitution na lieu que sils reprsentent par euxmmes un lment lexical (un token voir 3.1.2). Il est peu probable que vous ayez utiliser les digraphs...
c Jean-Paul R IGAULT
45
Les sparateurs permettent disoler les autres lments lexicaux. Les oprateurs sont aussi des sparateurs. Les espaces, cest--dire le blanc (sp), la n de ligne (nl), les tabulations horizontale (tab ou ht) ou verticale (vt) et le saut de page (ff) sont des sparateurs purs en ce sens que pour le reste ils sont ignors.
3.1.3 Commentaires
En C90 les commentaires sont encadrs entre /* et */ et peuvent stendre sur plusieurs lignes mais ne peuvent pas simbriquer :
/* Un long commentaire sur 3 lignes */ /* Une /* erreur de syntaxe */ coup sr ! */
Ce type de commentaire est quivalent un espace pour le compilateur (cest donc un sparateur). C99 autorise aussi les commentaires la C++ commenant par // et stendant jusqu la n de la ligne. Il est possible dimbriquer un commentaire C90 dans un commentaire C99 ou linverse :
// un commentaire-ligne /* Toute une section en commentaire, y compris ses commentaires-lignes x = 0; // plus de x y = 1; // encore un y / *
C99
3.1.4 Identicateurs
Les identicateurs permettent de nommer des objets (constantes, variables, fonctions). Ils commencent par une lettre ou le caractre soulign (_) suivi dune squence de lettres, de chiffres ou de souligns. Par exemple :
x j21 Prix_HT _strcmp X a2ps Square_Root __DATE__ toto char_0_to_9 num_secu __STDC__ NBUF PascalStyleId _1993
On rappelle que les majuscules et les minuscules sont considres comme des caractres diffrents : Square_Root et square_root ne sont pas le mme identicateur.
Portabilit : jeu de caractres tendu pour les identicateurs
En C90, seul le code ascii peut tre utilis pour coder les identicateurs (noms de constantes, variables, fonctions, macros. . .). En revanche, C99 permet lutilisation des caractres tendus (Unicode)
C99
c Jean-Paul R IGAULT
46
3. Bases du langage
dans les identicateurs. Cependant, peu de compilateurs ralisent actuellement cette fonctionalit (en particulier gcc-4.2 ne la supporte pas). On vitera donc les identifcateurs comme t42 ou .
Portabilit : identicateurs rservs
Les identicateurs dbutant par un double caractre soulign (_) sont rservs des usages spciaux (voir 6.3.6) et ne devraient pas tre utiliss en dehors de ces usages. En principe, les noms de fonctions de la bibliothque normalise (fopen, printf, exit... voir 9) ne devraient pas tre utiliss pour dautres usages. Enn, les mots-cls (voir 3.1.5) sont strictement rservs.
Portabilit : longueur dun identicateur
La norme ansi C rclame que les 31 premiers caractres au moins dun identicateur soient pris en compte sauf pour les identicateurs dobjets externes qui sont manipuls par lditeur de liens. Pour ces derniers, les 6 premiers caractres au moins doivent tre signicatifs. Les compilateurs et les diteurs de liens modernes ne placent pratiquement aucune limite sur la longueur des identicateurs. Se mer cependant : quant la modernit, les diteurs de liens sont bien plus rares que les compilateurs !
3.1.5 Mots-cls
Les mots-cls de C sont strictement rservs. Ils ne doivent pas tre utiliss comme identicateurs sinon le programme devient incompilable. Tous les mots-cls sont en minuscules ; ils sont au nombre de 32 pour C90 et le tableau 3.2 en donne la liste. Table 3.2 Mots-cls de C auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
C99
La norme C99 ajoute 5 mots-cls supplmentaires (table 3.3). Table 3.3 Mots-cls supplmentaires de Cnew restrict inline _Complex _Imaginary _Bool
c Jean-Paul R IGAULT
47
La squence a reprsente le caractre a. Une constante caractre est donc un caractre encadr de simples quotes (). Lorsque le caractre nest pas imprimable, on peut utiliser une squence dchappement comme \n. La table 3.4 donne la liste complte de ces squences. Table 3.4 Squences dchappement pour les caractres
\a \b \f \n \r \t \v \\ \0 \ \" \?
alarme retour arrire saut de page saut de ligne retour chariot tabulation horizontale tabulation verticale le \ lui-mme le caractre de code 0 la simple quote elle-mme la double quote elle-mme le point dinterrogation
alarm backspace form feed new line carriage return horizontal tab vertical tab backslash nul
A titre dexemple, le programme 3.1 remplace chaque n de ligne de lentre standard par un blanc. Programme 3.1 Remplacement des ns de ligne par un blanc
/***** Fichier: rm_nl.c *****/ #include <stdio.h>
5
int main() { int c; while ((c = getchar()) != EOF) { if (c == \n) c = ; putchar(c); } putchar(\n); }
10
15
La norme permet de reprsenter tout caractre par son code octal ou hexadcimal. Ainsi A, \101 et \x41 sont trois reprsentations quivalentes de la constante caractre A. Conformment la remarque dj faite en 3.1.1, ceci peut tre une cause de non-portabilit.
c Jean-Paul R IGAULT
48
3. Bases du langage
ansi C supporte trois reprsentations principales des constantes entires littrales : dcimale, constitue des chiffres de 0 9 (0, 123, 2812727837...) ; octale, dbutant par le caractre 0 et ne comportant que les chiffres de 0 7 (0123, 0177) ; hexadcimale, dbutant par le prxe 0x ou 0X et constitue des chiffres de 0 9 et des lettres de a f en majucules ou minuscules indiffremment (0x10 , 0xff, 0XaaBBcc00...). Une constante entire littrale peut tre prcde dun signe (+ ou -). Ce signe ne fait pas rellement partie de la constante mais dsigne en fait loprateur correspondant (voir 3.3.1). Il existe de nombreuses variations de syntaxe des constantes entires qui correspondent aux diffrents sous-types des entiers et qui seront tudies en 3.2.3.
Constantes relles littrales
Les constantes relles littrales peuvent prendre deux formes : virgule xe avec une partie entire et une partie fractionnaire (dcimales) spares par un point (2.718285, 0.314159...) ; exponentielle, identique la virgule xe avec en plus un facteur dchelle introduit par le caractre e ou E et qui reprsente un exposant en base 10 (6.02e+23 pour 6, 02 1023 , 1.e-6 pour 106 ...). Les deux remarques prcdentes sur la prsence possible dun signe et lexistence de variations sappliquent ici aussi (voir 3.2.4 pour les variations).
Une chane de caractres littrale ne peut tre coupe par un retour la ligne : ceci
printf("hello, world \n");
est illgal. Cependant quand des chanes littrales sont adjacentes (cest--dire uniquement spares par des espaces), elles sont concatnes par le compilateur :
printf("hello, " "world" "\n");
est quivalent
printf("hello, world\n");
c Jean-Paul R IGAULT
49
En mmoire, une chane littrale est reprsente par la suite des caractres qui la constitue (sans les doubles quotes bien sr) termine par le caractre nul (\0). Ainsi, une chane occupe-t-elle un caractre de plus que sa longueur utile (gure 3.1). Cette proprit, qui est impose par le compilateur pour les chanes littrales, est une convention qui doit tre respecte par le programmeur en manipulant des chanes variables. A titre dexemple, les deux programmes 3.2 et 3.3 utilisent cette proprit de terminaison des chanes : le premier pour dterminer la longueur utile, le second pour recopier une chane dans une autre noter que le caractre nul nal est lui aussi recopi. Programme 3.2 Longueur utile dune chane de caractres
/***** Fichier: string_length.c *****/ #include <stdio.h>
5
int string_length(const char orig[]) { int i; for (i = 0; orig[i] != \0; i++) {} return i; } int main() { printf("%d\n", string_length("hello, world\n")); }
10
15
Pour ces deux programmes, on remarque que largument orig est pass sous forme dun tableau de caractres constants
const char orig[]
ce qui signie que les lments du tableau ne sont pas modis dans le corps de la fonction (voir 7.3.2). En revanche, le tableau dest, paramtre de copy_string, est videmment modiable, dou labsence de const. Noter aussi le corps vide de la boucle de la fonction string_length : tout le travail est en effet ralis dans lentte de linstruction for.
Attention : a et "a"
c Jean-Paul R IGAULT
50
3. Bases du langage
void copy_string(char dest[], const char orig[]) { int i; for (i = 0; orig[i] != \0; i++) dest[i] = orig[i]; dest[i] = \0; /* copie du nul terminal */ } int main() { char str[100]; copy_string(str, "hello, world\n"); printf(str);
10
15
20
de caractres "a" rduite a et dont la reprsentation en mmoire comporte en fait deux caractres (a et \0).
c Jean-Paul R IGAULT
51
Le type int constitue la reprsentation naturelle 3 des valeurs entires avec les oprations habituelles (voir 3.3.1). Cependant plusieurs sous-types sont dnis, rpartis en deux catgories indpendantes selon la possibilit dtre sign ou non dune part, et selon le nombre de valeurs reprsentables dautre part. Un entier non sign (unsigned) est toujours positif et obit aux lois de larithmtiques modulo 2n , o n est le nombre de bits de sa reprsentation. Le tableau 3.6 rsume toutes les variations possibles des entiers, avec les contraintes minimales imposes par la norme ansi C sur lensemble des valeurs reprsentables. Lexamen de ce tableau appelle un certain nombre de commentaires et de remarques : 1. Les modieurs de type (signed, unsigned, short, long...) sont optionnels et signed est le dfaut, sauf pour char. Le type de base peut galement tre omis, cest alors int. Donc le type signed short int est quivalent short int ou mme short tout court. ; 2. Les charactres sont considrs comme des (petits) entiers et le fait que, par dfaut, ils soient signs ou non dpend de limplmentation ; 3. Les entiers ordinaires sont garantis tre reprsents sur 16 bits au moins ; en fait les valeurs indiques sont des contraintes minimales et une implmentation peut les dpasser : ainsi sur la plupart des processeurs mots
3. La reprsentation est naturelle au sens du matriel : cest le type dentier que le processeur manipule le plus aisment. Sur nos machines actuelles, cest gnralement un mot de 32 bits, voire de 64.
c Jean-Paul R IGAULT
52
3. Bases du langage
de 32 bits, un int est cod sur 32 bits (ce qui donne un intervalle de valeurs de -2 147 483 648 +2 147 483 647 pour un signed en complment 2) et de 0 4 294 967 295 pour un unsigned). 4. Les intervalles de valeurs effectivement utiliss par une implmentation sont dnis dans le chier denvironnement <limits.h>. Table 3.6 Sous-types de int Nom du type char signed char unsigned char signed short int signed int long int unsigned short int unsigned int unsigned long int Intervalle minimal de valeurs 0 .. 255 ou -127 .. +127 -127 .. +127 0 .. 255 -32 767 .. +32 767 -32 767 .. +32 767 -2 147 483 647 .. +2 147 483 647 0 .. 65 535 0 .. 65 535 0 .. 4 294 967 295
Le respect des intervalles de valeurs du tableau 3.6 permet dassurer la portabilit. Le ou sur le signe ou labsence de signe du type char doit inciter la prudence : si lon veut utiliser les caractres comme de petits entiers et faire dessus des oprations arithmtiques, il est prudent de spcier explicitement si lon veut des unsigned char ou des signed char.
Une constante caractre correspondant un caractre du jeu de caractres (ASCII par exemple) comme a ou \n est de type char et est garantie positive. Une constante entire littrale en notation dcimale, comme 123 ou encore 1 512 827 304, a le premier parmi les types suivants qui permet de reprsenter sa valeur : int, long, unsigned long. Si la reprsentation est octale ou hexadcimale, les types tests sont dans lordre : int, unsigned, long, unsigned long. On peut forcer une constante entire littrale, quelle que soit sa reprsentation, tre dun des sous-types des entiers en utilisant les sufxes suivants : u ou U pour unsigned, l ou L pour long. Voici quelques exemples :
123U 123L 123UL 123LU unsigned int long unsigned long unsigned long
c Jean-Paul R IGAULT
53
La norme C99 a fait un gros effort pour rendre plus portables et plus rigoureuses les manipulations de nombres rels. Ceci est voqu en 11.3.6.
C99
La syntaxe dune dclaration de variable dun type de base scalaire est immdiate : elle a la forme
nom-du-type liste-didentificateurs ;
Par exemple :
int i; short int i, j, k; long l1, l2;
seuls j et l sont initialiss. Si une variable nest pas initialise lors de sa dnition, C utilise des rgles par dfaut qui dpendent du contexte de dnition (voir 8.3.3) : dans certains cas la variable nest pas initialise du tout. Moralit : si une variable doit tre initialise, elle doit ltre explicitement.
Modieurs de type : const et volatile
Une dnition peut tre prcdes de const : lobjet est alors une constante (symbolique) : sa valeur ne pourra plus tre modie. Une consquence immdiate est quune dnition de constante comporte ncessairement son initialisation.
c Jean-Paul R IGAULT
54
3. Bases du langage
const int LINE_LENGTH = 100; const double PI = 3.141592; const double AVOGADRO = 6.02E+23;
Notez quil est usuel (mais conventionnel) de nommer les constantes avec des majuscules. Une dnition de variable peut galement tre prcde de volatile. Ceci signie que la variable peut changer de valeur en dehors de la volont du programme (cest--dire sans quil y ait de modication de cette variable dans le programme). Cest le cas des variables qui reprsentent des registres dentresortie de contrleurs et dont la valeur peut tre change par le monde extrieur. Un autre est constitu par les segments de mmoire partage entre processus. La prsence de volatile indique au compilateur quil doit viter certaines optimisations. Par exemple dans le cas de lextrait de programme
void test_io(void) { volatile int io_register = 0; while (io_register == 0) { sleep(1); /* attendre 1 seconde */ } }
un compilateur astucieux pourrait, en labsence de la dclaration volatile, sapercevoir que io_register nest pas modi dans le corps de la boucle, qutant une variable locale, il ne peut tre modi ailleurs, et que lon peut donc transformer la fonction en la forme quivalente suivante comportant une boucle innie et sans la variable locale puisquelle ne sert rien !
void test_io(void) { while (1) /* boucle infinie */ { } }
La prsence de volatile, en signalant la possibilit que io_register puisse tre modi lextrieur du programme empche cette optimisation : ltat de de io_register sera donc bien vri toutes les secondes jusqu ce quil devienne non nul. Une constante peut tre volatile :
const volatile double vc;
Cela signie que lobjet vc ne peut tre modi par programme mais peut ltre par lextrieur. Dans ce cas, la dnition de constante nexige pas dinitialisation.
c Jean-Paul R IGAULT
55
dnit-il un type, de nom enum color 4 . On peut dnir des objets du type et les affecter entre eux :
enum enum ... c1 = c2 = color c1; color c2 = GREEN; c2; RED;
Le type devient anonyme et il sera donc impossible de dnir plus tard dautres objets du mme type. En fait, les valeurs que peut prendre un objet de type numr sont des entiers, dbutant 0 par dfaut. Dans les exemples prcdents, VERT vaut 0 et ALICE vaut 3. On peut mme forcer les valeurs que lon souhaite pour certains lments :
enum instruction { LOAD = 0xfa, STORE, ADD = 0x10, SUB, MUL, DIV, JUMP = 0x20, HALT = 0xff, };
Dans une certaine mesure (voir cependant 3.4.2) on peut mlanger les objets de types numrs et les entiers. Deux types numrs diffrents ne peuvent avoir des valeurs de mme nom :
enum color {GREEN, ORANGE, RED}; enum fruit {APPLE, PEAR, ORANGE}; /* erreur! */
c Jean-Paul R IGAULT
56
3. Bases du langage
dnit le nom U_int comme tant synonyme de unsigned int. On peut donc maintenant dclarer ou dnir des objets de type U_int :
U_int u1, u2; U_int u3 = 12u; U_int add = 0xFFFF;
Cette instruction permet de donner un nom plus manipulable aux types numrs. Par exemple, en supposant que le type enum color soit dj dni, mais pas enum doc :
typedef enum color Colors; typedef enum doc {ARTICLE, REPORT, BOOK} Document; typedef enum {MALE, FEMALE} Sex; typedef enum {FALSE, TRUE} Boolean; ... Colors c = GREEN; Document d, d1 = REPORT; Sex sx = MALE; Boolean flag = FALSE;
Noter que typedef dnit une simple synonymie, et non de la cration dun nouveau type. Ainsi, aprs
Length l; Surface s;
les deux variables s et l sont de mme type (double) et on peut par exemple crire laffectation
s = l;
3.3 Oprateurs
Les oprateurs combins avec les identicateurs et les constantes littrales ou symboliques permettent dcrire des expressions. Chaque expression a un type et une valeur, rsultat de son valuation. Le tableau 3.7 donne la liste de tous les oprateurs du langage avec leur signication et la rfrence de la section o ils sont tudis.
c Jean-Paul R IGAULT
3.3. Oprateurs
57
ansi C connait deux oprateurs arithmtiques unaires : + et -. Cinq oprateurs binaires sont dnis : addition (+), soustraction (-), multiplication (*), division (/), modulo (%). Noter quil nexiste pas doprateur dexponentiation. La division est la division entire si les deux oprandes sont entiers. Lopration modulo est possible seulement sur les entiers, pas sur les rels : a%b donne le reste de la division entire de a par b. A titre dexemple, le programme 3.4 calcule de manire rcursive le Plus Grand Commun Diviseur de deux entiers (qui sont donns en arguments de la ligne de commande). En voici quelques exemples dexcution :
% gcd 0 1 gcd(0, 1) = 1 % gcd 12 7
c Jean-Paul R IGAULT
58
3. Bases du langage
gcd(12, 7) = 1 % gcd 24 12 gcd(24, 12) = 12 % gcd 27 63 gcd(27, 63) = 9 % gcd -27 63 gcd(-27, 63) = 9 % gcd -27 -63 gcd(-27, -63) = 9 % gcd 0 0 gcd(0, 0) = 0 %
Portabilit : signe du reste et du quotient
Pour la division entire et le modulo, le signe du quotient (a/b) et du reste (a%b) dpend de limplmentation. Cest bien entendu une calamit, et on doit donc viter les oprandes ngatifs pour ces deux oprations. En particulier, remarquez les prcautions qui sont prises dans le programme 3.4.
C99
Les oprateurs relationnels sont utilisables avec tout type scalaire entier ou rel. Ils sont au nombre de six : <, >, <=, >=, == et !=. Noter que loprateur dgalit est == et non pas = ce dernier est loprateur daffectation (3.3.4).
Remarque sur lidentit entre boolens et entiers
Le fait quune expression comme a < b retourne un entier (0 ou 1) permet dcrire des expressions tranges comme 4*(a < 0) + 8*(a < 4) + 16*(a < 8) Ce style de programmation est en gnral douteux.
Portabilit : comparaison des rels
Comme souvent dans les langages de programmation, on doit tre trs prudent dans les tests entre rels : le rsultat peut dpendre de la prcision du processeur.
La valeur de vrit dun expression arithmtique e, note VV (e) est dnie ainsi : vrai si e = 0 VV (e) = faux si e = 0
c Jean-Paul R IGAULT
3.3. Oprateurs
59
20
int gcd(int a, int b) { int a0 = abs(a); /* abs est defini dans <stdlib.h> */ int b0 = abs(b); int p = max(a0, b0); int q = min(a0, b0); return (q == 0) ? p : gcd(q, p%q); }
25
35
if (argc != 3) { fprintf(stderr, "usage: gcd a b\n"); return 1; } x = atoi(argv[1]); y = atoi(argv[2]); printf("gcd(%d, %d) = %d\n", x, y, gcd(x, y)); return 0; }
40
c Jean-Paul R IGAULT
60
3. Bases du langage
En principe e peut tre de nimporte quel type arithmtique, mais, comme on la dj remarqu, on doit se mer du test 0 des rels. Il vaut donc mieux se limiter des expressions entires. Noter aussi que mme les valeurs (strictement) ngatives donnent la valeur de vrit vrai.
Ngation logique
Dans lexpression a&&b, loprateur && dnote le et logique (conjonction) des deux valeurs de vrit des expressions a et b. Cependant lvaluation seffectue squentiellement de gauche droite et sinterrompt ds que la valeur de lexpression est dtermine, cest--dire au premier oprande faux. Ainsi, compte tenu de lassociativit de cet oprateur, dans
a$_1$ && a$_2$ && ... && a$_n$
les expressions ai sont values dans lordre, et lon sarrte la premire dont la valeur de vrit est faux, le rsultat global tant alors faux galement. Dans cet autre exemple
int t[100]; ... if (n < 100 && t[n] == 0) ...
lexpression t[n] nest value que si n est infrieur 100, vitant ainsi le potentiel dbordement dindice. Loprateur || est dual de && : cest le ou logique (disjonction). Son valuation est elle-aussi squentielle, de gauche droite, et sarrte donc la premire expression qui la valeur vrai.
et
OU
bit bit
Les deux oprateurs & et | ralisent respectivement le et et le ou bit bit de leurs oprandes. Loprateur ^ ralise le ou exclusif.
c Jean-Paul R IGAULT
3.3. Oprateurs
61
Dcalages
Une expression comme a<<b dcale la valeur de a gauche de b positions de bits alors que a>>b effectue le mme dcalage mais vers la droite.
Portabilit des oprations de dcalage
La portabilit des oprations de dcalage nest garantie que si les deux conditions suivantes sont runies : 1. le premier oprande (celui que lon dcale) est unsigned int ; 2. le second oprande (la valeur du dcalage) est un entier positif ou non sign de valeur infrieure au nombre de bits correspondant la reprsentation du type du premier oprande. Dans ces cas a<<b (resp. a>>b) est quivalent la multiplication (resp. la division) par 2b . Dans tous les autres cas, la valeur du rsultat est laisse la discrtion de limplmentation ! Le programme 3.5 illustre cette pathologie du dcalage. Voici le rsultat de son excution : % chkshift ui = 2147483647, (ui << 1) = -2 i = 2147483647, (i << 1) = -2 j = -1, (j >> 1) = -1 k = -1073741825, (k >> 1) = 2147483646 %
10
int main() { unsigned int ui = 0x7FFFFFFF; int i = ui; int j = -1; int k = 0xBFFFFFFF; printf("ui = %d, (ui << 1) printf("i = %d, (i << 1) = printf("j = %d, (j >> 1) = printf("k = %d, (k >> 1) = return 0; }
/* 01111...11 */
/* 10111...11 */
15
= %d\n", ui, ui << 1); %d\n", i, i << 1); %d\n", j, j >> 1); %d\n", k, k << 1);
Exemple
An dillustrer ces oprations de manipulation de bits, le programme 3.6 ralise des oprations courantes de test et de positionnement dun bit dans un tableau de bits (ici un unsigned char). Noter lutilisation de la fonction (en fait la macro) assert dnie dans le chier dentte <assert.h>.
c Jean-Paul R IGAULT
62
3. Bases du langage
10
unsigned char set_bit(Bit_Array bits, unsigned int n) { assert(n < CHAR_BIT); return bits | (1 << n); } unsigned char reset_bit(Bit_Array bits, unsigned int n) { assert(n < CHAR_BIT); return bits & ~(1 << n); } int test_bit(Bit_Array bits, unsigned int n) { assert(n < CHAR_BIT); return bits & (1 << n); } int main() { Bit_Array b = 0x00; set_bit(b, 156); b = set_bit(b, 3); if (test_bit(b, 3)) printf("Jusquici, tout va bien\n"); b = reset_bit(b, 3); if (b == 0) printf("Cela a lair de marcher...\n"); return 0; }
20
25
30
35
40
45
c Jean-Paul R IGAULT
3.3. Oprateurs
63
Linstruction assert(cond) value la condition cond. Si elle est vraie, rien ne se passe ; sinon lexcution se termine avec un message derreur. Par exemple, imaginons que nous invoquions set_bit(b, 156) dans le programme 3.6. Lexcution se termine alors ainsi :
% bit_ops bit_ops: bit_ops.c:17: set_bit: Assertion n < 8 failed. zsh: abort bit_ops %
3.3.4 Affectations
Affectation simple
Laffectation simple est dsigne par loprateur =. Dans une expression daffectation comme a = b, loprande de gauche ne peut pas tre une simple valeur (comme 12) ; ce doit tre une lvalue modiable cest- dire une rfrence sur une variable (par exemple le nom de cette variable). La valeur de cette variable est remplace par celle de lexpression en oprande gauche b. Tous les types scalaires sont affectables, ainsi que les structures et les unions comme nous le verrons (4.2.3). Comme toute expression, a = b a un type, celui de a, et une valeur, celle de a aprs que laffectation ait t ralise. On peut utiliser cette valeur dans une expression plus complexe comme
j = 4*(i = 5);
qui donne j la valeur 5. Ce type dexpression avec effet de bord doit tre utilis avec parcimonie. Nanmoins, dans certains contextes il est dans la logique sinon le gnie du langage ; nous avons dj rencontr en 2.2 des expressions comme
while ((c = getchar()) != EOF) ...
Attention : = et ==
La possibilit de cet effet de bord rend encore plus dangereuse la confusion entre loprateur daffectation (=) et celui dgalit (==). Dans if (a = 0) ... il ny a aucune erreur de syntaxe ; ce nest pas le test de a 0, mais laffectation de 0 a suivi du test de la nouvelle valeur de a (cest--dire 0 !). La condition ne sera donc jamais vraie ici.
Affectation compose
Pour tout oprateur binaire , C dnit un oprateur = 5 daffectation sur place tel que
a = b
est quivalent
a = a (b);
5. Il ny a pas despace entre et =.
c Jean-Paul R IGAULT
64
3. Bases du langage
avec cette diffrence que a nest valu quune seule fois. Cette dernire restriction ne pose pas de problme si a est une simple variable. mais nous verrons que la situation peut tre plus complexe (5.1.1). Noter les parenthses autour de b qui forcent lvaluation de cette expression avant dappliquer loprateur . Les oprateurs binaires composables avec laffectation (cest--dire ligibles pour remplacer ) sont les suivants :
* / % + << >> & ^ |
Dans les deux derniers cas, les autres bits de a sont inchangs.
Incrmentation et dcrmentation
Lexpression ++a, o a est une lvalue modiable, est strictement quivalente a += 1 Elle rend donc la valeur de a aprs lincrmentation : on parle dincrmentation prxe. En revanche, la valeur de lexpression ++a est celle de a avant son incrmentation. On parle dincrmentation postxe. Bien entendu, loprateur de dcrmentation -- est lui aussi dni avec les deux formes, prxe et postxe.
Pour (tenter de) convertir une expression e dans le type T, il suft dcrire (T)e :
float x = (float)3; ... x = 1/(double)2; ... c = (char)0; /* float x = 3.0; */ /* x = 1/2.0; */ /* c = \0; */
En C, cette conversion na de sens que si le type T et celui de a sont scalaires. La smantique de ces conversions est tudie en 3.4.2 et en 5.1.4. En C il sagit toujours dun mcanisme rsolu la compilation. On trouve le nom dun type en considrant une dclaration dun objet du type et en y retirant lidenticateur correspondant lobjet dclar :
c Jean-Paul R IGAULT
3.3. Oprateurs
65
int i; unsigned short int ui; const double PI = ...; volatile char c;
type : int type : unsigned short int ou unsigned short type : const double type : volatile char
Nous verrons des applications de cette rgle simple dans des cas bien plus complexes plus loin.
Taille mmoire dun objet
Lexpression sizeof(x) o x est soit une rfrence sur un objet, soit un nom de type. Le rsultat est la taille mmoire occup par lobjet ou un objet du type. Par dnition
sizeof(char) == 1
Cette taille est donc exprime en nombre de caractres. En C, la taille des objets est connue la compilation, et cest donc la compilation que sizeof(x) est valu : cest un exemple dexpression statique.
et se lit : si a alors b sinon c . Elle a donc la valeur de lexpression b si lexpression a est vrai (i.e. a est non nulle) et celle de c sinon. Une remarque supplmentaire : seule lexpression qui dtermine la valeur est value 6 . Ainsi dans
int t[100]; ... a = (n < 100) ? t[n] : 0;
t[n] nest valu que si n est infrieur 100, vitant un potentiel dbordement dindice.
Remarque : question de style
Il est clair quune expression comme la prcdente est quivalente if (n < 100) a = t[n]; else a = 0; Quelle forme prfrer? Jai tendance dans ce cas privilgier la premire qui indique clairement que a va changer de valeur. Dans la seconde forme, cest tout bonnement un hasard que a se retrouve cible de laffectation dans les deux instructions du if ; une lecture trop rapide pourrait la confondre avec
6. Non seulement cest bien naturel, mais en plus cest garanti !
c Jean-Paul R IGAULT
66
3. Bases du langage
if (n < 100) b = t[n]; else a = 0; On peut cependant regretter la syntaxe cryptique de cette instruction conditionnelle.
On peut noter au passage que a&&b est quivalent a?(b!=0):0 et a||b a?1:(b!=0).
Oprateur dvaluation squentielle
value dabord a1 , puis a2 . La valeur et le type de lexpression tout entire sont ceux de la dernire expression, cest--dire ici a2 . Par exemple, a++ est quivalent
((tmp = a), (a += 1), tmp)
La virgule qui spare les arguments dun appel de fonction sort(t, n); nest pas loprateur dvaluation squentielle. En fait, il ny a aucune garantie sur lordre dvaluation des paramtres dune fonction (voir 7.2.3).
puisque loprateur binaire - associe de gauche droite. Bien entendu, il est toujours possible dutiliser des parenthses pour forcer un autre ordre dvaluation ou tout simplement pour amliorer la lisibilit. Le sens dassociativit est dni pour tous les oprateurs bien que cela nait pas ncessairement de sens, lopration ntant pas naturellement associative. Par exemple
c Jean-Paul R IGAULT
67
Table 3.8 Prcdence et associativit des oprateurs de C Associativit gauche droite droite gauche
()
! & * + << < == & ^ | && || ? : (conditionnelle) Affectations simples et composes = += *= ... , (virgule)
gauche droite gauche droite gauche droite gauche droite gauche droite gauche droite gauche droite gauche droite gauche droite gauche droite droite gauche droite gauche gauche droite
a < b < c
ce qui compare le rsultat de (a < b) (0 ou 1) la valeur de c, ce qui nest sans doute pas ce qui est dsir. De manire plus souhaitable, on note que les affectations associent de droite gauche, ce qui permet dcrire
a = b = c
et qui affecte a et b la mme valeur, celle de c au type prs (voir 3.4.3). Certaines priorits sont pigeantes : par exemple
if (a == b&0xff) ...
doit se lire
if ((a == b)&0xff) ...
ce qui nest pas particulirement naturel. On doit galement se mer de lexpression conditionnelle :
x = 3 + a ? b : c;
sinterprte comme
x = (3 + a) ? b : c;
c Jean-Paul R IGAULT
68
3. Bases du langage
ce qui l encore nest pas trs naturel. En revanche, les affectations sont trs peu liantes, ce qui est normal. Ainsi les parenthses sont ncessaires dans
while ((c = getchar()) != EOF) ...
ce qui teste bien la n de chier, mais affecte c le rsultat de cette comparaison, 0 ou 1, et non pas le caractre lu effectivement. Cependant les affectations sont plus liantes que loprateur dvaluation squentielle :
x = a1, a2, a3;
se lit
(x = a1), a2, a3;
Prcdence et associativit ne sont pas sufsantes pour dterminer lordre dvaluation dune expression. En fait, seuls quatre oprateurs garantissent exactement lordre dvaluation de leurs oprandes : ||, &&, ?: et la virgule ,. Dans tous les autres cas, lordre dvaluation des oprandes est indni. On doit donc tre particulirement vigileant lorsque certains oprandes contiennent des effets de bord. Aucun des deux exemples ci-aprs nest portable : t[i] = i++; x = ((c = getchar()) ? c : 0) + c;
3.4.2 Conversions
ansi C permet deffectuer des conversions entre types scalaires, soit explicitement (comme en 3.3.6), soit implicitement comme on va le voir un peu plus loin (3.4.3). Cette section donne les rgles qui sont alors appliques, cest--dire la smantique des conversions. Une rgle gnrale simple est utilise pour toutes ces conversions : si la valeur initiale peut tre reprsente dans le nouveau type, la valeur est conserve 7 ; sinon le rsultat dpend de limplmentation.
Promotion entire
Cette conversion est effectue implicitement avant toute autre valuation dune expression. Elle permet dutiliser un caractre (char), un entier court (short), une valeur de type numr (enum), ou encore un champ de bits (voir 4.2.2), quils soient ou non signs, la place dun entier ordinaire. La rgle est simple : si le type int est sufsant pour contenir la valeur convertir, il est utilis ; sinon la valeur est convertie en unsigned int.
7. avec ventuellement une perte de prcision dans les conversions ayant pour cible un type rel
c Jean-Paul R IGAULT
69
Lorsquun entier sign a est converti en non sign, la valeur obtenue u est telle que u = min(v a
v >0
(mod 2n ))
o n est le nombre de bits de la reprsentation. Dans la reprsentation des entiers en complment 2 ceci revient tronquer gauche la reprsentation binaire si le nouveau type est moins large, ou a complter gauche par des 0 dans le cas contraire. Si un entier est converti en un autre type entier sign, la valeur est prserve si le nouveau type peut reprsenter cette valeur ; sinon le rsultat dpend de limplmentation :
int i = 12715; short s = i; char c = i; /* OK: short suffisamment grand */ /* ??: resultat non portable */
Lorsquun entier est converti en rel (float ou double) le rsultat est la valeur reprsentable la plus proche ; le fait que cette valeur soit suprieure ou au contraire infrieure la valeur entire initiale nest pas dni et est dpendant de limplmentation. Dans le cas inverse dune conversion de rel en entier, la partie fractionnaire est simplement ignore (troncature vers 0 et non pas arrondi !). Si la partie entire nest pas reprsentable dans le type cibl, le rsultat dpend de limplmentation. En particulier, la conversion dun rel en entier non sign (unsigned) nest jamais portable.
double x = double y = double z = int i; ... i = x; /* i = y; /* i = -x; /* i = -y; /* i = z; /* 3.141592; 2.7182818; 1.0e+30;
Si le type cibl est plus grand par exemple conversion de float en double la valeur reste inchange. Dans le cas contraire, si la valeur initiale est dans lintervalle reprsentable, le rsultat est la plus proche valeur (il nest pas dni si elle est infrieure ou suprieure) ; sinon, le rsultat dpend de limplmentation.
c Jean-Paul R IGAULT
70
3. Bases du langage
les deux oprandes sont valus chacun avec leur type propre. Puis, si ncessaire (et possible !), la valeur de loprande droit est convertie implicitement dans le type de celui de gauche.
double x = 3.141592; int i; ... i = x; /* troncature: i == 3 */ x = 5/2; /* division entiere: x == 2.0 */
On voit donc que le type de la partie gauche de laffectation na aucune inuence sur la manire dvaluer la partie droite ni sur le type de cette dernire. En C le type dune expression ne dpend que du type de ses oprandes. Pour les affectations composes, on se rappelle lquivalence de a += b avec a = a+(b), mais avec une seule valuation de a. Les rgles de conversions se dduisent de la forme expanse.
Attention : affectations en chane
c Jean-Paul R IGAULT
71
peut donner des rsultats surprenants : aprs cette expression, a, b et c ne sont pas ncessairement gaux. Aprs double a; int b; ... a = b = 3.141592; b vaut 3 et a vaut 3.0 (et pas 3.141592!).
Un bloc permet de regrouper plusieurs instructions : cest une suite dinstructions encadres par des accolades.
{ x = a + b; y = a > b ? a : b; printf("%d %d\n", x, y); }
Un bloc peut comporter des dnitions de variables qui sont alors locales au bloc, cest--dire ne sont visibles et mme ventuellement nont dexistence que dans le bloc (voir le chapitre 8) :
{ int i = 0, j = 3; x = 2*i; y = j -i; }
En C90, les variables locales dun bloc doivent tre dclares 8 en tte de ce bloc, avant toute instruction excutable. Ceci nest plus ncessaire en C99 : comme en C++, les variables locales peuvent tre dclares nimporte o dans le bloc, du moment que cest avant leur utilisation. Ainsi, le bloc suivant est lgal en C99, mais pas en C90 :
{ int x = int y = }
8. En fait, cette dclaration est en fait une dnition voir 8.2.
C99
i = 0; 2*i; j = 3; j -i;
c Jean-Paul R IGAULT
72
3. Bases du langage
Un bloc joue le mme rle syntaxique quune instruction simple. Dans la suite, nous dsignerons par instruction aussi bien une instruction simple quun bloc ou une instruction de contrle (objet de la section suivante). Les blocs peuvent bien entendu simbriquer (cest--dire que les instructions dun bloc peuvent elles-mmes tre des blocs).
Attention : pas de point-virgule aprs un bloc
Si une instruction simple est bien termine par un point-virgule, ce caractre ne gure pas derrire laccolade fermante dun bloc. Lerreur est le plus souvent sans consquence sauf dans certaines instructions composes (voir 3.5.2).
Noter que la condition qui est une expression scalaire quelconque (dont on value la valeur de vrit voir 3.3.2) est entre parenthses, et quil ny a pas en C de mot-cl then. Si les plusieurs instructions sont prsentes dans instruction-si-vrai et instruction-si-faux, il faut les regrouper dans un bloc. Enn la clause else est optionnelle.
if (a == 0) fprintf(stderr, "division par 0"); else x = b/a; if {a != b) { x = a; y = b; }
Bien entendu le dernier else est optionnel. Les fonctions isalpha, isdigit et isspace permettent de dterminer quelle catgorie appartient un caractre ; elles sont dnies dans le chier denvironnement standard <ctype.h>.
c Jean-Paul R IGAULT
73
Contrairement la plupart des langages impratifs modernes, le if nest pas termin par un mot-cl comme endif ou fi ; en consquence, dans une instruction comme if (a == b) if (c != d) x = y; else y = x; il y a une ambigut : on ne sait pas quel if se rfre le else. Bien que C ait sa propre rgle pour lever cette ambigut, ce nest pas un bon style de sy er. Il vaut mieux crire explicitement ce que lon veut : par exemple if (a == b) { if (c != d) x = y; else y = x; }
Attention : pas de point-virgule aprs une accolade
Dans lextrait de programme suivant, le point-virgule provoque une erreur de syntaxe : if (a != b) { x = a; y = b; }; else x = y = 0;
Cette instruction permet un aiguillage entre plusieurs possibilits ; elle correspond linstruction case des langages de la famille Pascal. Elle a la forme suivante :
switch (expression) { case val1 : instructions1 case val2 : instructions2 ... case valn : instructionsn default: instructions-default }
Les instructionsi , et linstructions-default sont des suites dinstructions quelconques (simples ou blocs) ventuellement vides. La clause default et les instructions-default sont optionnelles. Les expressions val1 , val2 , ..., valn doivent tre statiques cest--dire valuables la compilation. Lexpression est value et sa valeur est compare celle des expressions vali , dans lordre i = 1, 2, ..., n ; si lgalit est trouve, disons pour val j , alors les instructions j sont excutes puis on passe en squence pour excuter instructions j+1 , instructions j+2 , etc... jusqu
c Jean-Paul R IGAULT
74
3. Bases du langage
instructions-default incluses. Si aucune galit avec un des vali nest trouve, les instructions-default sont excutes si elles existent ; sinon on sort du switch et on passe en squence. Lorsque certaines des instructionsi sont vides ce comportement trange prend un sens : on obtient alors le ou logique des diffrents cas :.
switch (c) { case \n: printf("fin de ligne"); /* fall through ... */ case : case \t: case \f: printf("espace\n"); }
Dans cet extrait de programme, si c est une n de ligne on obtient les deux messages
fin de ligne espace
Si c est un blanc, un caractre de tabulation horizontal ou un saut de page, on obtient le seul message
espace
et dans tous les autres cas, on na aucun message. Le commentaire /* fall through ... */ ou quelque chose dquivalent est assez souvent rencontr dans cette situation pour bien marquer que le programmeur souhaite effectivement ce passage en squence. Mais dans limmense majorit des cas, on veut retrouver le comportement habituel de linstruction case de Pascal, cest--dire sortir du switch ds que lune des instructionsi celle qui correspond la valeur de lexpression a t excute ; on utilise alors linstruction break qui est une rupture de squence (un saut) la n du switch :
switch (c) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: printf("chiffre\n"); break; case : case \t: case \n: case \f: printf("espace\n"); break; default: printf("ni chiffre, ni espace\n"); break; }
Le dernier break est bien videmment inutile, mais il a t plac l par souci dhomognit.
c Jean-Paul R IGAULT
75
Le corps de la boucle, cest--dire instruction, est excute tant que la condition une expression entire ou convertible en entier a la valeur de vrit vrai. La condition est value et teste avant chaque itration, y compris la premire.
Boucle do ... while
effectue linstruction jusqu ce que la valeur de vrit de lexpression entire ou convertible en entier condition devienne faux. La condition est value en n de boucle. Le corps de la boucle, cest-dire instruction, est donc toujours valu au moins une fois. En ce sens, la boucle do ... while est donc quivalente la forme suivante :
instruction while (condition) instruction
Ainsi, lextrait de programme suivant o i est une variable entire, et t et u des tableaux de mme type ou de types compatibles et de dimension au moins gale a 10,
c Jean-Paul R IGAULT
76
3. Bases du langage
est-il quivalent
i = 0; while (i < N) { { t[i] = u[i]; u[i] = 0; } i++; }
ce qui a pour effet de transfrer les 10 premiers lments du tableau u dans le tableau t et de mettre les 10 premiers lments de u zro. Noter quen sortie de boucle, la variable i vaut 10.
Remarque : variable de controle de boucle
C99
En C90, La variable de contrle de la boucle, i dans lexemple ci-dessus, doit tre dclare avant la boucle for elle-mme. Sa porte est donc strictement plus grande que celle de la boucle elle-mme ; cest souvent une variable locale du bloc englobant. En C99, il est possible comme en C++ de dclarer que la variable de boucle est locale la boucle : for (int i = 0; i < N; ++i) t[i] = 0.0; Dans ce cas, la variable de boucle nest pas connue lextrieur de la boucle : for (int i = 0; i < N; ++i) if (t[i] == 0.0) break; /* sortie, voir plus loin */ printf("element nul en i = %d\n", i); /* NON ! i inconnu ici */ Enn, dans tous les cas, il est possible, bien que de style douteux, de modier cette variable dans le corps de la boucle.
Si la boucle for est trs souvent utilis dans des cas simples comme ceux de lexemple ci-dessus, elle est dune grande richesse dexpression. Dans lextrait suivant
for (i = 0; i < 10; i += 2) t[i] = 0;
un lment sur deux du tableau t est mis zro (lindice de boucle avance par pas de 2). On peut mme avoir plusieurs indices pour une seule boucle :
for (i = 0, j = 0; i < 10; i++, j += 2) t[i] = u[j];
C99
c Jean-Paul R IGAULT
77
Ici un lment sur deux du tableau u est transfr dans les dix premiers lments (conscutifs) du tableau t.
Attention : boucles simples et imbriques
Dans lexemple prcdent, il ny a quune seule boucle et les deux indices i et j progressent en mme temps, bien quavec des pas diffrents. Le nombre ditrations est donc de 10. Il ne faut pas confondre cet exemple avec celui des boucles for imbriques comme for (i = 0; i < 10; i++) for (j = 0; j < 20; j += 2) ... Dans ce dernier cas, il y a deux boucles et 200 itrations.
Remarque : boucle for incomplte Lun ou plusieurs des trois lments de contrle dune boucle for peuvent tre omises :
for ( ; i < 10; i++) ... for (i = 0; i < 10; ) ... for (i = 0; ; i++) ...
Dans le dernier cas, la condition dentretien, absente, est suppose valoir 1, cest--dire vrai : la boucle est a priori innie. Ainsi for (;;) {} est une vraie boucle innie quivalente while (1) {}
La boucle for de C dconcerte souvent les dbutants dans ce langage ; ils prfrent alors utiliser la forme while quivalente. Ceci est bien entendu une question de got. Cependant, jai tendance prfrer chaque fois que possible la forme for car elle a lavantage de concentrer syntaxiquement les trois lments de contrle de la boucle : point de dpart, condition de passage litration suivante, instruction de progression.
Linstruction break que nous avons dj rencontr dans le cas de switch (3.5.2) a une autre signication : elle permet dabandonner prmaturment une boucle (for, while ou do ... while). Ainsi
for (i = 0; i < 10; i ++) { if (t[i] == k) break; }
est une boucle qui se termine lorsque t[i] vaut k. Noter quen sortie de boucle, la valeur de i est conserve.
Remarque
c Jean-Paul R IGAULT
78
3. Bases du langage
for (i = 0; i < 10 && t[i] != k; i ++) {} avec un corps de boucle vide et pas de break.
on ne sort que de la boucle interne ; en C le seul moyen de sortir de deux (ou plus) boucles imbriques est dutiliser un goto. De plus, comme break est aussi utilise pour sortir dun switch, il nest pas possible, sans utiliser un goto de sortir prmaturment dune boucle depuis un switch interne :
for (i = 0; i < 10; i++) { switch (t[i]) { case 0: ... case 1: ... // sort de switch, pas de for break; ... } }
Linstruction continue est analogue break, mais au lieu dabandonner la boucle, on passe litration suivante (ou plutt on tente de passer en valuant la condition dentretien). Dans le cas dune boucle for les instructions de rebouclage sont bien entendu excutes :
for (i = 0; i < 10; i++) { ... if (i == 0) continue; /* va a literation suivante (i == 1) */ ... }
Linstruction goto permet de transfrer le contrle un point dsign par une tiquette.
... goto La_Bas;
c Jean-Paul R IGAULT
79
Une tiquette est un identicateur quelconque. Par exemple, pour sortir la fois dun switch et de la boucle qui lenglobe :
for (i = 0; i < 10; i++) { switch (t[i]) { case 0: ... case 1: ... goto out; ... } } out: ...
On peut faire un goto uniquement vers une tiquette dnie dans le mme bloc ou dans un bloc englobant. Il est interdit de faire des goto de lextrieur vers lintrieur dun bloc, du corps dune boucle, dun if ou dun switch. Enn signalons que linstruction goto est considre comme nuisible depuis un article clbre de E. W. Dijsktra [13]. En tout tat de cause, son utilisation est trs souvent lindice dune mauvaise organisation du code.
Retour de fonction: return
Nous avons dj souvent utilis cette instruction qui permet de terminer lexcution dune fonction. Elle a deux formes possibles :
return;
si la fonction une valeur de retour. Lexpression sera convertie dans le type de retour, si ncessaire et possible sinon, cest une erreur.
Parenthsage de lexpression suivant return
Comme return nest pas une fonction (contrairement, par exemple, exit()), il ny pas besoin de parenthses autour de lexpression retourner.
c Jean-Paul R IGAULT
80
3. Bases du langage
Exercice 3.2 (Nombre de caractres, mots et lignes dun texte) crire le pro-
gramme word_count qui lit son entre standard et imprime sur la sortie standard le nombre de lignes, mots et caractres lus sur lentre standard. Votre programme doit avoir le mme comportement que la commande dUnix wc(1) mais, videmment, ne doit pas utiliser cette dernire ! On pourra utiliser les fonctions dnies dans <ctype.h> en particulier isspace pour tester quun caractre est un espace.
Remarque 1
On prcisera ce quon entend par un mot et donc quels sont les sparateurs de mots. Ce doit tre la mme chose que pour la commande standard wc(1).
Remarque 2
Attention la n de chier !
Remarque 3
On pourra ajouter au programme des options -l, -c... comme dans wc(1).
Exercice 3.3 (valuation dun polynme) crire un programme qui reoit en
ligne de commande les coefcients a0 , a1 , . . . , an dun polynme de degr n, imprime le polynome obtenu, rclame une valeur de la variable x et value le polynme pour cette valeur. Pour ce programme, vous crirez deux fonctions dvaluation, une o vous utiliserez la fonction pow de <math.h> et lautre o vous appliquerez le schma de Horner. On rappelle que ce schma consiste remarquer quun polynme de degr n peut scrire sous la forme
(. . . (( an x + an1 ) x + an2 ) x + . . . + a1 ) x + a0
et peut donc tre valu par une procdure rcursive ou itrative (que nous vous laissons le soin de dnir et de mettre en place) ne ncessitant que n multiplications. Voici un exemple dutilisation :
% poly 2 3 4 polynome: 2 + 3 * x1 + 4 * x2 valeur de x ? 2 rsultat direct: 24 rsultat Horner: 24 %
Comme vous pouvez le constater, on a aussi crit une fonction qui permet dafcher le polynme sous une forme raisonnable.
Exercice 3.4 (Nombres premiers) crire un programme qui reoit une suite
dentiers en ligne de commande et pour chacun de ces entiers imprime sil est premier ou non.
Exercice 3.5 (Quelques oprations non portables) tudiez comment votre plate-forme se comporte pour certaines oprations non portables sur les entiers. En particulier : que se passe-t-il en cas de dpassement de capacit entire ?
c Jean-Paul R IGAULT
81
que se passe-t-il en cas de division par zro entire ? que se passe-t-il lorsque loprande droit dune opration de dcalage est ngatif ? le dcalage droit est-il arithmtique ou logique, cest--dire conserve-t-il le signe ou non de son oprande gauche ? quen est-il du dcalage gauche ? que se passe-t-il lorsque le diviseur dune opration modulo est ngatif ? lorsque le dividende est ngatif ? lorsque les deux oprandes sont ngatifs ?
c Jean-Paul R IGAULT
82
3. Bases du langage
c Jean-Paul R IGAULT
Chapitre 4
4.1 Tableaux
4.1.1 Tableaux mono-dimensionnels
Dnition et dclaration
Un tableau est bien sr une collection dobjets qui sont tous du mme type. Nous avons dj rencontr des dclarations et dnitions de tableaux monodimensionnels (2.4). En voici quelques exemples :
int t[10]; double vec[100]; ... #define NBUF 10000 char buffer[NBUF];
La dimension, cest--dire le nombre dlments, doit tre une expression statique : la taille des tableaux de C90 doit donc tre connue la compilation (voir 4.1.4 pour C99). Il y a cependant exception lorsquun tableau est pass en argument dune fonction 1 : la dimension du tableau peut tre laisse libre, condition que la fonction puisse la connaitre par ailleurs. La dimension dun tableau de C nest en effet pas conserve lexcution : en particulier, C ne sait pas vrier les dbordements dindice 2 . A titre dexemple, dans le programme 4.1, la fonction prod_scal calcule le produit scalaire des deux vecteurs de dimension n donns en arguments.
Remarque : dimension dun tableau et const
En C90, on ne peut malheureusement pas xer la dimension dun tableau laide dune const : const int N = 100; /* ERREUR!! */ double t[N];
1. Nous verrons plus tard (5.3.2) que ce nest pas vraiment le tableau qui est pass en paramtre mais plutt un pointeur, ladresse de son premier lment. 2. Certains compilateurs, ou certains outils de lenvironnement de programmation, peuvent cependant proposer un tel service, mais il nest pas dni dans la norme.
83
c Jean-Paul R IGAULT
84
20
int main() { double x1[5] = {0, 1, 2, 3, 4}; double x2[5] = {3, 1, -1, -1, 1}; printf("<x1, x2> = %g\n", prod_scal(5, x1, x2)); return 0; }
Bien entendu, il est toujours possible dutiliser les macros du prprocesseur et #define : #define N 100; ... double t[N]; mais ceci est loin davoir les mmes avantages (voir 6.3.1).
C99
En revanche, C99 permet ce type de dimensionnement par une constante, mais uniquement pour les variables locales (voir 4.1.4).
On peut bien sr dnir un synonyme pour le nom dun type tableau ; la dimension fait partie du type :
typedef double Vector[1000]; Vector v1, v2;
c Jean-Paul R IGAULT
4.1. Tableaux
85
La syntaxe utilise est dite initialisation dagrgat . Dans lexemple du tableau u seules les deux premires composantes sont initialises. Pour v, la dimension est laisse libre ; cest le compilateur qui la calculera en fonction du nombre dlments dans la liste dinitialisation (ici la dimension est donc 6).
Taille dun tableau et operateur sizeof
Noter que pour tout tableau, le rapport entre la taille du tableau et la taille dun composante (ces tailles tant fournies par loprateur sizeof 3.3.6) est gal au nombre de composantes. Ainsi pour v plus haut : const int n_elems_v = sizeof(v)/sizeof(int);
Bien entendu, un tableau peut tre constant (resp. volatile), ce qui revient dire que toutes ses composantes le sont :
const int ctab[] = {1, 0, 0, 0}; const volatile unsigned status_registers[NDEVICES];
Dans ce cas, ctab[i] ne peut tre modi et status_registers[i] peut changer de valeur sans que le programme ne le modie explicitement.
Opration dindexation
Lopration dindexation est la principale et de fait la seule opration sur un tableau : pour un tableau t, t[i] dsigne une rfrence sur la composante dindice i : lindice doit tre une expression entire, ou convertible en entier, la premire composante a comme indice 0. Comme loprateur retourne une rfrence, lexpression dindexation peut donc apparaitre en partie gauche dune affectation, condition que le tableau ne soit pas constant, bien sr :
int t[10] = {...}; int u[10]; ... u[0] = 0; for (i = 1; i < 10; i++) u[i] = t[i - 1];
Copie de tableaux
Comme nous le justierons plus tard (5.3.2), on ne peut pas affecter les tableaux entre eux. Cet extrait de programme est incompilable :
int t[10], u[10]; ... u = t; /* ERREUR */
c Jean-Paul R IGAULT
86
mat est une matrice de double 10 lignes et 100 colonnes. En mmoire, une telle matrice est range par lignes, cest--dire que cest le dernier indice qui varie le plus vite :
mat[0][0] mat[1][0] ... mat[9][0] mat[0][1] mat[1][1] mat[9][1] mat[0][2] ... mat[0][99] mat[1][2] ... mat[1][99] mat[9][2] ... mat[9][99]
On lit sur cette initialisation les diffrentes lignes du tableau ident (la matrice identit de dimension 3). A titre dexemple trs simple, le programme 4.2 effectue la transposition sur place dune matrice carre 3 3 lue sur lentre standard. En voici un exemple dexcution :
% transpose ligne 0 (entrez 3 valeurs): 1 -1 -1 ligne 1 (entrez 3 valeurs): 1 2 -2 ligne 2 (entrez 3 valeurs): 1 2 3 Resultat: 1 1 1 -1 2 2 -1 -2 3 %
En C, le nombre de dimensions dun tableau nest pas limit : on peut donc avoir des tableaux 2, 3, n dimensions qui sont autant de tableau de tableaux :
int t[3][5][7]; double u[][2][2] {{1, 2}, {2, {{2, 3}, {3, {{3, 4}, {4, }; = { 3}}, 4}}, 5}},
Noter, avec lexemple de u, que seule la premire dimension peut tre laisse libre (ici sa valeur calcule par le compilateur sera 3).
c Jean-Paul R IGAULT
4.1. Tableaux
87
#define N 3 typedef double Mat3x3[N][N]; void Transpose(int n, Mat3x3 m) { int i, j; double x; for (i = 0; i < N; i++) for (j = i + 1; j < N; j++) { x = m[i][j]; m[i][j] = m[j][i]; m[j][i] = x; } } int main() { int i, j; Mat3x3 mat; for (i = 0; i < N; i++) { printf("ligne %d (entrez %d valeurs): ", i, N); for (j = 0; j < N; j++) scanf("%le", &mat[i][j]); } Transpose(N, mat);
10
15
20
25
30
35
40
printf("Resultat:\n"); for (i = 0; i < N; i++) { for (j = 0; j < N; j++) printf("%g ", mat[i][j]); putchar(\n); } }
c Jean-Paul R IGAULT
88
Il est bien entendu possible de dnir des tableaux multi-dimensionnels de caractres (voir aussi 5.3.2) :
const char Days[][10] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", };
Ici Days est une matrice de char dont la dimension est 7 10 soit 9 lignes et 10 colonnes (en fait 7 9 sufrait, mais rappelons nous que seule la premire dimension peut tre calcule automatiquement par le compilateur).
Affectation de chanes de caractres
Les chanes de caractres tant des tableaux, comme ces derniers elles ne sont pas affectables. Ainsi, avec les exemples prcdents, les affectations suivantes sont illgales : msg = "Bonjour le monde!"; Days[0] = "Monday";
Nous avons vu que la (les) dimension(s) dun tableau en C90 devaient tre des constantes de compilation. Ce nest plus vrai en C99, au moins pour les tableaux qui sont des variables locales automatiques dune fonction ou dun bloc. En C99, il est possible de dimensionner un tableau local laide dune variable, comme dans
void g(int n) { int t[n]; ... }
Notez cependant que la taille nest toujours pas un attribut du tableau : il est ncessaire de la connaitre par ailleurs (ici grce n). Le programme 4.3 montre un exemple un peu plus compliqu. Voici le rsultat de son excution :
c Jean-Paul R IGAULT
4.1. Tableaux
89
/* Quatre prototypes equivalents pour f */ void f(int n, int t[n]); void f(int, int t[*]); void f(int, int t[]); void f(int, int *t); int main() { int n; printf("n? "); scanf("%d", &n); int tab[n]; printf("main: n = %d sizeof(tab) = %d\n", n, sizeof(tab));
10
15
20
f(n, tab); } void f(int n, int t[n]) { int u[n][n*n]; printf("f: n = %d sizeof(t) = %d sizeof(u) = %d\n", n, sizeof(t), sizeof(u)); }
25
On peut faire plusieurs remarques sur ce code : Les quatre formes du prototype de pr-dclaration de f (lignes 5 8) sont quivalentes en C99 ; seules les deux dernires sont correctes en C90. Dans la fonction main, le tableau tab reoit la dimension n lue juste avant ; on voit ici au passage lintrt de pouvoir mlanger dclaration et code (3.5.1) au lieu dtre oblig de mettre toutes les dclarations en tte de bloc. Dans main toujours, noter le calcul de sizeof lexcution et non pas, comme la plupart du temps la compilation. Dans la fonction f, on constate que plusieurs dimensions peuvent tre variables (tableau u). Enn, f montre aussi que lorsquon passe un tableau dimensions variable en paramtre (tableau t), en fait rien ne change par rapport C90. En particulier, la taille est perdue (sizeof(t) retourne ici 4, cest--dire la taille dun pointeur !).
c Jean-Paul R IGAULT
90
Comme il a t indiqu au dbut, ce mcanisme de dimension variable ne sapplique quaux variables locales (automatiques), et certainement pas aux variables statiques. Par exemple, ceci ne compile pas : int n = 10; int tab[n]; int main()} { ... } car tab nest pas locale. Le fait que n soit constante ou pas ne change rien laffaire. Notons que mcanisme permet au passage de dimensionner un tableau local par une constante : en fait il permet dj de le faire par une variable et donc, qui peut le plus, peut le moins.
4.2 Structures
La structure C est lanalogue du record des langages de la famille de Pascal. Elle permet de regrouper en un seul type composite une collection dobjets dont les types sont possiblement diffrents.
Chaque champ de la liste a la mme syntaxe quune dclaration 3 dobjet. Par exemple
struct Address { int street_number; char street_name[100]; short zip_code; char city_name[50]; };
dnit un nouveau type dont le nom est struct Address. Le tag Address est un identicateur choisi par lutilisateur. Un objet de ce type est constitu de quatre champs : un entier (street_number), une chane de 100 caractres 4 (street_name), un entier court (zip_code) et une chane de 50 caractres 5 (city_name). Ce type tant dni, on peut en crer des objets :
struct Address addr1, addr2; struct Address addr_epunsa = { 930, "route des Colles",
3. une dclaration, pas une dnition : en particulier, il ne peut pas y avoir dinitialisation. 4. dont 99 utiles 5. dont 49 utiles
c Jean-Paul R IGAULT
4.2. Structures
91
On remarque lutilisation de la syntaxe dinitialisation dagrgat. Ici encore, cette intialisation peut tre partielle : seuls les premiers champs sont alors initialiss.
Remarque: nom dun type structure
Le nom du type, struct Address, conserve le mot-cl struct. Cest parfois pnibleet lon peut toujours faire une synonymie de type grce typedef : typedef struct aaaa { int street_number; char street_name[100]; short zip_code; char city_name[50]; } Address; ou mme (voir 8.1) typedef struct Address { int street_number; char street_name[100]; short zip_code; char city_name[50]; } Address; et dans les deux cas, le nom du type sera Address tout court : Address addr_epunsa = { 930, "route des Colles", 6903, "Sophia Antipolis", };
Comme toujours, on peut crer un type et un objet de ce type (et mme linitialiser) en une seule instruction :
struct Person { char first_name[50]; char last_name[50]; enum {FACULTY, ADMIN, STUDENT} position; struct Adresse addr; } jpr = { "Jean-Paul", "Rigault", FACULTY, { 930, "route des Colles", 6903, "Sophia Antipolis", } };
On peut mme dans certains cas faire lconomie du tag. Le type devient alors anonyme.
struct { int a; double x; } s1, s2 = {3, 3.141592};
c Jean-Paul R IGAULT
92
Cependant, dans ce dernier cas, le type tant anonyme, on ne pourra pas crer dautres objets de ce type structure. Combiner dans la mme instruction la fois la dnition dun type et la dnition dobjets de ce type est de style douteux, et devrait tre vit.
Remarque : initialisation dagrgat
En C90, dans la dnition de lobjet tel que jpr ci-dessus, en supposant lobjet addr_epunsa pralablement initialis, il est impossible dcrire simplement (quel que soit le contexte) struct Person jpr = { "Jean-Paul", "Rigault", FACULTY, addr_epunsa, }; En effet, dans une initialisation daggrgat de C90, tous les initialiseurs doivent tre des constantes statiques.
C99 a apport deux modications linitialisation des structures. Tout dabord, il est possible dinitialiser une variable locale (automatique) avec des expressions non statiques. Ceci est donc maintenant permis :
void f() { struct Person jpr = { "Jean-Paul", "Rigault", FACULTY, addr_epunsa, }; int i = random(); struct { int a; double x; } s = {i, i * 5}; // ... }
Dans lextrait prcdent, addr_epunsa aurait pu aussi bien tre une variable gobale, mais jpr doit tre locale. Ainsi
struct Person jpr = { "Jean-Paul", "Rigault", FACULTY, addr_epunsa, }; int main() { ... }
ne compile pas, ni en C90, ni en C99. La deuxime extension apport par C99 est la possibilit dinitialiser les champs de la structure en les nommant :
c Jean-Paul R IGAULT
4.2. Structures
93
On voit dans ce cas que lordre des champs est sans importance et aussi que linitialisation peut ntre que partielle (pas dadresse par exemple). Cette syntaxe est galement utilisable pour les tableaux de structures (voir 4.2.4).
dcrit un format dinstruction dun langage-machine, schmatis dans la gure 4.1. Le nom du champ peut tre omis : le champ de bits sert alors despace dalignement (padding).
Type des champs de bits
Un champ de bits ne peut tre que de type entier (int) sign ou non. En outre, en C90, ce ne peut pas tre un type numr, et cest parfois bien dommage! C99 a lev cette impossibilit. Le code suivant compile donc en C99 mais pas en C90 : enum Op_Code {MOVE, ADD, SUB, BRANCH}; struct Instruction { unsigned code_op : enum Op_Code; unsigned index_reg : 4;
C99
c Jean-Paul R IGAULT
94
Limplmentation des champs de bits est extrmement dpendante du compilateur et du processeur. Les programmes utilisant les champs de bits peuvent tre portables, cependant, sils ne font aucune hypothse sur limplmentation, mais cela rduit de beaucoup leur intrt. En effet, il est clair que lutilisation des champs de bits ne peut se justier que pour se rapprocher de la machine , et dans ce cas, on sait que lon a des chances de perdre toute portabilit.
Cest lopration la plus importante : elle est reprsente par loprateur . (dot) et permet de rcuprer une rfrence sur lun des champs. Avec les exemples prcdents:
struct Person pers; ... if (pers.position == FACULTY) { printf("%s %s est un enseignant\n", pers.first_name, pers.last_name); }; ... char initial = pers.last_name[0]; ... pers.position = ADMIN; ... int zip = pers.addr.zip_code;
On remarque lutilisation en cascade de loprateur de slection dans le dernier exemple. Nous verrons (5.2.1) quil existe une autre oprateur de slection applicable aux pointeurs, la che (->)
Attention: initialisation dagrgat et affectation
La syntaxe dinitialisation dagrgat ne peut tre utilise, comme son nom lindique, que dans une initialisation. Elle est illgale pour une affectation : struct Address addr_epunsa = { 930, "route de Colles", 6903, "Sophia Antipolis", }; /* OK: initialisation */ adr_epunsa = { 930, "route de Colles", 6903, "Sophia Antipolis", }; /* ILLEGAL: affectation */
c Jean-Paul R IGAULT
4.2. Structures
95
Affectation de structures
Contrairement aux tableaux, les structures sont affectables ; laffectation revient faire une copie champ par champ :
struct Person jpr = { "Jean-Paul", "Rigault", { 930, "route des Colles", 6903, "Sophia Antipolis", } }; struct Person autre_jpr; autre_jpr = jpr;
Nous verrons aussi (7.1.3) que les structures peuvent tre passes en argument ou retournes par une fonction.
Retour sur laffectation des tableaux
On voit que si les tableaux ne sont pas affectables directement (4.1.1), ils le sont quand on les embarque dans une structure !
Pour lire ces expressions, et dcider si lon doit utiliser des parenthses, une seule solution: consulter le tableau de prddence et dassociativit des oprateurs (3.8). Sachez cependant que les priorits ont t bien choisies et quon a rarement besoin de parennthses dans ce type dexpression.
Intiailisation des tableaux de structures en C99
En C99, linitialisation des structures en nommant les champs est tendu aux tableaux de la faon suivante : struct Person faculty[] = { [0].first_name = "Jean-Paul", [0].last_name = "Rigault", [1].first_name = "Paul", [1].last_name = "Franchi", }; L encore, lordre na pas dimportance (mais vitez de perdre le lecteur !). En outre comme la dimension de faculty est laisse libre, le compilateur la calculera automatiquement (ici 2).
C99
c Jean-Paul R IGAULT
96
4.3 Unions
Les agrgats de type union sont une des particularits du langage C, bien que lon puisse trouver des constructions analogues dans dautres langages. Ils permettent de placer un mme emplacement mmoire des objets de types diffrents mais, contrairement aux structures, un seul objet la fois.
Par exemple :
union Real_Int { int int_view; double real_view; };
La diffrence avec une structure est la suivante : une structure corrrespond un emplacement mmoire assez grand pour contenir lensemble de tous ses champs ; une union, au contraire, correspond un emplacement mmoire sufsant pour contenir le plus grand des champs et donc nimporte lequel dentre eux. Dans lexemple prcdent, union Real_Int est donc le type dun objet qui peut tre soit un int, soit un double. Les objets de type union se dclarent et se dnissent comme les structures :
union Real_Int u1, u2;
Une prcaution cependant : on ne peut initialiser un objet de type union quavec un objet du type du premier champ de lunion. Ceci peut engendrer des surprises en cas de conversions implicites ; ainsi lextrait suivant union Real_Int u4 = {3.141592}; passe bien la compilation. Malheureusement, il ne range pas la valeur relle 3.141592 dans lobjet u4, mais la valeur 3 (rsultat de la conversion de 3.141592 dans le type du premier champ !).
C99
Cependant, en C99, si on utilise la possibilit de nommage des champs comme pour les structures (4.2.1), on peut prciser le type que lon veut utiliser linitialisation: union Real_Int u4 = {.real_view = 3.141592};
c Jean-Paul R IGAULT
4.3. Unions
97
lexpression u.int_view est une rfrence sur le contenu de u interprt comme un entier, alors que u.real_view est le mme objet mais interprt comme un rel double prcision. Loprateur de slection che (->) tudi en 5.2.1 est galement applicable aux unions.
Attention : type courant dune union
Il ny a aucun mcanisme en C qui permette de conserver, lexcution, la trace du type de lobjet effectivement prsent dans une union. De plus, aucune conversion nest effectue lors de la slection dun champ dunion. Lextrait de programme suivant est trs certainement erron : union Real_int u; int i; u.real_view = 3.141592; i = u.int_view; En effet, on range dabord la valeur (relle) de dans u, puis lon interprte cet objet comme un entier dans linstruction immdiatement suivante. Le rsultat de linterprtation de la conguration binaire de interprt comme un entier a trs peu de chances davoir une valeur voisine de 3 (par exemple, sur ma machine, la valeur de i est dans ce cas -57 999 238) ! Cest donc le programmeur qui a la responsabilit de garder la trace des types des objets quil place dans une union (voir 4.3.3).
Portabilit des unions
Lutilisation des unions est portable, sauf si le programme fait des suppositions sur limplmentation interne (cadrage, disposition interne, etc.).
Pour toutes les raisons discutes ici, lutilisation des unions en C doit tre rservs des cas particuliers trs spciques (par exemple programmation systme) et doit tre faite avec soin, par des programmeurs trs prudents !
C99
c Jean-Paul R IGAULT
98
tenu du choix des noms, il est assez clair, au moins dans la tte du programmeur et sans doute aussi du lecteur, que cest la valeur du champ position qui dtermine la manire dinterprter le contenu de cette union. Mais, faut-il le rpter, rien dans la syntaxe ne lexprime. La fonction print_person rend cette hypothse plus explicite grce linstruction switch. Il est dailleurs extrmement frquent de voir associs une union lutilisation dun type numr et de son corollaire quasi-invitable switch. En outre noter que C requiert une qualication complte du chemin conduisant la variante, comme dans
pers.info.faculty_info.CNU_section
C99
Ici .info slectionne le champ union de pers, .faculty_info slectionne linterprtation du contenu de cette union, et .CNU_section slectionne un champ dans la structure correspondante. Ouf ! Enn, la fonction main contient un cas de test. Notez la manire dont lobjet a_student est initialis. Nous utilisons la possibilit de nommer les champs propres C99, mais pour ce qui concerne la structure Student qui est simple, nous nous contentons de linitialisation dagrgat ordinaire de C90. Programme 4.4: Structure avec variante (en C99)
/***** Fichier: struct_variant.c --- ATTENTION: C99 *****/ #include <stdio.h>
5
10
/* Informations specifiques a un membre du corps enseignant */ struct Faculty { enum {PR, MDC, ATER, MONITOR} grade; char CNU_section[10]; }; /* Informations specifiques a un personnel administratif */ struct Admin { enum {A, B, C, D} category; // categorie administrative char fonction[20]; // fonction exercee }; /* Informations specifiques a un etudiant */ struct Student { enum {Y1 = 1, Y2, Y3} year; // annee detude _Bool repeater; // redoublant ? }; /* Inforamtions pour une eprsonne de lune quelconque * des categorie precedentes */ struct Person { char first_name[50]; // nom char last_name[50]; // prenom enum {FACULTY, ADMIN, STUDENT} position; // statut
15
20
25
30
c Jean-Paul R IGAULT
4.3. Unions
99
35
Faculty faculty_info; Admin admin_info; Student student_info; informations specifiques selon statut
40
45
/* Impression des informations */ void print_person(const struct Person pers) { printf("%s %s\n", pers.first_name, pers.last_name); switch(pers.position) { case FACULTY : printf("\tenseignant : %s\n", pers.info.faculty_info.CNU_section); break; case ADMIN: printf("\tadministratif : %s\n", pers.info.admin_info.fonction); break; case STUDENT: printf("\tetudiant : annee %d %s\n", pers.info.student_info.year, pers.info.student_info.repeater?"redoublant":""); break; } }
50
55
60
65
70
/* Test simple */ int main() { struct Person a_student = { .last_name = "Glandu", .first_name = "Anthelme", .position = STUDENT, .info.student_info = {Y2, 1}, }; print_person(a_student); return 0; }
75
c Jean-Paul R IGAULT
100
des tableaux de bits de longueur maximum NBITS pouvant tre suprieur au nombre de bits dans un char (CHAR_BIT dni dans <limits.h>).
Exercice 4.2 (Calcul matriciel simple) Raliser quelques oprations de calcul
matriciel simple : addition et produit de deux matrices rectangulaires, impression dune matrice, etc.
Utilisation des facilits de C99 C99
Il peut tre agrable de proter des facilits des tableaux de taille variable de C99 (voir 4.1.4). Par exemple le prototype de la fonction daddition peut tre le suivant :
void matrix_sum(int m, int n, double mat1[m][n], double mat2[m][n], double mres[m][n]);
Soyez cependant conscient que le fait de spcier les dimensions dans le prototype nentraine pas de vrication de ces dimensions et quil vous appartient toujours dassurer que vous passez des matrices de taille correcte (et compatible avec les dimensions indiques). En particulier, cest un excellent exercice de faire tourner votre programme en passant volontairement des tableaux de taille incorrecte. Essayez mme de lexcuter plusieurs fois avec les mmes donnes (incorrectes).
Exercice 4.3 (Recherche en table) Un enseignant anglo-saxon note habituellement ses examens en pourcentage (donc de 0 100), mais son administration peut lui demander des notes reprsentes pas les lettres de A E. La table de correspondance est la suivante :
Note E D C B A
crire un programme qui imprime la table de correspondance entre les notes de 0 100 et les lettres. Amliorer ce programme pour ajouter un + ou un - aprs la lettre en fonction du dernier chiffre du pourcentage selon la tableau suivant : Chiffre 13 47 80 Modieur +
c Jean-Paul R IGAULT
101
Ce tableau nest valable que pour les notes de A D, E ne peut avoir de modieur !
Exercice 4.4 (Oprations sur les dates) On souhaite reprsenter les dates par
une structure trois champs entiers reprsentant respectivement lanne, le mois, et le jour du mois an de faire des oprations arithmtiques. Les oprations suivantes doivent tre ralises (d, d1 et d2 sont des dates et n un entier) : date_valid(d) Retourne un boolen indiquant si la date est valide : lanne doit tre suprieure ou gale 1900 (Posix ne gre pas de dates infrieures), le mois doit tre compris entre 1 et 12, et le jour entre 1 et 28, 29, 30 ou 31 selon le mois et lanne... is_leap(y) Retourne un boolen indiquant si lanne y est bissextile ; une anne est bissextile si elle est divisble par 4, moins que ce soit une n de sicle auquel cas elle doit tre divisble par 400 (1900, 2100 ne sont pas bissextiles, 2000 lest). today() Retourne la date du jour. On utilisera les fonctions time et localtime de <time.h> pour obtenir cette information. print_date(d) Imprime une version humainement lisible de la date comme 23/01/2008. compare_date(d1, d2) Retourne 0 si les deux dates sont gales, une valeur ngative si la premire est infrieure la seconde, une valeur positive sinon. add_date(d, n) Retourne la date n jours aprs d ; ainsi add_date(today(), 1) retourne demain. sub_date(d, n) Retourne la date n jours avant d ; ainsi sub_date(today(), 1) retourne la veille. diff_date(d1, d2) Retourne le nombre algbrique de jours entre d1 et d2. Ainsi la diffrence entre aujourdhui et demain est -1 alors quentre demain et aujourdhui, cest +1.
Remarque
Pour la ralisation des fonctions arithmtiques add_date, sub_date et diff_date nessayez pas dtre trop malin ! Utilisez des algorithmes simples, par exemple des boucles jour par jour, en grant correctement les passages de mois et danne.
Exercice 4.5 (Gestion de liste de contacts) crire une gestion dagenda, cest-
-dire dune collection dindividus avec leur nom, prnom, adresse et numro de tlphone. Les fonctions suivantes doivent au minimum pouvoir tre effectues : recherche sur le nom et le prnom,
c Jean-Paul R IGAULT
102
afchage de la liste complte, introduction dun nouveau contact condition quil ne gure pas dj dans la base, destruction dun enregistrement tant donns son nom et son prnom. Dans cette premire version on se contentera dune structure de donnes simple et en mmoire. Dautres exercices viendront plus tard complter celui-ci.
Remarque
Pour cette exercice, vous utiliserez les fonctions de manipulation de chanes de caractres de la bibliothque standard (<string.h>) en particulier la fonction de comparaison de chanes strcmp.
c Jean-Paul R IGAULT
Chapitre 5
Pointeurs
pointeur est un objet (variable ou dont la est ladresse Undun autre(types access). Cependantconstante) dans desvaleurbien plus gobjet. Cette notion est bien connue langages comme Pascal ou Ada les pointeurs de C sont nraux que ceux de ces langages, dans la mesure o il est possible de prendre ladresse de tout objet et pas seulement des objets allous dynamiquement. En contre-partie de cette gnralit, la manipulation de pointeurs en C est dlicate et souvent source de graves erreurs. Maitriser C, cest dabord maitriser lutilisation des pointeurs.
Les pointeurs de C sont typs : un pointeur sur un entier na pas le mme type quun pointeur sur un double ou quun pointeur sur une structure ; des pointeurs sur des structures de types diffrents nont pas le mme type. On ne peut donc pas mlanger des types de pointeurs diffrents dans les expressions. La syntaxe de la dnition et de la dclaration de pointeurs doit indiquer le type de lobjet point :
type-point *identificateur;
Par exemple :
int *pi; double *px; struct Person *ptr_pers; union Real_Int *pu;
Attention: factorisation des dclarations de pointeurs
Lorsque lon veut dclarer plusieurs pointeurs en une seule dclaration (ce qui est par ailleurs de style souvent douteux), il importe de remarquer
103
c Jean-Paul R IGAULT
104
5. Pointeurs
que ltoile est attache lidenticateur (mme sil peut y avoir des espaces entre cette toile et le nom de lobjet). Ainsi int *pi, *pj; dclare bien deux pointeurs sur int, alors que int * pi, pj; dclare un pointeur sur entier (pi) et un simple entier (pj).
Une dclaration de pointeur peut bien sr comporter une initialisation. Loprateur & unaire permet de prendre ladresse dun objet. Plus prcisment, pour un objet a de type T, lexpression &a est un pointeur constant 1 sur a. Par exemple, dans
int i = 4, j; int *pi = &i;
pi est un pointeur sur entier qui contient ladresse de lobjet entier i. La gure 5.1 illustre la situation. Loprateur adresse ne peut sappliquer qu une rfrence sur un objet, et pas une valeur : &4, &(x + 2) ou encore &sin(3.14) sont des erreurs. Dans le chier dentte standard <stddef.h> est dnie la constante NULL : on peut donner cette valeur tout pointeur, quel que soit son type. On peut galement comparer tout pointeur cette valeur. Par convention, un pointeur qui a la valeur NULL ne pointe sur aucun objet. Dans une expression entre pointeurs, la valeur entire 0 est galement convertie implicitement en NULL 2 . On peut donc crire, par exemple,
1. car ladresse dun objet est une constante et ne peut pas tre modie par le programme 2. En fait, en ansi C, NULL est dni ainsi: #define NULL 0
c Jean-Paul R IGAULT
105
int *pi = 0;
au lieu de
int *pi = NULL;
Loprateur unaire * est linverse de &. Il sapplique un pointeur et rend une rfrence sur lobjet point. Ainsi, aprs les dnition de i, j et pi prcdentes
j = *pi; *pi = 5; (*pi)++; /* j vaut 4 */ /* i vaut maintenant 5 */ /* i vaut maintenant 6 */
Noter les parenthses indispensables cause de la prcdence des oprateurs (voir 3.4.1 et 5.1.3). Il est clair que loprateur dindirection ne doit tre appliqu un pointeur que si ce dernier pointe effectivement sur un objet. Sinon, lexcution du programme sera sanctionne (le plus souvent par un core dumped !) En particulier, appliquer loprateur dindirection un pointeur qui a la valeur NULL provoque invitablement une telle catastrophe.
Remarque : partie gauche daffectation et valuation unique pour les affectations composes
Avec les pointeurs, la partie gauche dune affectation peut devenir une expression assez complexe. En voici quelques exemples : int *p, *p1, *p2; ... /* lire: *(p++) = 3 */ *p++ = 3; *(a > b ? p1 : p2) = 4; On note la prcdence des oprateurs dans le premier exemple ; dans le second, selon le rsultat de la comparaison a < b, cest lobjet point par p1 ou celui point par p2 qui prend la valeur 4. Dans le cas dune affectation compose, on voit lintrt de la remarque sur lvaluation unique (3.3.4). Considrons en effet *(a < random() ? p1 : p2) += 2; o random est une fonction qui retourne une valeur alatoire. Cette instruction value une seule fois random, et ajoute 2 soit lobjet point par p1, soit celui point par p2. Il est clair que ceci est un comportement radicalement diffrent de celui de *(a < random() ? p1 : p2) = *(a < random() ? p1 : p2) + 2;
c Jean-Paul R IGAULT
106
5. Pointeurs
&pi est une constante de type pointeur sur pointeur sur entier ou encore double pointeur sur int , alors que ppi est une variable de ce mme type. La situation est schmatise dans la gure 5.2. Sur un pointeur double comme ppi on peut appliquer une ou deux fois loprateur dindirection : *ppi est une rfrence sur un pointeur sur entier (int *), alors que **ppi est une rfrence sur un entier.
Des pointeurs de mme type peuvent tre compars pour lgalit (==) ou lingalit (!=) ; ces mmes oprateurs permettent de comparer un pointeur, quel que soit son type, la valeur NULL. On peut aussi comparer les pointeurs grces aux oprateurs <, <=, > et >=. Cette comparaison na pas vraiment de sensla plupart du temps. Cependant elle a un sens lorsque les deux pointeurs pointent sur un mme agrgat, par exemple un tableau, comme dans lexemple suivant :
int t[100]; int *p1 = &t[0]; int *p2 = &t[5]; ... if (p1 < p2) {...}
/* OUI */
c Jean-Paul R IGAULT
107
/* ??? */
le rsultat dpend de limplmentation. Et quel peut bien tre lintrt de savoir si &t1[0] est infrieure ou suprieure celle de &t2[5] ?
Addition dun entier un pointeur
Pour un pointeur p sur un type T (ou une expression de type pointeur sur T) et une expression entire n, lexpression p+n a comme valeur un pointeur sur T qui pointe sur le nme objet de type T suivant si n > 0 (resp. prcdant si n < 0) celui point par p. Illustrons cette opration par lextrait de programme suivant :
struct Person *px; struct Person tab[100]; ... px = &tab[5];
Ici px pointe sur la structure Person qui est la composante dindice 5 du tableau tab. Dans ces conditions, px+3 pointe sur le troisime lment de type struct Person suivant tab[5], cest--dire tab[8]. Et px-2, quant lui, pointe sur tab[3]. On voit donc que laddition dun entier un pointeur nest pas la simple addition la valeur de ladresse mmoire. Il y a en fait une mise lchelle en fonction de la taille de lobjet point. Notons, une fois encore, quil ny a aucune vrication de dbordement dindice : dans lexemple prcdent, px-10 est certainement une erreur, mais elle nest dtecte ni la compilation, ni lexcution sauf option spciale de mise au point. Bien entendu, les cas particuliers daddition et de soustraction entires reprsents par les affectations composes (+= et -=) et les incrmentations et dcrmentations (++ et --) sont applicables aux pointeurs.
Soustraction de deux pointeurs (de mme type)
Si p1 et p2 sont deux pointeurs pointant sur des objets de mme type T, lexpression p2-p1 rend une valeur entire (int) qui est le nombre algbrique dobjets de type T contenu entre celui point par p2 et celui point par p1. Ce nombre est bien sr nul si p2 == p1, positif si p2 > p1, ngatif si p2 < p1. Comme pour la comparaison de pointeurs mentionne ci-dessus, une telle diffrence na vraiment de sens que si p1 et p2 sont non seulement de mme type, mais pointent sur des lments dun mme tableau : dans ce cas en effet on est sr quil y a un nombre entier exact dlments de mme type entre les deux pointeurs :
double t[10]; int u[3]; double v[5]; ... double *p1 = &t[3]; double *p2 = &t[7];
c Jean-Paul R IGAULT
108
5. Pointeurs
int n = p2 - p1; // OUI : n == 4, le nombre de double entre p2 et p1 ... n = &v[2] - p1; // cela compile, mais quest-ce que cela signifie ?
Attention : addition de deux pointeurs
Lopration daddition de deux pointeurs, mme sils sont de type identique na pas de sens. Le compilateur ansi C met dailleurs un message derreur. Quant aux programmeurs qui voudraient calculer un pointeur sur un objet qui est exactement au milieu des objets points par les deux pointeurs p1 et p2, ils devront dabord se rendre compte que la notion dtre exactement au milieu nest pas compltement dnie : elle dpend de la parit de p2-p1. Une fois le choix fait, ils pourront essayer des expressions de la forme p1 + (p2 - p1)/2 qui ont un sens, car il sagit de laddition dun entier un pointeur.
Par convention 3 , un pointeur sur void est un pointeur brut , une adresse machine. Tout pointeur peut tre converti en void* sans perte dinformation. Ceci signie que si le void* est nouveau converti dans le type initial, il retrouve la mme valeur (i.e., pointe sur le mme objet). Ces conversions, dans les deux sens, peuvent tre implicites ou bien sr explicites (cast) comme dans les exemples suivants :
int i = 4; double x = 3.14; int *pi = &i; double *px = &x; void *pv; ... pv = pi; pi = pv; ... pv = (void *)px; px = (double *)pv;
Dans le dernier exemple, pv prend la valeur dun pointeur sur int puis est converti en pointeur sur double. Si lon essaie de prendre une rfrence sur lobjet maintenant point par px (*px), cela revient interprter lentier point par pi comme un double, mais sans conversion ! Cest un problme analogue celui des unions (4.3.2) : le programmeur a la responsabilit de garder la trace du type exact point par un pointeur sur void. Il est noter que ce type derreur est indtectable dans le cas gnral, la compilation comme lexcution.
3. Ce ne peut tre quune convention, puisquil ny a aucun objet de type void.
c Jean-Paul R IGAULT
109
Enn, et pour des raisons videntes, les seules oprations permises sur un void* sont laffectation et les tests dgalit et dingalit, ainsi que la prise de ladresse du pointeur lui-mme. Les oprations dindirection (*), laddition et la soustraction dun entier, la soustraction de deux pointeurs, loprateur -> (5.2.1) ou encore lindexation (5.3.2) sont interdites pour les void*, car elle ncessitent toutes la connaissance du type, ou au moins de la taille, de lobjet point.
Dans le premier cas, cest la valeur de pconst qui est constante ; pconst ne peut donc pas apparaitre gauche dune affectation. En revanche, lobjet point qui sera donc toujours le mme nest pas constant, et *pconst peut apparaitre gauche dune affectation. Noter que linitialisation est ici ncessaire (cest la seule chance de donner une valeur pconst). Dans le second cas, cest lobjet point qui est constant : *p_sur_const ne peut donc apparaitre gauche dune affectation, mais p_sur_const en revanche pourra pointer sur plusieurs objets au cours de sa vie. Linitialisation nest donc pas indispensable ici. Dire, dans le cas de p_sur_const, que lobjet point est constant est un raccourci, voire un abus de langage. En fait, pour tre plus prcis, lobjet ne peut tre modi lorsquil est accd au travers du pointeur ; il peut cependant exister dautres voies daccs qui permettent de modier lobjet point. Cest clair dans lexemple prcdent, puisque p_sur_const pointe sur i qui est une variable. Donc lobjet en question est modiable qand il est accd par le nom i mais ne lest pas quand on utilise *p_sur_const. Un pointeur simple peut la fois tre constant et pointer sur une constante : la dclaration devient alors
const int *const pconst_sur_const = &i;
Ni pconst_sur_const, ni *pconst_sur_const ne peuvent apparaitre en partie gauche dune affectation. L encore linitialisation est ncessaire. Pour les pointeurs multiples, la combinatoire des possibilits augmente. Par exemple pour un double pointeur sur entier, on a les possibilits suivantes :
int **p; const int **p; int **const p; const int **const p; int *const *p; const int *const *p; etc... double pointeur variable **p est constant p est constant p et **p sont constants *p est constant *p et **p sont constants
c Jean-Paul R IGAULT
110
5. Pointeurs
On laisse au lecteur le soin de dterminer les cas o les initialisations sont indispensables. Le compilateur ansi C est extrmenent vigilant lors des affectations et initialisations mettant en jeu des pointeurs sur constante. Il impose la rgle naturelle suivante : il est toujours possible de restreindre laccs un objet, mais jamais de ltendre. On peut donc dcider de faire implicitement une constante partir dune variable, mais pas linverse. Les exemples suivants rsument les possibilits et les impossibilits dans le cas de pointeurs simples :
int i; const int ic = 4; int *p = &i; int *p = ⁣ const int *p = ⁣ const int *p = &i; int *const p = &i; int *const p = ⁣ const int *const p = &i; const int *const p = ⁣
OUI: pointeur sur variable NON: objet point constant OUI: pointeur sur une constante OUI: i non modiable travers p OUI: pointeur constant NON: pointeur sur une variable OUI: i non modiable travers p OUI
int *pp; int *const pconst = &i; const int *p_sur_const; const int *pconst_sur_const = ⁣ pp = pconst; pconst = pp; p_sur_const = pp; pp = p_sur_const; p_sur_const = pconst; p_sur_const = pconst_sur_const;
OUI: NON: affectation interdite OUI: restriction daccs NON: largissement daccs OUI OUI
Noter que dans la premire srie dexemples, &i est de type int *const, alors que &ic est de type const int *const. Un cas particulier dinitialisation est celui du passage darguments une fonction tudi en 7.3.2.
Si lon veut slectionner un champ de structure ou dunion travers le pointeur on peut utiliser loprateur de slection habituel (.), mais ceci oblige appliquer auparavant loprateur dindirection :
(*ps).position = ADMIN; (*pu).int_view = 3;
c Jean-Paul R IGAULT
111
Compte tenu de la prcdence des oprateurs (3.4.1), les parenthses sont indispensables et lcriture devient vite pnible. Cest pourquoi C introduit loprateur de slection de champ travers un pointeur, dnot ->. Lexpression p->champ dsigne une rfrence sur le champ de la structure ou de lunion pointe par p ; elle est strictement quivalente lexpression (*p).champ, mais moins lourde. Avec cet oprateur, les exemples prcdents scrivent de manire plus lgre :
ps->position = ADMIN; pu->int_view = 3;
Nous verrons dans la sous-section suivante (5.2.2) lorigine et tout lintrt de cette notation.
est interdit. La raison principale est que C insiste pour connaitre la taille des objets la compilation, et qu lvidence ceci est ici impossible. En revanche, la taille dun pointeur est connue la compilation. Il est alors possible quun champ dune structure ou dune union soit de type pointeur sur cette structure ou union. Lexemple prcdent peut donc scrire correctement
struct Recur { int i; struct Recur *pr; };
Lun des exemples les plus vidents de type rcursif est celui des listes simplement chanes. Le chier dinclusion simple_list.h en 5.1 prsente la dnition dun tel type : une liste simplement chane de valeurs entires. Ce chier contient aussi le prototype de la fonction insert, dont le corps est prsent plus loin. La structure List_Cell 4 comporte donc deux champs : une valeur entire (val) et un pointeur (next) sur un objet de mme type soit List_Cell* . Ceci va permettre de chaner linairement de tels objets comme schmatis sur
4. On voit que List_Cell est la fois le tag et le nom du type dni par typedef. Ceci est autoris, comme nous le verrons en 8.1. Noter aussi que le tag est ici obligatoire car le nom du type dni par typedef nest pas encore connu lintrieur de la dnition de structure.
c Jean-Paul R IGAULT
112
5. Pointeurs
10
typedef struct List_Cell { int val; struct List_Cell *next; } List_Cell; struct List_Cell *insert(List_Cell *, int); #endif
la gure 5.3. Par convention mais cette convention est assez universelle la liste sarrte quand le pointeur next vaut NULL. La liste tout entire est reprsentable par un pointeur (type List_Cell *) sur son premier lment (phead).
Remarque: origine de la notation che
Sur la gure 5.3, on voit lorigine et mme llgance de la notation ->. En effet, dans un expression comme phead->next->next->val qui dsigne la valeur porte par le troisime lment de la liste (le second ave la valeur 3), les -> correspondent exactement aux ches du schma.
La fonction insert est dnie dans un autre chier prsent en 5.2. On doit bien entendu inclure le chier simple_list.h (programme 5.1) an dimporter la dnition du type List_Cell. La directive #include effectue ce travail : noter lutilisation des doubles quotes ("...") la place des piquants
c Jean-Paul R IGAULT
113
(<...>) qui indiquent ici de chercher ce chier dabord dans le rpertoire courant, avant dexaminer les rpertoires standards du systme (voir 6.2.2). La fonction insert insre la valeur entire v de son deuxime argument dans la liste dont la tte est pointe par son premier argument. Le point dinsertion est devant le premier lment de la liste dont la valeur est suprieure ou gale v. Ainsi, si la liste tait range par ordre croissant avant insertion, elle le demeure aprs. La liste peut comporter des rptitions. La fonction retourne un pointeur 5 sur la nouvelle tte de liste. En effet, au cas o v est infrieur toutes les valeurs de la liste, cest la nouvelle cellule qui devient la tte de liste. Le parcours de liste et linsertion, schmatiss en 5.4, mettent en jeu trois pointeurs : 1. pcurr visite les cellules lune aprs lautre ; 2. pprev pointe toujours sur la cellule prcdant immdiatement celle pointe par pcurr, sauf au dbut quand pcurr vaut phead, o il est NULL ; 3. enn pnew pointe sur une nouvelle cellule de liste qui est alloue dynamiquement pour contenir la valeur v. Lallocation dynamique est effectu par lappel de la fonction malloc. Cette fonction reoit en argument la taille dun objet, obtient du systme lallocation dune zone mmoire sufsamment grande pour contenir un tel objet et retourne un pointeur sur cette zone (voir 9.1.4). Nous ignorons ici le traitement du cas derreur o la mmoire serait indisponible : malloc retournerait alors la valeur NULL. Le parcours de liste sinterrompt soit en n de liste (pcurr devient NULL), soit ds que la valeur contenue dans la cellule courante (pcurr->val) devient suprieure ou gale v. On note que tout le travail est effectu dans la partie
5. Pour une explication de la dclaration de cette fonction, et en particulier de son type de retour, voir 7.3.1.
c Jean-Paul R IGAULT
114
5. Pointeurs
#include "simple_list.h" List_Cell *insert(List_Cell *phead, int v) { List_Cell *pcurr; List_Cell *pprev; List_Cell *pnew; pnew = malloc(sizeof(List_Cell)); pnew->val = v;
10
15
20
for (pcurr = phead, pprev = NULL; pcurr != NULL && v > pcurr->val; pprev = pcurr, pcurr = pcurr->next) { /* Vide */ } /* Ici, soit pcurr est NULL, soit pcurr pointe sur le premier element superieur ou egal a v / * if (pprev != NULL) { /* La liste netait pas vide (phead != NULL) */ pnew->next = pcurr; pprev->next = pnew; return phead; } else if (pcurr != NULL) { /* La nouvelle cellule devient la tete de liste */ pnew->next = pcurr; return pnew; } else { /* La liste etait vide (phead == NULL) */ pnew->next = NULL; return pnew; } /*NOTREACHED*/ }
25
30
35
40
45
c Jean-Paul R IGAULT
115
contrle de la boucle for (le corps est vide) : cest videmment une question de style et de got. Lorsque la boucle sarrte trois cas sont possibles : 1. si pprev nest pas NULL on insre pnew 6 juste derrire la cellule pointe par pprev noter que pcurr peut tre NULL si le nouvel lment devient le dernier de la liste ; la tte de liste est inchange (gale phead) ; 2. si pprev est NULL, mais pas pcurr, cest que pcurr pointe sur le premier lment de la liste (pcurr est gal phead) et que pnew devient la nouvelle tte de liste ; 3. enn si pprev et pcurr sont tous les deux NULL, cest que phead ltait aussi et pnew devient la tte de liste et aussi la seule cellule. Enn, le programme 5.3 sert de test pour la fonction insert. Il lit une liste de nombres entiers, les insre un un et imprime le rsultat qui doit tre une liste trie. Noter ici que le source du programme tient sur deux chiers ; pour obtenir un excutable, on peut utiliser la commande
% gcc -o simple_list simple_list_main.c simple_list.c
mais il est conseill dutiliser make (10.2.2). Ici, on a besoin dun chier de nom makefile (ou Makefile) dans le mme rpertoire que les chiers-source. En voici le contenu (en supposant que vous avez dni les variables denvironnement CC et CFLAGS) :
# Makefile pour la liste simplement chaine # Cible finale : excutable simple_list simple_list : simple_list_main.o simple_list.o T A B $(CC) -o simple_list simple_list_main.o simple_list.o # Dpendances individuelles simple_list_main.o simple_list.o : simple_list.h # Nettoyage clean : T A B -rm *.o simple_list
Les tab reprsentent effectivement le caractre tab du clavier. Il suft alors dinvoquer make depuis le shell puis dexcuter le programme produit :
% make gcc -g -std=c99 -Wall -c simple_list_main.c simple_list.c gcc -g -std=c99 -Wall -c simple_list.c gcc -o simple_list simple_list_main.o simple_list.o % simple_list Entrez une suite dentiers terminees par EOF 1 2 5 7 0 6 4 2 6 9 -1 -3 Liste triee: -3 -1 0 1 2 2 4 5 6 6 7 9 %
Allocation dynamique ou variable locale ?
Dans le programme prcdent les cellules de la liste sont alloues dynamiquement, grce lappel de malloc. Ceci est invitable. Comme on ne
6. abus de langage pour la cellule pointe par pnew !
c Jean-Paul R IGAULT
116
5. Pointeurs
#include "simple_list.h" int main() { List_Cell *phead = NULL; List_Cell *p; int n; printf("Entrez une suite dentiers terminees par EOF\n"); while (scanf("%d", &n) != EOF) phead = insert(phead, n); printf("Liste triee:\n"); for (p = phead; p != NULL; p = p->next) printf("%d ", p->val); putchar(\n); return 0; }
10
15
20
connait pas lavance le nombre (maximal) de cellules ncessaires, il nest pas possible de les allouer, par exemple, dans un tableau dont la taille devrait tre connue la compilation ; il faut donc les crer au fur et mesure des besoins. Cette cration ne peut pas tre sous la forme dune variable locale (automatique voir 8.3.1), dans la fonction insert, car une telle variable disparaitrait ds que la fonction se terminerait alors que les cellules et la liste doivent perdurer (aussi longtemps que le programmeur le dsire).
La fonction dinsertion prcdente utilise deux pointeurs pour parcourir la liste : sur la cellule courante et sur la cellule immdiatement prcdente. On peut se demander sil nest pas possible dutiliser un unique pointeur. La rponse est positive, comme le montre le programme 5.4. Le pointeur unique est en fait un double pointeur pp qui pointe tour tour, non pas sur chaque cellule, mais sur le champ next de la cellule. Il faut aussi modier la structure de donnes elle mme : on introduit une cellule de liste xe qui sert de tte de liste et dont le champ val nest pas utilis. Avec cette hypothse, la tte de liste est xe et la fonction insert na plus besoin de retourner une valeur. En outre, pour reprsenter la liste vide, il suft de faire boucler la tte de liste sur elle-mme (gure 5.5(a)). On constate que le code est plus court, sinon plus simple comprendre. Il est aussi plus homogne puisquil ne traite aucun cas particulier comme le faisait la programmation prcdente. Bien sr, sa lecture et plus encore son cri-
c Jean-Paul R IGAULT
117
Programme 5.4 Insertion dans une liste avec un seul (double) pointeur
/***** Fichier: Simple_List/simple_list_double_ptr.c *****/ #include <stdlib.h> #include <stdio.h>
5
10
typedef struct List_Cell { int val; struct List_Cell *next; } List_Cell; void insert(List_Cell *phead, int v) { List_Cell **pp; List_Cell *pnew; pnew = malloc(sizeof(List_Cell)); pnew->val = v;
15
20
25
for (pp = &phead->next; *pp != NULL && v > (*pp)->val; pp = &(*pp)->next) { /* Vide */ } pnew->next = *pp; *pp = pnew; }
30
35
int main() { List_Cell head = {0, NULL}; List_Cell *p; int n; printf("Entrez une suite dentiers terminees par EOF\n"); while (scanf("%d", &n) != EOF) insert(&head, n);
40
printf("Liste triee :\n"); for (p = head.next; p != NULL; p = p->next) printf("%d ", p->val); putchar(\n);
45
return 0; }
c Jean-Paul R IGAULT
118
5. Pointeurs
c Jean-Paul R IGAULT
119
ptab est un tableau de 10 pointeurs sur entiers (int) et p2tab un tableau de 100 doubles pointeurs sur entiers. Pourquoi en est-il ainsi ? Comment interprter ces dclarations? Un petit retour en arrire simpose. Lorsque nous dclarons un entier par
int i;
nous disons simplement que lobjet dnot i, quand il est rencontr dans une expression, a une valeur de type int. De mme une dclaration comme
int t[10];
indique que si lon rencontre t[e] dans une expression, o e est une expression entire, alors cette forme dsigne un int. Toujours de manire analogue, la dclaration
int *pi;
signie que dans une expression, *pi est un int. Comme * est loprateur dindirection, ceci revient bien dire que pi est un pointeur sur int. Revenons maintenant aux dclarations
int *ptab[10]; int **p2tab[100];
Compte tenu de cette rgle dinterprtation, la premire dclaration signie que *ptab[e], o e est une expression, est un int. A cause de la prcdence des oprateurs (3.4.1), *ptab[e] doit se lire comme *(ptab[e]). Ceci signie que ptab[e] est un pointeur sur int, puisquon peut lui appliquer loprateur dindirection. Et nalement ptab est lui-mme un tableau de pointeurs sur int, puisquon peut lui appliquer loprateur dindexation. Cette rgle de lecture des dclarations de C est toujours valable et elle sera prcieuse lorsque nous tudierons les dclarations de pointeurs sur fonctions (7.3.3).
Revenons sur lopration daddition dun entier un pointeur (5.1.3) dans le cadre de cet extrait de programme :
int t[10]; int *p = &t[0];
Le pointeur p pointe donc sur le premier lment du tableau t. Conformment la dnition de laddition dun entier un pointeur, pour tout entier i (suprieur ou gal 0 et infrieur 10), p+i est un pointeur sur llment dindice i de t. Donc p+i == &t[i]. Donc encore, *(p+i) et t[i] rfrencent le mme objet. A cause de ces remarques, le langage C permet dappliquer lopration dindexation un pointeur. Pour tout pointeur p et toute expression i, p[i] est dni comme tant quivalent *(p+i). Ceci permet donc de manipuler des pointeurs comme des tableaux mono-dimensionnels. Par exemple, aprs
struct Person *ppers = malloc(10*sizeof(struct Personne));
c Jean-Paul R IGAULT
120
5. Pointeurs
le pointeur ppers pointe sur une zone de mmoire pouvant accueillir un tableau de 10 structures Person. On peut dsigner les diffrentes composantes sous la forme ppers[0], ppers[1]... On pourra donc crire des instructions comme
ppers[i].age = 18;
Toujours dans le cadre de lexemple prcdent o p pointe sur le premier lment du tableau t, on peut donc dsigner les lments de t par t[0], t[1]... bien sr, mais aussi par p[0], p[1]..., et encore par *(p+0), *(p+1).... Si lon peut traiter un pointeur comme un tableau, on doit par cohrence traiter un tableau comme un pointeur ! Ceci revient dire que lon doit aussi pouvoir dsigner les lments de t par *(t+0), *(t+1)... ou encore que lon doit considrer un nom de tableau comme un pointeur sur sa premire composante. C fait donc hardiment cette assimilation des noms de tableaux des pointeurs. Ainsi, lextrait prcdent aurait-il pu scrire
int t[10]; int *p = t; /* t == &t[0] */
La gure 5.6 illustre cette relation trs particulire entre pointeurs et tableaux en C. Comme les chanes de caractres ne sont que des tableaux particuliers, C est aussi amen considrer quune chane de caractres littrale comme "bonjour\n" est aussi de type pointeur (sur caractre, donc char *).
Diffrence entre tableaux et pointeurs
Lquivalence entre tableau et pointeur nest cependant pas totale : Un pointeur comme p est une variable ; en revanche, t est un pointeur sur un objet dont le compilateur est maitre de ladresse, pas le program-
c Jean-Paul R IGAULT
121
meur. Un nom de tableau est donc un pointeur constant (t est de type int *const). Dautre part, une dnition de tableau comme celle de t rserve effectivement la place pour toutes les composantes (ici 10), alors que la dnition de p ne rserve que la place dun pointeur. La deuxime remarque est fondamentale : on peut manipuler des pointeurs comme des tableaux, mais aprs stre assur quils pointent effectivement sur des tableaux ! Une consquence de cette remarque est que sizeof(t) est la taille du tableau tout entier, alors que sizeof(p) est la taille dun simple pointeur. Ainsi,
sizeof(t) == 10*sizeof(int) sizeof(p) == sizeof(int *)
Sur une machine 32 bits, sizeof(t) vaut 40, alors que sizeof(p) ne vaut que 4 ! Une autre consquence est que C ne sait pas distinguer un pointeur sur un objet individuel dun pointeur sur un tableau dobjets !
Cas des tableaux multi-dimensionnels
Si un nom tableau mono-dimensionnel a comme type celui de pointeur sur le type de ses composantes, quen est-il dun tableau bi-dimensionnel comme par exemple Days dj rencontr en 4.1.3 ?
const char Days[][10] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", };
La rponse est vidente : Days est de type double pointeur sur charactre (char**). Lexpression Days[i] devient possible (quivalente *(Days+i), et donc de type char* : elle dsigne tout naturellement la ligne dindice i de Days, cest--dire un tableau mono-dimensionnel. Considrons la dnition suivante :
const char *Jours[] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", };
Cette fois, Jours est un tableau de 10 pointeurs sur caractres. Linitialisation est correcte, puisque nous avons dj remarqu que les chanes comme "lundi" taient des pointeurs sur caractres. Les noms Days et Jours sont des expressions de mme type, savoir char**. Ils sont tous les deux simplement indexables : Days[1], comme Jours[1], pointe sur la chane "mardi". Ils sont galement tous les deux doublement indexables : Days[1][3], comme Jours[1][3], est une rfrence sur le caractre d de "mardi". Il y a cependant une diffrence : Days occupe en mmoire une taille de 70 caractres (sizeof(Days) vaut 70), alors que Jours noccupe que la place de 7 pointeurs (par exemple sizeof(Jours) vaut 28 sur ma station de travail). La gure 5.7 montre les reprsentations diffrentes en mmoire des deux objets Days et Jours.
c Jean-Paul R IGAULT
122
5. Pointeurs
Une autre diffrence entre tableaux et pointeurs concerne la double (ou plus) indexation. La double indexation dun double pointeur pur cest--dire qui nest pas lui mme soit un tableau de pointeurs, soit un tableau bi-dimensionnel na pas de sens : dans char **pp = Days; /* probleme identique avec Jours */
comment pourrait-on calculer pp[i][j] ? Le calcul dindice ncessite de connaitre le nombre de colonnes du tableau la compilation et cette information nest pas disponible pour le pointeur pp.
tiers trie par ordre croissant en dnissant les fonctions suivantes : chercher si une valeur est dans la liste ; retourner le nime lment, sil existe ;
c Jean-Paul R IGAULT
123
dtruire une valeur dans la liste, si elle sy trouve (une seule occurrence et toutes les occurences) ; fusionner une liste avec une autre, cest--dire ajouter la premire liste une copie des valeurs de la seconde ; bien entendu la liste nale doit aussi tre trie ; dupliquer une liste, cest--dire retourner une nouvelle liste dont les valeurs sont des copies des valeurs de la premire liste ; imprimer une liste.
Exercice 5.2 (Liste trie doublement chane) Mme exercice que prcdem-
ment, mais avec des listes doublement chaines. La cellule de liste a donc la forme suivante :
struct DList_Cell { int val; struct DList_Cell *prev; /* pointeur arriere */ struct DList_Cell *next; /* pointeur avant */ };
Par ailleurs, limplmentation des listes avec une n marque par un pointeur nul comme dans lexercice 5.1 est dj maladroite pour des listes simplement chane mais elle est carrment dbile pour des listes doubles. Vous utiliserez donc limplmentation dcrite dans la gure 5.8. Ici la liste contient toujours une cellule de garde (dont la valeur ne fait pas partie de la liste). Lorsque la liste est vide, les deux pointeurs de cette cellule pointent sur elle-mme (gure 5.8(a)). Sinon, les cellules sont chanes naturellement, et la liste reboucle sur la cellule de garde. Si phead est ladresse de la cellule de garde (gure 5.8(b)), la premire cellule utile est donc pointe par phead->next et la liste se termine la cellule phead exclue. Bien entendu, un lger inconvnient par rapport limplantation directe de lexercice 5.1 est que vous avez maintenant besoin de dnir une fonction dinitialisation de liste pour crer la cellule de garde et vous ne devez pas oublier dinvoquer cette fonction avant de manipuler le liste.
Remarque sur le test de ce programme
Un bon moyen pour (commencer ) tester ce programme est de prendre comme programme de test exactement le mme que pour lexercice 5.1, de rcuprer les sorties des deux programmes dans des chiers et de vrier que ces chiers sont identiques, par exemple en utilisant les commandes cmp ou diff du shell.
Exercice 5.3 (Manipulation de chanes de caractres) Les programmes 3.2 et
3.3 montrent comment on peut manipuler les chanes de caractres en C, sous lhypothse (forte) que les dites chanes se terminent par le caractre nul. La bibliothque standard de C fournit un certain nombre de fonctions de manipulation de chanes qui reposent toutes sur cette hypothses. Ces fonctions sont dclares dans le chier dentte <string.h> et documentes dans la page de manuel correspondante (pour vous familiariser avec elles, faites man string). Lexercice consiste utiliser quelques unes de ces fonctions pour raliser des oprations de sous-chanes, oprations peu reprsentes dans la bibliothque standard.
c Jean-Paul R IGAULT
124
5. Pointeurs
Dans le mme esprit (et sous la mme hypothse), crire (et testez !) les fonctions suivantes (s, s1 et s2 dsignent des chanes, i et n des entiers, c un caractre) : string_trim(s) Enlve les espaces initiaux et naux de la chane. substring(s, i, n) Retourne la sous-chane de s de longueur maximale n commenant lindice i ; si n est trop grand, on ne retourne que ce qui est possible. substring_search(s1, s2) Recherche la premire occurrence de la chane s1 dans s2 et retourne lindex correspondant dans s2 ou -1 (si s1 nest pas une sous-chane de s2). insert_substring(s1, s2, i) Insre la chane s1 devant le caractre dindice n de s2 ; ne fait rien si n est invalide.
c Jean-Paul R IGAULT
125
delete_substring(s, i, n) Dtruit la sous-chane de s de longueur maximale n commenant lindice i ; si n est trop grand, on ne dtruit que ce qui est possible. replace_substring(s1, i, n, s2) Remplace la sous-chane de s1 de longueur maximale n commenant lindice i par la chane s2; si n est trop grand, on ne remplace que ce qui est possible. tokenize_string(s, c, ts[], n, empty_fields) tant donne une string s compose de champs spars par le caractre c, copie ces champs un par un dans les lments du tableau de chanes ts et retourne le nombre de champs ; n est la taille maximale du tableau ts. Si le boolen empty_fields est vrai et si deux sparateurs c sont adjacents, la chane correspondante de ts doit tre la chane vide. Ainsi aprs
char *ts[10]; char s[] = "/bin:/usr/bin::/usr/local/bin:/home/jpr/bin"; int n = tokenize_string(s, :, ts[], 10, b);
si b est vrai, n vaut 5, et ts[0] est "/bin", ts[1] est "/usr/bin", ts[2] est la chane vide, ts[3] est "/usr/local/bin", etc. En revanche is b est faux, il ny a pas de champ vide (ts[2] est "/usr/local/bin", etc.
Remarque 1
Commencez par dnir prcisment les prototypes (type des arguments et de la valeur de retour de ces fonctions), et placez les dans un chier dentte substring.h. Puis, dans substring.c, incluez substring.h et dnissez chacune des fonctions. Compilez et testez vos fonctions au fur et mesure : inutile dessayer de tout crire dabord et de se retrouver devant des pages de messages derreur de compilation, ou alors face un crash (memory fault, segmentation violation, etc.) dont on ne sait o il a eu lieu. Soyez peu prs srs que vous allez effectivement crasher dans vos premires versions de certaines fonctions.
Remarque 2
Pour certaines de ces fonctions, vous serez sans doute obligs de faire des copies de chanes de caractres et, comme leur longueur nest connue qu lexcution, dutiliser malloc. Quel inconvnient(s) voyez vous cela ? Pourquoi la bibliothque standard ne dnit-elle pas plus de fonctions du type de celles que vous venez de raliser ?
Remarque 3 : compatibilit
ISO
Il y a beaucoup de fonctions disponibles dans <string.h> et elle ne sont malheureusement pas toutes standardises. Nutilisez que les fonctions conformes la norme iso. Elle sont indiques comme conforme iso 9899 dans les pages du manuel (man).
Exercice 5.4 (Tri dun tableau de pointeurs) Modier le tri par insertion du
c Jean-Paul R IGAULT
126
5. Pointeurs
Exercice 5.5 (Commande fgrep) Utilisez les fonctions de lexercice 5.3 pour crire une version simplie de la commande fgrep. Cette commande reoit
une chane de caractre en argument, lit lentre standard et en afche toutes les lignes qui contiennent la chane.
Remarque
Larbre complet est bien entendu dni par un pointeur sur sa racine et a la forme dcrite dans la gure 5.9. Ecrire la procdure dinsertion suivante pour un entier n donn : si la valeur porte par la racine est infrieure (resp. suprieure) n, insrer la valeur n dans le sous-arbre de gauche (resp. de droite) ; si les deux valeurs sont gales, ne rien insrer ; cette procdure est applique rcursivement sur les sous-arbres. Ensuite crire une fonction de recherche qui explore larbre pour trouver une valeur donne et retourne un pointeur sur la cellule correspondante (ou bien le pointeur nul si la valeur nest pas dans larbre). Enn, crivez une fonction dimpression de larbre en profondeur dabord : tire dexemple, larbre de la gure 5.10 devra tre imprim ainsi :
10 -6 --3 ---EMPTY
c Jean-Paul R IGAULT
127
---EMPTY --9 ---7 ----EMPTY ----EMPTY ---EMPTY -12 --EMPTY --14 ---EMPTY ---17 ----EMPTY ----EMPTY
chotomique dun entier donn dans une liste trie dentiers. La fonction retourne lindice de la valeur dans la liste, ou -1 si la recherche choue.
Recherche dichotomique
Soit a la valeur rechercher dans la liste trie u0 u1 ... un ; si a < un/2 (resp. a > un/2 ) alors a doit tre recherch dans la liste u0 , u1 , ..., un/21 (resp. un/2+1 , ..., un ) ; si a = un/2 alors le rsultat est bien videmment n/2.
Remarque 1
On pourra crire une version rcursive et/ou une version non-rcursive de ce programme.
Exercice 5.8 (Pathologie des pointeurs) Les pointeurs fournissent des possibi-
lits trs puissantes mais sont aussi une des principales cause derreur dans les programmes C. Parmi les erreurs frquentes : Utiliser lobjet point par un pointeur sans que ce pointeur ait t initialis (ou aprs quil ait t initialis NULL). Se tromper lors de larithmtique des pointeurs en dbordant les limites et en pointant ainsi sur des objets de type diffrent de ce que lon croit.
c Jean-Paul R IGAULT
128
5. Pointeurs
Ne pas librer la mmoire alloue par malloc lorsque lon nen a plus besoin ( fuite de mmoire ). En particulier, lorsque lon utilise une fonction (de bibliothque standard ou pas) dont certains des paramtres sont des pointeurs, on doit toujours se poser les questions suivantes (et obtenir la rponse) : le paramtre peut-il tre un simple pointeur (possiblement non initialis) ? ou doit-il tre un pointeur sur un objet effectivement allou ? et dans ce cas, est-ce un pointeur sur un objet individuel ou sur un tableau ? Si la fonction retourne un (ou plusieurs) pointeurs, il faut galement toujours se demander si les objets correspondants ont t allous et comment (i.e., par malloc ou autre ?). Testez la robustesse des fonctions que vous avez ralises lors de lexercice 5.3 par rapport ces problmes.
c Jean-Paul R IGAULT
Chapitre 6
Le prprocesseur ANSI C
prprocesseur constitue premire phase dun proLegramme C.textuelles. sur lelasource, chier par de la compilationnombre de Il effectue chier, un certain transformation Prsent depuis lorigine en C, il a subit un nettoyage complet lors de la normalisation dansi C. Le nouveau prprocesseur est donc largement incompatible avec lancien (dit traditionnel ). Ce dernier nexiste pratiquement plus et nous ne parlerons ici que du prprocesseur ansi C et de ses extensions en C99. Ce chapitre ne prtend pas tre une tude exhaustive de toutes les possibilits du prprocesseur ansi C : ce serait trop long et mon avis sans grand intrt. Les possibilits sont trop nombreuses, souvent obscures, parfois inadquates. On doit rserver lutilisation du prprocesseur des oprations o il est incontournable, savoir : dnition de constante, inclusion de chiers, compilation conditionnelle, et, en C90 uniquement, en prenant des prcautions, dnition de fonctions en ligne .
129
c Jean-Paul R IGAULT
130
6. Le prprocesseur ANSI C
La norme ne prcise pas comment sont raliss ces diffrents traitements. Dans la plupart des implmentations, les quatre premires phases sont de la responsabilit du prprocesseur C, un programme qui effectue des transformations textuelles sur le source. Outre les transformations cites ci-dessus, le prprocesseur permet lutilisateur de spcier certaines transformations particulires : linclusion de chiers (source), le remplacement de mots (i.e. de tokens voir 3.1.2), la compilation conditionnelle. Ltude de ces transformations est lobjet de ce chapitre. Les directives au prprocesseur tiennent sur une ligne (logique 1 ) dont le premier caractre qui nest ni un blanc, ni une tabulation horizontale, doit tre un dise (#). Noter quaucun point-virgule ne termine les directives au prprocesseur.
Le nom-de-fichier ne doit pas contenir lun des caractres (ou lune des squences) suivants, sinon le rsultat risque dtre indni : > (au moins pour la forme en <...>), n de ligne, , ", \, /*. Leffet de cette directive est simple : tout se passe comme si la ligne correspondante tait remplace par le contenu du chier. Les chiers inclus peuvent eux-mmes comporter des directives #include : linclusion sera alors rcursive (voir cependant 6.4.2).
cherche le chier dans un liste de rpertoires dpendant de linstallation du systme, mais qui peut tre modie par loption -I de la commande de compilation C. Le nom-de-fichier est alors un nom relatif ces rpertoires. Sur la plupart des systmes Unix, la liste par dfaut est rduite au rpertoire /usr/include. Par exemple :
#include <stdio.h> #include <sys/signal.h> /* defaut: /usr/include/stdio.h */ /* defaut: /usr/include/sys/signal.h */
1. qui peut tre constitue de plusieurs lignes physiques, si lon prcde chaque n de ligne du caractre \
c Jean-Paul R IGAULT
131
La forme
#include "nom-de-fichier"
quant elle, commence par chercher dans le rpertoire o se trouve le chier source qui comporte cette directive dinclusion 2 . Si le chier nest pas trouv, le fonctionnement est identique la forme avec des piquants (<...>).
permet de remplacer lidenticateur ident par toute la chane-dfinition. Noter que cette chane commence au premier blanc rencontr aprs ident. Lidenticateur ident nest pas remplac sil est lintrieur dune chane de caractres littrale. Par exemple, le chier source suivant
#define NBUF 512 #define NBUF2 NBUF*2 #define MESSAGE "NBUF = " int t[NBUF]; int u[NBUF2]; int main() { printf(MESSAGE "%d\n", NBUF); }
c Jean-Paul R IGAULT
132
6. Le prprocesseur ANSI C
aprs concatnation des chanes littrales adjacentes. On remarque aussi que la macro NBUF est correctement expanse dans la dnition de NBUF2. Lutilisation la plus frquente de cette forme de dnition de macros est la dnition de constantes, en particulier celles servant dimensionner des tableaux. Nous avons en effet dj remarqu (4.1.1) quen C90 il ntait pas possible de le faire avec une const int et que ce ntait que partiellement possible avec C99 (4.1.4).
Attention : une macro nest pas vraiment une dnition de constante
Une macro telle que #define N 10 ne dnit pas vraiment une constante, mais simplement une substitution textuelle. Certaines substitutions conduisent donc des expressions incorrectes : par exemple, ladresse de N, &N, na pas de sens, car cest quivalent &10 et la valeur 10 na pas dadresse. En revanche une const int N = 10 est un vritable objet de C qui a un type (const int videmment) et, si ncessaire, une adresse (&N).
toutes les occurrences de lidenticateur MAX 3 suivi dune paire de parenthses entourant deux suites de mots spares par une virgule (ouf !) seront remplaces. Ainsi
x = MAX(y + 4, z - 1);
deviendra
x = ((y + 4) > (z - 1) ? (y + 4) : (z - 1));
C99
On voit donc que ce type de dnition permet dobtenir des sortes de fonctions qui sont expanses en ligne, plutt que dtre invoques avec une squence dappel normale. En gnral, on utilise cette possibilit pour des raisons defcacit. Notez que C99 a introduit la possibilit davoir des macros avec un nombre variable dargument (variadic macro voir 11.1.2).
Attention : une macro nest pas une fonction !
Les diffrences entre macro et fonction sont nombreuses et doivent inciter la prudence dans lutilisation des macros arguments. Illustrons ces diffrences laide de la macro MAX ci-dessus et dune vraie fonction Max :
3. Noter quil ny pas despace entre MAX et la parenthse ouvrante (pourquoi ?).
c Jean-Paul R IGAULT
133
int Max(int a, int b) { return a > b ? a : b; } Tout dabord la fonction est type, et le compilateur vrie le bon usage des types. Il nen est pas de mme pour une macro : MAX est valable aussi bien pour des entiers que des rels par exemple. Cest une puissance supplmentaire, mais cest aussi, parfois, une perte de scurit. Ensuite, un appel de fonction est syntaxiquement quivalent une valeur de son type de retour : x = 2 + Max(y, z); se lit bien x = 2 + (Max(y, z)); Avec la macro, lanalyse de lexpression nest faite quaprs le remplacement textuel, ce qui peut provoquer quelques suprises. Supposons par exemple quau lieu de dnir la macro MAX avec ce qui parait tre un sur-parenthsage, nous ayons crit #define BAD_MAX(a, b) a > b ? a : b alors x = 2 + BAD_MAX(y, z); est expans en x = 2 + y > z ? y : z; qui, compte tenu de la prcdence des oprateurs (3.4.1), se lit x = (2 + y) > z ? y : z; ce qui, pour le moins, nest pas trs naturel. Le sur-parenthsage de la dnition de MAX vite ce genre de surprise. Enn la troisime diffrence est quune fonction nvalue ses arguments quune seule fois ; comparer int y = 0; int z = 0; x = Max(y++, ++z); o les incrmentations nont lieu quune fois (y et z valent 1 aprs lappel) avec int y = 0; int z = 0; x = MAX(y++, ++z); qui va tre expans en x = ((y++) > (++z) ? (y++) : (++z)); avec comme consquence quune des incrmentations (savoir laquelle dpend du rsultat de la comparaison) sera effectue deux fois ! Pour toutes les raisons invoque ci-dessus, C99 a emprunt C++la notion de fonction inline (mot-cl inline). Voir 7.2.4 pour les dtails.
C99
c Jean-Paul R IGAULT
134
6. Le prprocesseur ANSI C
donne lexpansion
i = A*2 *A*2;
ansi C introduit dans le prprocesseur loprateur de concatnation de symboles dsign par ##. Ainsi dans
#define cat(a, b) a ## b ... int cat(toto, _old) = toto;
Dans une macro avec arguments, un argument formel nest pas remplac si dans la chaine dnissante il gure lintrieur dune chane littrale. Par exemple, dans
#define message(a) "le message est: a" ... printf(message(bonjour));
ce qui nest pas forcment ce quon veut. Loprateur unaire # appliqu un argument de macro en effectue la stringication 4 cest--dire transforme cet argument en chane littrale. Ainsi, avec
#define message(a) "le message est: " #a ... printf(message(bonjour));
qui deviendra
printf("le message est: bonjour");
c Jean-Paul R IGAULT
135
En C99 est aussi dnie la macro __STDC_VERSION__ qui a la valeur (entire longue) 199901L. Voici un exemple dutilisation inspir de la macro assert dnie dans le chier <assert.h>.
#define ASSERT(cond) \ if (!(cond))\ {\ printf("%s %s: ", __DATE__, __TIME__);\ printf("%s, line %d: ", __FILE__, __LINE__);\ printf("assertion failed: " #cond "\n");\ exit(1);\ }
Si ASSERT est invoqu avec une condition fausse (noter une fois de plus le sur-parenthsage indispensable), on obtient un message comme
Jan 14 2008 15:25:02: cpp-test1.c, line 22: assertion failed: a < b
c Jean-Paul R IGAULT
136
6. Le prprocesseur ANSI C
lignes-si-faux #endif
On value lexpression-statique qui est constitue laide des oprateurs habituel de C et sa valeur de vrit dtermine les lignes source que lon considre. Cette valeur de vrit doit bien entendu tre valuable la compilation.
#define NBUF 512 #define NBUF2 NBUF*2 ... #if NBUF > 512 && NBUF2 > 1024 int t[NBUF]; int u[NBUF2]; #else int t[512]; int u[1024]; #endif
Bien entendu, la clause #else est optionnelle. En outre la clause #elif permet denchaner les conditions :
#if NBUF < 512 ... #elif NBUF < 1024 ... #elif NBUF < 2048 ... #else ... #endif
La encore, la clause #else est optionnelle. Le prprocesseur fournit un oprateur permettant de dterminer si un symbole a t dni (cest--dire a fait lobjet dun #define non encore annul par un #undef). Cest loprateur defined :
#if defined(LINUX) || defined(MSWINDOWS) || defined(MACOS) ... /* code commun a ces systemes */ #endif
La forme suivante
#if defined(__STDC__) double cos(double); #else double cos(); #endif
De la mme manire,
c Jean-Paul R IGAULT
137
scrit aussi
#ifndef getchar int getchar(void); #endif
Supposons aussi que A.h et B.h aient chacun besoin des dnitions contenues dans un troisime chier dentte, Common.h ; ils vont tout naturellement inclure chacun ce dernier chier. La consquence est que ce chier va tre inclus deux fois dans main.c. Ceci peut tre grave car certaines de dnitions de Common.h peuvent provoquer des erreurs si elles sont rencontres plusieurs exemplaires dans le mme chier source cest le cas par exemple des dnitions de type. Comment viter ce phnomne de double inclusion ? En protgeant le chier Common.h de la manire suivante :
/**** fichier Common.h ****/ #ifndef _Common_h_ #define _Common_h_ ... /* contenu du fichier Common.h */ ... #endif /* _Common_h_ */
Par le jeu de la compilation conditionnelle, le contenu utile du chier ne sera effectivement inclus que lors de la premire directive #include. Bien entendu, lidenticateur _Common_h_ doit tre choisi de manire unique. On utilise trs souvent, comme ici, un identicateur qui rappelle le nom du chier. Cette technique doit tre utilise systmatiquement sur tous les chiers dentte, moins quune raison premptoire soit donne de ne pas le faire.
c Jean-Paul R IGAULT
138
6. Le prprocesseur ANSI C
fait croire au compilateur que la prochaine ligne source a comme numro la constante (qui est soumise macro-expansion si ncessaire). Cette directive nest en gnral pas utilise directement par le programmeur, mais plutt par les compilateurs et prprocesseurs de toute nature an dassurer la remonte dans le source des messages derreur.
et, en C90, son effet est entirement laiss limplmentation ! En fait lide est de permettre de donner des commentaires qui on un sens pour un compilateur particulier et qui peuvent lui suggrer un comportement spcial. En tout tat de cause, ce comportement spcial ne devrait pas altrer la smantique profonde du programme ! titre dexemple, un environnement peut proposer la vrication optionnelle des dbordements dindices. An que le compilateur gnre le code correspondant, on lui prcise par un #pragma. On peut imaginer, par exemple, ce type de syntaxe :
#pragma CHECK_INDEX_RANGE ON int t[10]; ... t[i] = t[j]; ... #pragma CHECK_INDEX_RANGE OFF
C99
La norme exige quun #pragma non reconnu par un compilateur soit simplement ignor, an de permettre la portabilit des sources. C99 introduit des pragmas pr-dnis qui ont trait la manipulation des nombres rels et complexes (voir 9.2.4).
c Jean-Paul R IGAULT
139
c Jean-Paul R IGAULT
140
6. Le prprocesseur ANSI C
c Jean-Paul R IGAULT
Chapitre 7
Fonctions
dcrit ici la que nous dj largement Onutilise dansenlesdtail en notion de fonctionfonction estavonsdes mcanismes chapitres prcdents. La lun principaux de modularit C ; cest aussi lune des bases de la programmation structure. En ansi C, chaque fonction a un type, dtermin la fois par le nombre et le type de ses arguments et par celui de sa valeur de retour. Comme pour tout objet, ce type doit tre connu avant toute utilisation. Trois contextes diffrents sont associs la notion de fonction : 1. la dclaration dune fonction, qui en dnit le type et qui est mise en uvre en ansi C, grce la notion de prototype ; 2. la dnition dune fonction, qui reprend son type, mais surtout en dnit le corps, cest--dire la liste des instructions excutes chaque appel ; 3. lappel ou linvocation de la fonction.
linvocation cos(2*PI*t) est quivalente une valeur de type double. En ansi C, les types de retour possibles dune fonction sont les suivants : Le type void : la fonction ne retourne pas de valeur dans dautres langages on dirait que cest une procdure ; Tout type scalaire, entier ou rel, y compris les numrations et les pointeurs ; Les structures et les unions. A lexception de void, ce sont en fait les types affectables . Les tableaux ny gurent donc pas : une fonction ne peut donc retourner un tableau tout entier ; en revanche, elle peut retourner un pointeur sur le (premier lment du) tableau.
141
c Jean-Paul R IGAULT
142
7. Fonctions
En C traditionnel comme en ansi C, il ny a quun seul mode de passage dargument : le passage par valeur. En fait on devrait plutt parler de passage par copie de la valeur. Le mcanisme exact est le suivant. Soit f une fonction un argument de type T dont la dnition ressemble :
void f(T t) { ... }
Lexpression tt (largument effectif) est dabord value ; si ncessaire elle est convertie dans le type T 1 ) ; la valeur obtenue sert initialiser une variable locale au corps de la fonction f correspondant largument formel t. Donc si largument formel t est modif dans le corps de la fonction, cette modication reste locale, et na aucune inuence sur la valeur de largument effectif.
Passage des tableaux et des chanes de caractres
Il ny a aucune exception cette rgle de passage par valeur. Cependant, cause de lassimilation des tableaux aux pointeurs (5.3.2), les tableaux paraissent tre passs par adresse comme dans
void raz(int n, int t[]); int tab[100]; raz(100, tab);
Il ny a pas de contradiction : un nom de tableau est une constante de type pointeur, et ce pointeur est pass par valeur. En fait, le prototype de raz peut tout aussi bien scrire :
void raz(int n, int *t);
et lappel
raz(100, &tab[0]);
1. Si cette conversion est impossible, il y a erreur de compilation.
c Jean-Paul R IGAULT
143
Cette mme remarque sapplique galement aux chanes de caractres littrales qui paraissent passes par adresse : en fait ce sont aussi des pointeurs (sur leur premier caractre).
void string_copy(char *dest, const char *orig); char str[100]; string_copy(str, "bonjour");
Rappelons que la fonction doit avoir un moyen de dterminer le nombre dlments du tableau. Cest pourquoi, dans le cas de la fonction raz, ce nombre (100) est pass en argument. Ce nest pas ncessaire pour les chanes de caractres puisque la convention de les terminer par le caractre nul permet den connaitre la longueur.
Passage par valeur des structures et unions
Le passage par valeur sapplique aussi quand une structure ou une union est en argument :
void print_person(struct Person who); struct Person somebody; print_person(somebody);
Ce passage par valeur implique donc une recopie champ par champ de largument effectif somebody dans largument formel who. Si la structure est de grande taille, cela peut tre inefcace. Nous examinerons en 7.3.2 un moyen davoir le mme effet mais plus efcacement.
Si la liste dargument est vide, liste-args doit tre rduite au mot-cl void comme dans
int getchar(void);
La liste est une suite de noms de types spars par des virgules comme dans
double cos(double); int strcmp(const char *, const char *);
Les noms des arguments formels peuvent tre prsents comme dans
char *strcpy(char *dest, const char *orig);
c Jean-Paul R IGAULT
144
7. Fonctions
Ils ne jouent alors pas dautre rle que celui de faciliter la lisibilit en autodocumentant le source ; pour le reste, ils sont ignors. On peut avoir dans un chier source plusieurs prototypes pour la mme fonction, du moment quils sont identiques (aux noms ventuels des arguments formels prs).
Liste variable darguments
On a parfois besoin de fonction pour lesquelles le nombre et le type des arguments est inconnu, totalement ou partiellement. Un cas typique est celui de printf : nous savons que le premier argument, le format, est une chane de caractres, dailleurs constante ; en revanche les autres arguments sont en nombre et types variant chaque appel. En ansi C, son prototype est
int printf(const char *, ...);
Lellipse (...) indique au compilateur de ne faire de vrication ni de type ni de nombre sur les arguments ventuels correspondants. En revanche, il y aura bien ici vrication du type du premier argument. On ne peut lider ainsi quun segment terminal de la liste dargument. Ceci
int f(int, int, ..., double); int g(int, ..., double, ..., int);
est interdit. Pour crire le corps dune fonction dont la liste darguments est lide, il faut pouvoir trouver quelque part une information sur le type des arguments. Dans le cas de printf, ce sont les spcications de format, contenues dans le premier argument, qui jouent ce rle. Une fois ces types connus, les macros standards denvironnement dnies dans le chier <stdarg.h> (voir 9.6.1) permettent de dcomposer ces listes variables dargument.
Compatibilit avec C traditionnel et C++
Pour des raisons de compatibilit arrire, en ansi C, un prototype comme int f(); est quivalent int f(...); et non pas int f(void). Ceci est une cause dincompatibilit avec C++ o int f() est le prototype dune fonction sans argument.
Type de retour implicite dune fonction en C99
C99
La rgle encore valable en C90 selon laquelle une fonction dont le type de retour ntait pas connu tait suppose retourner un entier (int) nest plus vraie en C99. Dans cette dernire version, labsence de dclaration du type de retour est une erreur.
c Jean-Paul R IGAULT
145
Lentte de la fonction qui est une simple reprise du prototype : cette fois-ci cependant, il est utile, voire ncessaire, de donner un nom aux arguments formels si on veut pouvoir les rfrencer dans le corps ; Le corps de la fonction qui est un simple bloc. La dnition dune fonction ne peut pas tre situe dans un bloc. Elle est ncessairement au niveau 0 de la hirarchie de bloc. Il ny a donc pas dimbrication de fonctions, pas de fonction locale en C. Voici quelques exemples de dnition de fonctions ::
void raz(int n, int t[]) { int i; for (i = 0, i < n; i++) t[i] = 0; } void string_copy(char *dest, const char *orig) { while ((*dest++ = *orig++) != \0) {} } /* entete */ /* corps */
La virgule qui spare les arguments formels lors de lappel nest pas loprateur dvaluation squentielle (3.3.7). En fait lordre dvaluation des arguments nest pas dni en ansi C. On devra donc se mer des effets de bord dans les arguments effectifs. Ainsi, ceci int f(int); int Max(int, int); int i, j; j = Max(i++, f(i)); est non portable : on ne sait pas si f est invoqu aprs que i ait t incrment, ou avant.
c Jean-Paul R IGAULT
146
7. Fonctions
C99
cest une sugggestion au compilateur de faire lexapansion en ligne. Notez le mot suggestion : le compilateur est libre de ne pas obir, car il sait, en gnral mieux que le programmeur, valuer le compromis entre le gain en temps dexcution et laugmentation de la taille mmoire du code.
Combinaison de inline et static
Une fonction inline devrait toujours tre dclare static. En outre si cette fonction doit tre utilise par plusieurs units de compilation, elle doit tre dans un chier dentte (.h) inclus dans ces units. Voir 8.4.2 pour plus de dtails.
Linterprtation dune telle dclaration utilise la rgle introduite en 5.3.1. Le prototype signie simplement que, dans une expression,
*insert(lc, i);
est une List_Cell ; compte tenu de la prcdence des oprateurs (3.4.1), insert(lc, i) est donc un pointeur sur List_Cell et nalement insert est une fonction qui retourne un pointeur sur List_Cell et qui a deux arguments, un pointeur sur List_Cell et un int.
Attention : pointeurs en lair
Lorsquune fonction retourne un pointeur, lobjet point doit avoir une dure de vie suprieure celle du corps de la fonction, sinon on court la catastrophe. Ainsi dans int *f(void) { int tmp = 1; ... return &tmp; }
c Jean-Paul R IGAULT
147
la sanction sera svre : lobjet local tmp nexiste plus aprs le retour de la fonction ! La plupart des compilateurs donneront un warning dans des cas aussi simples, mais ce nest pas toujours possible en gnral.
On veut raliser une fonction qui change deux entiers. Les entiers ne peuvent pas tre passs par valeur car leur changement de valeur dans le corps de la fonction serait sans effet sur les arguments effectifs :
void bad_swap(int i, int j) { int temp = i; // les variables locales sont bien echangees... i = j; j = temp; // mais pas les parametres effectifs ! }
et lors de lappel, on doit faire attention prendre les adresses des objets changer :
int i, j; swap(&i, &j);
Si cest un pointeur qui doit tre modi, il faut utiliser un double pointeur comme dans la fonction alloc_mem suivante 2 :
void alloc_mem(size_t sz, void **ptr) { if ((*ptr = malloc(sz)) == NULL) { fprinft{stderr, "Alloc_Mem: memoire saturee\n"); exit(1); } }
2. Stylistiquement, la fonction alloc_mem serait plus lgante si son prototype tait void *alloc_mem(size_t), mais cest juste pour lexemple.
c Jean-Paul R IGAULT
148
7. Fonctions
mais cela risque dentrainer une lourde copie de mmoire mmoire. Si lon passe brutalement un pointeur, comme dans
void print_person(struct Person *);
on vite ces copies, mais en contre-partie largument effectif pourra tre modi dans la fonction, ce qui rduit la scurit de programmation. La bonne solution est videmment de passer un pointeur sur une constante :
void print_person(const struct Person *);
ce qui empche la modication de lobjet point dans le corps de la fonction. Noter que le compilateur effectue dans le cadre des appels de fonctions les mmes vrications que celles voques en 5.1.5. Ainsi, dans
void f(struct Person *); void print_person(const struct Person *somebody) { ... f(somebody); ... }
lappel de f provoque une erreur : en effet largument de f nest pas un pointeur sur une constante, mais sur une variable, et donc laccs lobjet point par somebody serait largi dans le corps de f. Chaque fois que lon passe un pointeur en argument, on doit se demander si lobjet point pourra ou non tre modi dans le corps de la fonction. Si la rponse est non, ne pas hsiter passer un pointeur sur une constante. Cest plus quune clause de style : cela renforce la scurit de la programmation et cest un indice de qualit.
Supposons que lon souhaite passer une fonction en argument une autre fonction. Par exemple, on a crit un programme dintgration numrique qui calcule
b a
f ( x )dx
pour toute fonction f (raisonnable !) argument et rsultat rels. La fonction dintgration, disons integr va donc avoir trois arguments, deux rels et la fonction. En C on passe en fait un pointeur sur fonction :
double integr(double a, double b, double (*pf)(double));
c Jean-Paul R IGAULT
149
double (*pf)(double)
est un rel double ; donc *pf est une fonction un argument double qui retourne un double ; et nalement, pf est bien un pointeur sur une telle fonction.
Utilisation dun pointeur sur fonction
Tout dabord, un nom de fonction rencontr seul cest--dire sans liste darguments associe est de type pointeur sur fonction ; ce pointeur est dailleurs constant puisque la fonction a une adresse qui est choisie par le compilateur et lditeur de liens et qui nest pas modiable. Ainsi, notre fonction integr peut tre invoque correctement par
double cos(double); r = integr(0.0, PI, cos);
cos x dx
Dans le corps de la fonction integr, il est possible dinvoquer la fonction pointe sous la forme complte
(*pf)(x)
Un pointeur sur fonction est un type (scalaire) comme un autre et, comme tel, peut tre soumis nimporte quelle construction de type. On peut en dnir des tableaux, il peut tre le type de retour dune fonction, il peut tre un champ de structure ou dunion. Le programme 7.1 illustre quelques unes de ces possibilits. Dans ce programme, on trouve quelques types intressants 3 : funct_tab est un tableau de (3) pointeurs sur fonction argument double et rsultat double ; funct_names est un tableau de (3) structures ; le premier champ de cette structure est une chane de caractres (banal !), et le second un double pointeur sur fonction ( argument double et rsultat double) ; La fonction select na pas dargument et retourne un pointeur sur fonction ( argument double et rsultat double) choisie au hasard ; La fonction main choisit une fonction excuter grce la fonction select ; elle applique cette fonction la valeur , puis cherche dans la table funct_names le nom de la fonction qui a t choisie en comparant la valeur du pointeur pcurrf au contenu du champ ppf.
3. On peut essayer de rcrire ce programme avec des typedef pour le rendre plus lisible.
c Jean-Paul R IGAULT
150
7. Fonctions
Programme 7.1 Les pointeurs sur fonction dans tous leurs tats
/***** Fichier: ptr_fonct.c *****/ #include <stdlib.h> #include <stdio.h> #include <math.h> double (*funct_tab[])(double) = {sin, cos, tan}; struct Funct_Name { const char *name; double (**ppf)(double); /* double pointeur sur fonction */ } funct_names[] = { {"sinus", &funct_tab[0]}, {"cosinus", &funct_tab[1]}, {"tangente", &funct_tab[2]}, }; const int N_FUNCT_NAMES = sizeof(funct_names)/sizeof(struct Funct_Name); double (*select(void))(double) { extern long int random(void);
25
10
15
20
30
int main() { double (*pcurrf)(double); double res; int i; while (1) { pcurrf = select(); res = pcurrf(PI); for (i = 0; i < N_FUNCT_NAMES; i++) { if (*funct_names[i].ppf == pcurrf) { printf("Fonction: %s Resultat: %g\n", funct_names[i].name, res); break; } } } }
35
40
45
50
c Jean-Paul R IGAULT
151
dans main termine le programme en transmettant le code de retour c au processus pre (par exemple le shell ; elle est quivalente exit(c). On peut dnir main avec de 0 3 arguments. La totale sous Unix est
main(int argc, char *argv[], char *envp[])
o
argc argv envp le nombre darguments positionels un tableau de pointeurs sur caractre de dimension argc un tableau de chanes de caractres qui se termine au premier i tel que envp[i] == NULL
Les chanes de caractres argv[0], argv[1], ..., argv[argc - 1] correspondent aux arguments de la ligne de commande la manire des arguments positionnels $0, $1, ... du shell. En particulier, argc vaut toujours au moins 1, et argv[0] est le nom de la commande. Il est garanti que argv[argc] est gal au pointeur NULL. Le troisime argument est rarement utilis ; cest aussi un tableau de chanes de caractres dont chacune est analogue chane reprsentant une initialisation de variable denvironnement du shell, par exemple
"PATH=/usr/ucb:/bin:/usr/bin"
Ceci permet de passer main un environnement particulier. A titre dexemple, le programme 7.2 ralise la commande echo du shell avec quelques modications. On rafche les arguments que lon a reu. La commande accepte trois options :
4. au moins du point de vue du programmeur
c Jean-Paul R IGAULT
152
7. Fonctions
-n -l -u
pas de n de ligne les majuscules sont transformes en minuscules les minuscules sont transformes en majuscules
Les fonctions de transformation en majuscules et minuscules utilisent les fonctions de bibliothque standard toupper et tolower dont le prototype est dans le chier dentte <ctype.h> (voir 9.4.1). Programme 7.2: La commande echo modie
/***** Fichier: newecho.c *****/ #include <stdio.h> #include <ctype.h>
5
15
void to_upper(char str[]) { int i; for (i = 0; str[i] != \0; i++) str[i] = toupper(str[i]); } void to_lower(char str[]) { int i; for (i = 0; str[i] != \0; i++) str[i] = tolower(str[i]); } int main(int argc, char *argv[]) { int nopt; int narg; for (nopt = 1; nopt < argc && argv[nopt][0] == -; nopt++) { switch (argv[nopt][1]) { case n: no_new_line++; break; case u: upper_case++; break; case l: lower_case++; break; default: fprintf(stderr, "%s: option inconnue: %s\n", argv[0], argv[nopt]); break; } }
20
25
30
35
40
45
c Jean-Paul R IGAULT
153
50
55
for (narg = nopt; narg < argc; narg++) { if (upper_case) to_upper(argv[narg]); else if (lower_case) to_lower(argv[narg]); printf("%s ", argv[narg]); } if (!no_new_line) putchar(\n); return 0; }
une fonction dintgration par la mthode des trapzes invocable de la manire suivante :
o f est une fonction argument double et rsultat double et o s, a, b et delta_x sont des double. Cette fonction doit retourner une approximation de
b a
f ( x ) dx
c Jean-Paul R IGAULT
154
7. Fonctions
c Jean-Paul R IGAULT
Chapitre 8
155
c Jean-Paul R IGAULT
156
/* E1 */ /* E3 */ /* E4 */ /* E1 */ /* E3 */ /* E4 */ /* E4 */
typedef struct st { int st; } st; struct var { double st; double var; }; enum e { A, B } x; struct A { int A; }; int main() { goto var;
10
15
/* E3 */ /* E1 */ /* E1 */ /* E1 */ /* E3 */ /* E4 */
20
25
/* E1 */ /* E2 */ /* /* /* /* /* E2 E2 E2 E2 E2 */ */ */ */ */
30
35
c Jean-Paul R IGAULT
157
ment ou indirectement (voir la directive #include en 6.2). La gure 8.1 schmatise ce concept. Chaque unit (il y en a trois dans la gure 8.1) est compile sparment par une excution du compilateur : ceci comporte lexcution du prprocesseur (6), lanalyse lexicale et syntaxique, la vrication des types et la gnration de
c Jean-Paul R IGAULT
158
code. Chaque unit de compilation produit en rsultat un chier-objet binaire (extension .o). Toutes les excutions sont indpendantes et en consquence chaque unit doit comporter toute linformation ncessaire au compilateur pour pouvoir effectuer sa tche. Puis les chiers-objets obtenus sont envoys lditeur de liens qui les regroupe en un chier binaire excutable unique en les combinant avec bibliothques ncessaires.
c Jean-Paul R IGAULT
159
/* Objet global */ /* Definition de fonction */ /* Masque le g global */ /* Objet local */ /* Le s local */ /* Bloc imbrique */ /* Masque le s precedent */
10
15
/* On retrouve le premier s */
Objet locaux
Les objets locaux se subdivisent en deux catgories : ceux qui sont statiques, cest--dire dure de vie permanente, et dont la dclaration (locale) est prxe par le mot-cl static ; on parle parfois dobjets rmanents ; ceux qui sont automatiques, cest--dire dure de vie de bloc ; aussi leur dclaration a la forme simple habituelle, ventuellement prcde du mot-cl auto ou du mot-cl register. Lextrait suivant illustre ces deux types dobjets locaux :
int f(int i)
3. En fait trois, car on a aussi la visibilit de fonction applicable aux tiquettes (voir 8.3.6) 4. Ce terme de visibilit de chier est passablement impropre : il vaudrait mieux parler de visibilit dunit de compilation.
c Jean-Paul R IGAULT
160
{ int a; static int n; register int r; auto int b; ... } /* /* /* /* Objet Objet Objet Objet local local local local automatique */ statique (remanent) */ automatique */ automatique */
Un objet rmanent (statique local) comme n conserve sa valeur dune entre du bloc lautre. Ainsi la fonction suivante conserve dans une variable locale rmanente le nombre de fois o elle a t appele :
int f(int i) { static int nb_calls = 0; nb_calls++; ... }
Modieurs de type register et auto
Le modieur de type register suggre au compilateur de placer la variable correspondante dans un registre machine (quoi que cela signie...). Il ne peut sappliquer qu une variable automatique. Le compilateur est libre dignorer cette suggestion qui ne devrait dailleurs pas faire partie du langage et qui est battue en brche par la plupart des optimiseurs de code modernes. Sauf cas exceptionnel, on ignorera donc register. Quant au mot-cl auto, il ne sert absolument rien ! Mais, comme ces deux mots sont rservs, vous ne pouvez pas les utiliser pour autre chose (par exemple, nom de variable, de fonction, de type...).
Objets globaux
Les objets globaux tombent eux-mmes dans deux catgories : ceux dont la visibilit est uniquement limite au chier (en fait lunit de compilation) o ils sont dnis ; on parle alors dobjets privs (au module) ; ceux qui sont potentiellement visibles dans tout le programme ; on parle alors dobjets externes. Les objets privs sont des objets globaux dont la dnition est prcde du mot-cl static comme dans
static int g = 3; void f() { ... }
Des objets privs de mme nom, mais dans des chiers source diffrents nentrent pas en conit. Comme les objets externes sont potentiellement visibles dans tout le programme, cause de la compilation spare, chaque chier source doit en connaitre le type ; ceci est ralis par une dclaration dexterne comme par exemple :
c Jean-Paul R IGAULT
161
Il sagit bien ici dune dclaration et non pas dune dnition. Il peut y avoir de nombreuses dclarations de cette forme, mais il ne peut y avoir, dans tous les chiers source dun programme, quune seule dnition et seule cette dnition pourra effectuer linitialisation ; elle aura la forme suivante, dj rencontre :
char Days[7][10] = {...};
Noter que lobjet s nest ici initialis quune seule fois, au dbut du programme. Lexpression dinitialisation doit donc tre statique, cest--dire valuable la compilation. En revanche, pour un objet automatique comme j, linitialisation est excute chaque entre dans le bloc et peut donc utiliser une expression valuable lexcution.
Visibilit des objets allocation dynamique
Les objets allocation dynamique explicite sont manipuls travers des pointeurs. Leur rgles de visibilit sont donc celles de ces pointeurs.
c Jean-Paul R IGAULT
162
Il peut donc exister plusieurs fonctions de mme nom dans le programme, du moment quelles sont toutes dclares static et dnies dans des units de compilation diffrentes.
La gure 8.2 illustre quelques unes des situations possibles. La variable v est externe. Elle est dnie (et initialise 2) la ligne 1 de fic1.c. Elle est visible globalement dans tout ce chier. Comme elle fait lobjet dune dclaration dexterne la ligne 1 de fic2.c, elle est galement visible globalement dans tout ce dernier chier. La variable vv est galement externe. Elle est dnie la ligne 2 de fic1.c. Elle est donc visible globalement partir de cette ligne et dans tout ce chier. Nayant pas dinitialisation explicite, elle est initialise 0, comme tout objet allocation statique. Comme elle fait lobjet dune dclaration dexterne locale la fonction f3 de fic2.c (ligne 7), elle est visible dans cette fonction. Et elle est galement visible partir de sa dclaration dexterne globale (ligne 18) du mme chier, jusqu la n. En revanche elle nest pas visible lintrieur de la fonction f4. Les paramtres x, y de f1 (ligne 8 de fic1.c), x de f2 (ligne 17 de fic1.c), x de f3 (ligne 3 de fic2.c) ainsi que les variables z (ligne 11 de fic1.c) et i (ligne 19 de fic1.c) sont des variables locales automatiques. Leur porte est le bloc de la fonction dans laquelle elles sont dnies. Ces variables automatiques ne sont pas initialises par dfaut. La variable v de f2 (ligne 20 de fic1.c) est locale automatique. Sa porte est le bloc de f2. lintrieur de cette fonction elle masque la variable globale externe v. Les deux variable nommes z sont toutes les deux globales statiques. Comme elles sont dnies dans deux units de compilation diffrentes (ligne 15 de fic1.c et 17 de fic2.c), elles dsignent des objets distincts, de types dailleurs galement diffrents. Nayant pas dinitialisation explicite, elles sont initialiss 0. Elles sont visibles de leur point de dnition jusqu la n du chier o elles sont dnies. Elles ne peuvent pas tre lobjet dune dclaration dexterne. Les fonctions f1, f2 et g sont externes. Elles sont visibles de leur point de
c Jean-Paul R IGAULT
163
Figure 8.2 Visibilit et dure de vie : quelques exemples Programme 8.4 Visibilit et dure de vie : fic2.c
extern int v; static int { double static extern /* ... } f3(int x) cos(double); int w; int vv; */
static int z[10]; extern int vv; extern int f1(int, int); double g(void) { /* ... */ }
dnition jusqu la n du chier o elles sont dnies. En outre f1 est galement visible partir de la ligne 19 du chier fic2.c. La fonction g a aussi une dclaration dexterne (ligne 10 de fic1.c) qui la rend visible dans le bloc de la fonction f1. Enn les fonctions f3 et f4 de fic2.c (lignes 3 et 11) sont statiques. Elles ne sont donc visibles qu partir de leur point de dnition jusqu la n du chier courant. Elles ne peuvent pas tre lobjet dune dclaration dexterne.
c Jean-Paul R IGAULT
164
/* Global */
10
/* Local */
15
/* Local */
20
25
/* Local */
30
/* Local */
c Jean-Paul R IGAULT
165
En C, il est possible avec une certaine discipline de raliser ce type de programmation modulaire. Linterface est constitue par un chier dentte comportant les dnitions de type utilises, les dclarations dobjets externes et les prototypes des fonctions externes de manipulation du module. Ce chier sera inclus par tous les chiers-sources qui veulent utiliser le module. La gure 8.3 schmatise cet organisation. Elle dcrit un module de nom mod. Son interface est un chier dentte mod.h et son implmentation une unit de compilation mod.c. Bien entendu, linterface elle-mme peut avoir besoin de dclarations ou dnitions emprunts dautres modules, elle peut donc en inclure les chiers dentte (.h) correspondants. De mme, limplmentation mod.c peut avoir besoin directement dautres ressources qui ne sont pas dans mod.h ni dans les chiers que ce dernier inclus. Enn le code client (client.c) qui utilise le module mod ne doit avoir inclure que linterface de ce module (mod.h). Bien entendu, ldition de liens, les deux chiers objets client.o et mod.o doivent tre fournis ainsi que ceux correspondant aux autres modules quils utilisent. Lart du concepteur de modules est dtablir les dpendances minimales entre ces diffrents composants. Le code client dpend de tout qui est dans linterface (directement ou inclus). Toute modication directe ou indirecte de cette interface risque donc dinduire des modications et en tout tat de cause une recompilation du code client. Il faut donc ne mettre dans linterface que ce qui est ncessaire et sufsant au code client pour utiliser le module.
c Jean-Paul R IGAULT
166
chier dinterface peut contenir des dnition de constantes et variables globales ainsi que des dnitions de fonctions du moment que ces objets sont privs (static) lunit de compilation. Tout se passera comme si chaque unit de compilation avait sa propre copie des objets en question, mais toutes ces copies seront de mme type. En particulier, les fonctions inline (7.2.4) doivent toujours tre dclare aussi statiques et tre dnies dans un chier dentte .h inclut par toutes les units de compilation qui souhaitent en partager la dnition
#ifndef _Utilitaires_h_ #define _Utilitaires_h_ /***** Fichier: utilitaires.h *****/ /* * Quelques fonctions inline dusage general et * destinees a etre utilisees dans de nombreux modules. */ static inline int max(int a, int b) { return a > b ? a : b; } static inline int min(int a, int b) { return a < b ? a : b; } static inline int abs(int a) { return a < 0 : -a : a; } #endif
Enn, noter que comme tout chier .h, mais encore plus que pour les autres un chier dinterface doit tre protg en inclusion unique (6.4.2).
c Jean-Paul R IGAULT
167
10
void stack_push(int elem); int stack_pop(void); int stack_is_full(void); int stack_is_empty(void); #endif
/* /* /* /*
15
% Stack Taille de Entrez un Entrez un Entrez un Entrez un Entrez un Entrez un Entrez un Entrez un Entrez un Entrez un Resultat: %
la pile? 10 nombre: 1 nombre: 2 nombre: 3 nombre: 4 nombre: 5 nombre: 6 nombre: 7 nombre: 8 nombre: 9 nombre: 10 10 9 8 7 6 5 4 3 2 1
Ecrire un module fifo (cest--dire une le premier entr, premier sorti ) la manire du module pile dentiers .
FIFO )
c Jean-Paul R IGAULT
168
#include <stdio.h> #include <stdlib.h> #include "Stack.h" static int *the_stack; /* La pile elle-meme */ static int stack_top = 0; /* Indice du sommet */ static int stack_size; /* Taille amximale */ static void error(const char *msg) { fprintf(stderr, "Stack: ERREUR: %s\n", msg); exit(1); } void stack_init(int size) { if (size < 0) error("taille"); if ((the_stack = malloc(size*sizeof(int))) == NULL) error("malloc"); stack_size = size; } void stack_push(int elem) { if (!stack_is_full()) the_stack[stack_top++] = elem; else error("pile pleine"); } int stack_pop(void) { if (!stack_is_empty()) return the_stack[--stack_top]; else error("pile vide"); /*NOTREACHED*/ return 0; // pour eviter un warning... } int stack_is_full(void) { return stack_top == stack_size; } int stack_is_empty(void) { return stack_top == 0; }
10
15
20
25
30
35
40
45
50
c Jean-Paul R IGAULT
169
#include <stdio.h> #include "Stack.h" int main() { int n = 10; printf("Taille de la pile = %d\n", n); stack_init(n);
10
15
for (int i = 0; !stack_is_full(); ++i) stack_push(i); printf("Resultat: "); while (!stack_is_empty()) printf("%d ", stack_pop()); putchar(\n); return 0; }
20
c Jean-Paul R IGAULT
170
c Jean-Paul R IGAULT
Chapitre 9
La bibliothque standard
n ne ici donner description complte toutes Opagesprtend pas(commande une deou aPoursite enillignedecelui deles fonctions de la bibliothque standard C. cela, vaut mieux se reporter aux du manuel ) un : Wikipeman
dia est un bon point de dpart (cherchez "c standard library manual"). Le livre de Prata [3] contient galement une description trs complte de la bibliothque standard.
171
c Jean-Paul R IGAULT
172
9. La bibliothque standard
C99 dnit le mot rserv _Bool qui est une numration avec deux valeurs 0 et 1. Si, en plus, vous incluez le chier <stdbool.h>, alors _Bool est rendu synonyme de bool et vous gagnez aussi le deux nouvelles macros true et false. Il est fortement conseill de le faire. Outre une meilleure lisibilit, cela assure la compatibilit avec C++.
Ce chier dnit un ensemble de macros pour faciliter la conversion entre les types entiers nombre de bits garantis de <stdint.h>.
Ce chier dnit un certain nombre de types et de fonctions permettant de manipuler des exceptions lors des calculs en nombres ottants. Le pragma
#pragma STD FENV_ACCESS on
active le mcanisme dexception (et le mme pragma avec off le dsactive). Les exceptions en question capturent des erreurs de calcul comme le dpassement de capacit, la division par zro, les valeurs illgales, etc. On peut aussi contrler la manire dont seffectuent les arrondis. Tout ceci est rserv aux spcialistes du calcul scientique...
c Jean-Paul R IGAULT
173
c Jean-Paul R IGAULT
174
9. La bibliothque standard
Ce chier dclare aussi des fonctions de manipulation brutales de zones de mmoire comme la copie (memcpy), la comparaison (memcmp), etc. Voici par exemple une manire trs efcace (beaucoup plus efcace quune boucle lment par lment) de copier un tableau de double dans unn autre (de mme taille) :
double t1[100]; double t2[100]; // initialisation des elements de t2 memcpy(t1, t2, 100 * sizeof(1)); // copie t2 dans t1
Ce chier est lquivalent de <ctype.h> mais pour les caractres encods sur plus dun octet (type wchar_t), par exemple Unicode.
Ce chier est lquivalent de <string.h> mais pour les caractres encods sur plus dun octet (type wchar_t), par exemple Unicode. Il contient galement des opration de conversions entre les diffrents encodages ainsi que lquivalent des oprations dE/S de <stdio.h>) pour les caractres wchar_t.
9.6 Divers
c Jean-Paul R IGAULT
9.6. Divers
175
Lisez donc la page de manuel (man setjmp) pour comprendre ce que fait ce programme... Inutile de dire quil sagit l dun horreur absolue par rapport un vrai mcanisme dexception comme en Java ou C++.!
c Jean-Paul R IGAULT
176
9. La bibliothque standard
c Jean-Paul R IGAULT
Chapitre 10
Environnement de dveloppement
Pour dvelopper du logiciel avec un langage on a besoin doutils pour supporter les diffrentes activits : un compilateur du langage, capable de produire du code pour le processeur et le systme dexploitation vis, un diteur de texte, de prfrence connaissant la syntaxe du langage et capable de la faire ressortir par le jeu des couleurs et/ou des polices, un gestionnaire de conguration permettant denchaner harmonieusement et efcacement les diffrentes tapes de la production de code, des outils de mise au point ( debuggers ), des outils de gestion et de gnration de documentation.
10.1 Compilateurs
Quils soient libres ou propritaires, il existe un grand nombre de compilateurs pour le langage C. Wikipedia 1 en liste la plupart. Il est noter que le clbre Visual C++ de Microsoft 2 nest pas, dans ses versions rcentes tout au moins, un compilateur C99. La suite gcc 3 , le gnu c compiler, est universellement rpandue. Elle possde un certain nombre davantages : elle est libre, sous licence gpl 4 ; il sagit non pas dun simple compilateur dun langage donn, mais dun multi-compilateurs, supportant plusieurs langages diffrents : C et C++, Objective C et C++, Ada, Fortran, Java... la suite gcc volue de manire trs efcace et ractive ce qui lui permet de suivre de trs prs, voire danticiper, le travail de normalisation de ces diffrents langages ; elle produit du code pour virtuellement tout processeur et la plupart des systmes disponibles sur le march ; sa qualit globale nest plus dmontrer (mme si certains compilateurs propritaires peuvent tre suprieurs sur certains points particuliers : e.g., la suite de compilateurs Intel et larchitecture multi-curs) ;
1. 2. 3. 4. http://en.wikipedia.org/wiki/List_of_compilers http://msdn.microsoft.com/en-us/library/60k1461a(VS.80).aspx http://gcc.gnu.org/ http://www.gnu.org/licenses/gpl.html
177
c Jean-Paul R IGAULT
178
cest la suite favorite du monde du logiciel libre, disponible par dfaut sur tout les Linux et autres UnixBSD ; cest le compilateur par dfaut qui est derrire la plupart des environnements intgrs de dveloppement (IDEs) multi-plateformes actuellement disponibles : Eclipse, Code::Blocks, CodeLite, etc.
c Jean-Paul R IGAULT
179
Pendant longtemps, les diteurs de type Emacs ont eu la rputation dtre complexes utiliser et de ncessiter lapprentissage dun grand nombre de raccourcis clavier grand renfort de touches control ou escape. Ce ntait pas faux mais il faut reconnaitre que, de nos jours, mme si la maitrise totale de toutes les fonctionnalits dEmacs reste videmment difcile, grce la souris, aux touches spciales du claviers, aux boutons et aux menus, lutilisation quotidienne en est devenue trs aise.
c Jean-Paul R IGAULT
180
par dfaut. Par exemple, si vous programme tient dans un seul chier source, disons prog.c et que vous avez correctement dnit les variables denvironnement CC et CFLAGS, vous navez mme pas besoin de makefile ; tapez simplement
% make prog gcc -g -Wall -std=c99 -o prog prog.c %
Par ailleurs, si vous ntes pas sr de vos dpendances entre chiers, le compilateur gcc vous aidera : utilisez loption -MM sur la liste des units de compilation (chiers *.c) et il vous fournira ces dpendances sous une forme telle que vous naurez plus qu la couper-coller dans votre makefile :
% gcc -MM *.c random_list.o: random_list.c simple_list.o: simple_list.c simple_list.h simple_list_double_ptr.o: simple_list_double_ptr.c simple_list_main.o: simple_list_main.c simple_list.h %
c Jean-Paul R IGAULT
181
Pour utilisr valgrind, aucune option de compilation ou ddition de liens nest ncessaire. Vous invoquez juste lutilitaire avec le nom de votre excutable :
% valgrind simple_list ==4576== Memcheck, a memory error detector. ==4576== Copyright (C) 2002-2008, and GNU GPLd, by Julian Seward et al. ==4576== Using LibVEX rev 1884, a library for dynamic binary translation. ==4576== Copyright (C) 2004-2008, and GNU GPLd, by OpenWorks LLP. ==4576== Using valgrind-3.4.1, a dynamic binary instrumentation framework. ==4576== Copyright (C) 2000-2008, and GNU GPLd, by Julian Seward et al. ==4576== For more details, rerun with: -v ==4576== Entrez une suite dentiers terminees par EOF 1 3 2 Liste triee: 1 2 3 ==4576== ==4576== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 2) ==4576== malloc/free: in use at exit: 2,156 bytes in 3 blocks. ==4576== malloc/free: 4 allocs, 1 frees, 2,172 bytes allocated. ==4576== For counts of detected errors, rerun with: -v
c Jean-Paul R IGAULT
182
searching for pointers to 3 not-freed blocks. checked 71,744 bytes. LEAK SUMMARY: definitely lost: 32 bytes in 2 blocks. possibly lost: 0 bytes in 0 blocks. still reachable: 2,124 bytes in 1 blocks. suppressed: 0 bytes in 0 blocks. Rerun with --leak-check=full to see details of leaked memory.
Pendant lexcution qui se droule normalement (lignes marque en bleue 5 ), les fonctions de la famille malloc sont instrumentes. la n un rapport est mis. On constate un dsquilibre entre le nombre dallocation (4) et de dallocation (1) ; on nous indique aussi que 2 blocs totalisant 32 octets sont non rcuprables. Rien de dramatique ici puisque le programme se termine et que donc lensemble de son espace mmoire est retourn au systme. Mais dans des cas plus complexes, cela pourrait tre lindication dune catastrophe venir... Sachez que valgrind possde dautres options et bien dautres possibilits. Nhsitez pas lutiliser et apprenez interprter ses rapports.
c Jean-Paul R IGAULT
183
c Jean-Paul R IGAULT
184
c Jean-Paul R IGAULT
185
plates-formes, si il ntait pas pollu (parfois insensiblement) par les extensions propres Microsoft, si il respectait les normes internationales, comme C99... La gure 10.7 montre Visual Studio en mode dition et compilation et la gure 10.8 en mode de mise au point.
c Jean-Paul R IGAULT
186
c Jean-Paul R IGAULT
187
c Jean-Paul R IGAULT
188
c Jean-Paul R IGAULT
Chapitre 11
n mentionne ici quelques extensions propres C99 et dont la plupart nont pas eu tre utilises dans le reste du texte. Cette section est complter.
11.1 Prprocesseur
11.1.1 Macros prdnies 11.1.2 Macros nombre variable darguments
189
c Jean-Paul R IGAULT
190
c Jean-Paul R IGAULT
Chapitre 12
e chapitre liste brivement les diffrences entre C dit traditionnel , C90, C99 et C++. Ce chapitre est complter.
C99
inline functions variable declaration no longer restricted to le scope or the start of a compound statement several new data types, including long long int, optional extended integer types, an explicit boolean data type, and a complex type to represent complex numbers variable-length arrays support for one-line comments beginning with //, as in BCPL or C++ new library functions, such as snprintf new header les, such as stdbool.h and inttypes.h type-generic math functions (tgmath.h) improved support for IEEE oating point designated initializers compound literals support for variadic macros (macros of variable arity) restrict qualication to allow more aggressive code optimization
ISO
C99 C++
191
c Jean-Paul R IGAULT
192
c Jean-Paul R IGAULT
Bibliographie
[1] von Hagen, W. The Denite Guide to GCC, 2e dition. Apress, 2006. [2] ansi Committee x3j11. Rationale for the ansi C Programming language. Silicon Press, 1990. [3] Prata, S. C Primer Plus, 5e dition. Sams Publishing, 2005. [4] Prata, S. C++ Primer Plus, 4e dition. Sams Publishing, 2001. [5] Flipo, D. Documentation sur le module frenchb de babel. Site Web, http://daniel.flipo.free.fr/frenchb/frenchb2-doc.pdf, 2007. [6] Joseph, N. Divers articles sur C http://nicolasj.developpez.com/. et C++. Site Web,
[7] Braams, J. Babel, a multilingual package for use A with L TEXs standard document classes. Site Web, http://css.ait.iastate.edu/Tex/Sp/babel.pdf, 2005. [8] Kochan, S. G. Programming in C, 3e dition. Sams Publishing, 2005. [9] Tribble, D. Incompatibilities between iso C and iso C++. Site Web, http://david.tribble.com/text/cdiffs.htm.
A [10] Lamport, L. LTEX, a Document Preparation System. Addison-Wesley, 1986. [11] Lippman, S. B., Lajoie, J. C++ Primer, 3e dition. Addison Wesley, 1998.
[12] Delannoy, C. Langage C, 4e dition. Eyrolles, 1999. [13] Dijkstra, E. W. Go to statement considered harmful. Communications of the ACM 11, 3 (March 1968), 147148. Version annote par David Tribble en http://david.tribble.com/text/goto.html. [14] Institut fr Theoretische Informatik Darmstadt. xindy, a exible indexing system. http://xindy.sourceforge.net/. at THSite Web,
[15] Valgrind Developpers. Valgrind. Site Web, http://valgrind.org/. [16] Kernighan, B. W., Ritchie, D. M. The C Programming Language, 1re dition. Software Series. Prentice Hall, 1978. [17] Kernighan, B. W., Ritchie, D. M. The C Programming Language (ansi C edition, 2e dition. Software Series. Prentice Hall, 1988. [18] Kernighan, B. W., Ritchie, D. M. Le langage C : norme ansi, 2e dition. Sciences Sup. Dunod, 2004. [19] gnu Project. Gdb: The gnu project debugger. http://sourceware.org/gdb/. Site Web,
193
c Jean-Paul R IGAULT
194
BIBLIOGRAPHIE
debugger.
[21] gnu Project. Gprof: The Gnu proler. http://www.gnu.org/software/binutils/. [22] gnu Project. Status of C99 features in GCC. http://gcc.gnu.org/c99status.html. [23] gnu Project. Programmation http://gcc.gnu.org/. et langages.
[24] Stroustrup, B. The C++ Programming Language, 3e dition. AddisonWesley, 1991. [25] Stroustrup, B. Programming: Principles and Practice Using C++. AddisonWesley, 2009. [26] Mittelbach, F., Goossens, M., Braams, J., Carlisle, D., Rowley, C. The A LTEX Companion, 2e dition. Addison Wesley, 2004. [27] Braquelaire, A. Mthodologie de la programmation en C : norme C 99 api Posix, 4e dition. Dunod, 2005. [28] Forum interactif. Programmation et http://www.developpez.net/forums/. langages. Site Web,
[29] Multiples auteurs. Diverses ressources sur la programmation. Site Web, http://www.developpez.com/.
c Jean-Paul R IGAULT
Glossaire
tente ici de donner dun certain nombre de termes et Ondetraductiongnrauxla (une) dnitionde programmationtreauignore par concepts lis aux langages et processus de leur en langage-machine. Elle peu sans dommage linformaticien expriment. affectation Lune des instructions 1 fondamentales de la programmation procdurale. Laffectation permet de modier la valeur dun objet (qui doit tre variable bien sr). Voir aussi rfrence et valeur. agrgat Un type dont les objets sont composs dautres objets. En C, il y a trois sortes dagrgats: les tableaux, les structures et les unions. analyse lexicale La premire phase de la compilation, o lon reconnait les lments lexicaux (token), cest--dire les mots dun langage de programmation (identicateurs, oprateurs, constantes...). analyse statique (ou de smantique statique) Elle est effectue par le compilateur aprs lanalyse syntaxique. On y vrie en particulier ce qui relve du typage (voir type). analyse syntaxique La deuxime phase de la compilation o lon recherche la structure des phrases cest--dire celle des instructions et dclarations. archive Voir bibliothque. argument formel et effectif dune fonction Largument effectif dune fonction est celui fourni lappel (linvocation) de la fonction; sa valeur sert initialiser largument formel correspondant, qui est un objet local la fonction. En cas de typage fort le type de largument formel et celui de largument effectif doivent tre identiques ou alors il doit exister une conversion implicite du type de largument effectif en celui de largument formel. En C, la correspondance entre arguments effectifs et formels seffectue de manire positionnelle (cest--dire selon lordre des arguments dnis dans le prototype ou lentte de la fonction). assembleur Forme symbolique du langage-machine. Les compilateurs C produisent souvent de lassembleur plutt que du langage-machine directement.
1. En fait en C, laffectation est une expression et non une instruction (voir 3.3.4).
195
c Jean-Paul R IGAULT
196
GLOSSAIRE
On dsigne aussi par assembleur le programme qui traduit ce langage lui-mme en binaire objet. bibliothque Un ensemble de chiers-objets regroups en un chier unique (library ou archive en anglais) utilisable lors de ldition de liens. Tout environnement C comporte plusieurs bibliothques dont le contenu est prdni: la bibliothque standard, la bibliothque mathmatique, etc... (voir le chapitre 9). binaire excutable Le rsultat de ldition de liens. Sous UNIX le chier binaire excutable porte par dfaut le nom a.out. Il contient le code (les instructions) du programme et la valeur initiale des donnes. Si ldition de liens sest correctement passe, ce chier est effectivement excutable, cest--dire invoquable directement depuis le shell. binaire objet Voir chier-objet. compilation Le processus de traduction dun chier-source en chier-objet 2 . Elle comporte quatre phases principales (en principe successives bien quune certaine imbrication soit possible): 1. lanalyse lexicale, 2. lanalyse syntaxique, 3. lanalyse statique (analyse du typage), 4. la gnration de code. Optionnellement, il peut exister une cinquime phase doptimisation. En C, la compilation est prcde dun prtraitement effectu par le prprocesseur et est suivie dune dition de liens. La gure 7.1 schmatise la chane de compilation dun programme C constitu de 3 chiers-sources fic1.c, fic2.c et fic3.c. Le sufxe .c caractrise par convention les chiers contenant du texte C. Chaque chier-source est analys sparment, dabord par le prprocesseur qui effectue des remplacements textuels (voir 6), puis par le compilateur proprement dit qui produit un chier-objet de mme nom de base que le source mais avec le sufxe .o. Enn lditeur de liens (ld sous UNIX) assemble les diffrents chiersobjets en un chier binaire excutable unique de nom conventionnel par dfaut a.out. Cest lditeur de liens daller chercher dans les bibliothques standards ou utilisateurs les modules demands par le programme. En fait, au moins sous UNIX, ces diffrentes phases sont enchaines par
2. En fait la compilation, dans lacception la plus large, recouvre la traduction dun langage de programmation en un autre. La dnition donne ici (traduction dun langage de haut niveau en langage-machine ou assembleur) est cependant trs rpandue et la seule utile ici.
c Jean-Paul R IGAULT
GLOSSAIRE
197
une commande unique, dnomme en gnral cc 3 comme par exemple sous le shell 4 :
% cc fic1.c fic2.c fic3.c
c Jean-Paul R IGAULT
198
GLOSSAIRE
compilation spare Lorsque le texte dun mme programme est reparti dans plusieurs chiers-sources diffrents, chacun de ces chiers est trait sparment par le compilateur. Ceci signie que chaque chier-source doit contenir toute linformation ncessaire au compilateur, et en particulier toute celle qui permet lanalyse de smantique statique (typage). constante Un objet qui ne peut tre modi. constante littrale Un objet qui ne peut tre modi et dont la valeur peut tre value lors de la compilation (on parle aussi de constante lexicale, ou encore statique). Ainsi en C, 12, -4, "bonjour" sont des constantes littrales. contrle (ot de contrle, instruction de contrle) Le t de contrle est dtermin par lordre dexcution des instructions. En programmation procdurale Ce ot est en gnral squentiel (les instructions sont excutes dans lordre o elles sont crites) mais cette squentialit peut tre brise par les instructions de contrle qui sont le plus souvent de trois types: slection (if et switch en C), boucles (while, do ... while et for), la rupture de squence (goto). corps dune fonction Voir fonction. corps dun module La partie du module contenant la dnitions des donnes et des fonctions du module. Certaines de ces donnes et de ces fonctions peuvent tre prives au module (encapsulation) et seront donc invisibles dans le reste du programme. Les autres sont exportes dans les parties du programmes qui voient les dclarations contenues dans la spcication du module. dclaration dun objet La dclaration dun objet indique simplement le type de cet objet au compilateur. Il peut en gnral y avoir autant de dclarations que lon veut pour un mme objet du moment quelles sont identiques (ou compatibles). dnition dun objet La dnition dun objet, outre quelle indique aussi son type, rserve et ventuellement initialise la zone mmoire correspondant lobjet. Il y a une et une seule dnition dans tout le programme pour chaque objet. dynamique Une notion quelconque est dynamique si elle ne peut tre vrie, value, utilise... qu lexcution. Sinon, elle peut ltre la compilation et est dite alors statique. dition de liens En cas de compilation spare, lditeur de liens rassemble tous les chiers-objets en un chier binaire excutable unique. Cest donc lui qui rsoud les rfrences externes. Certaines de ces objet externes peuvent tre dnis dans des bibliothques.
c Jean-Paul R IGAULT
GLOSSAIRE
199
Sous UNIX, lditeur de liens sappelle ld (le loader). Il est noter quen gnral aucune information sur le type des objets nest transmise lditeur de liens. encapsulation Un mcanisme syntaxique qui permet de rendre des donnes ou des fonctions prives un module invisibles dans les autres modules. Lencapsulation renforce la scurit de programmation et vite la pollution de lespace des noms globaux. entte dune fonction Voir fonction. expression statique Une expression valuable la compilation (voir aussi constante statique). externe Voir rfrence externe. chier-objet Le rsultat de la compilation dun chier-source. Cest un chier qui contient le code gnr (binaire objet) et la valeur initiale des donnes; en cas de compilation spare, il nest pas en gnral directement excutable, car il peut contenir des rfrences externes non rsolues, cest-dire des rfrences des objets dnis dans dautres chiers-sources. Le nom du chier-objet est celui du chier-source qui la engendr o lon remplace le sufxe (.c) par .o. chier-source Un chier contenant le texte du programme (ou dune partie du programme). Noter que compilateur analyse le chier selon lordre lexical (lordre de lecture). Par convention expresse, les chiers de source C ont un nom sufx par .c. fonction Llment principal de structuration du ot de contrle en programmation procdurale. En C, la dnition dune fonction comporte deux parties: lentte qui en reprend la signature et prcise le nom des arguments formels. le corps qui contient les dnitions dobjets locaux la fonction et les intructions excuter chaque appel de la fonction. gnration de code La dernire phase obligatoire de la compilation pour produire le binaire objet. En fait, trs souvent les compilateurs C produisent de lassembleur quil est ncessaire de traduire encore en langage-machine. langage de haut niveau Par opposition au langage-machine ou lassembleur, un langage de haut niveau a une structure et une forme plus proche des applications que de la machine. C est un langage de haut niveau pour la programmation systme, mme sil comporte quelques caractristiques de bas niveau (voir 1.2.2).
c Jean-Paul R IGAULT
200
GLOSSAIRE
langage-machine Le langage des instructions et des donnes interprtables directement par le matriel. Il constitue le contenu du binaire excutable. library Voir bibliothque. lvalue Voir rfrence. modularit La possibilit de dcouper le source dun programme en units syntaxiquement indpendantes regroupant chacune des donnes et les fonctions de manipulation directes de ces donnes. La modularit dsigne aussi la qualit dun programme ainsi dcoup. La modularit en C fait lobjet du chapitre 8. module Une unit syntaxique de modularit. Un module comporte en gnral deux parties: la spcication, qui indique les dclarations des objets et les dnitions des types que le module rend visible (quil exporte), le corps qui contient les dnitions de ces objets. Les parties du programme qui veulent utiliser un module doivent en voir la spcication (elles limportent). En C, le corps dun module est contenu dans un chier-source .c, alors que sa spcication est en gnral dans un chier dentte (de sufxe conventionnel .h). Limportation est effectue grce la directive dinclusion de chier du prprocesseur. nom (dun objet) Dans un langage de programmation, la plupart des objets sont dsigns par un nom reprsent par un identicateur. Un identicateur constitue une rfrence sur lobjet. Cependant, certains objets sont anonymes: ils peuvent tre temporaires (crs automatiquement par le compilateur), ou alors allous dynamiquement et accessibles uniquement au travers de pointeurs. objet Un objet 5 est en fait une zne de mmoire, quil sagisse de mmoire de donnes ou de mmoire dinstruction. En C, un objet peut tre une constante, une variable ou une fonction; chaque objet a un type qui doit tre connu la compilation avant toute utilisation; un objet aussi une valeur (le contenu de la zne mmoire).
objet (chier-objet) Voir chier-objet. optimisation de code Cest la dernire phase, en gnral optionnelle, de la compilation.
5. Cette notion dobjet est plus large que la forme spcialise et plus riche laquelle on se rfre dans des expressions comme langage orient-objets ou programmation objets .
c Jean-Paul R IGAULT
GLOSSAIRE
201
pointeur Un pointeur est un objet dont la valeur est ladresse dun autre objet. En C, les pointeurs ont un type diffrent selon le type de lobjet point. Les pointeurs jouent un rle central en C, mais ils constituent aussi un danger potentiel pour la scurit de programmation. prprocesseur Avant la compilation, un chier-source de C est analys par le prprocesseur (cpp) qui effectue un certain nombre de remplacement textuels (cest--dire lexicaux). Le prprocesseur est tudi au chapitre 6. Profondment remodel lors de la normalisation, il constitue une des causes dincompatibilit principales entre ANSI C et le C traditionnel. procdure En C, il sagit simplement dune fonction qui na pas de valeur de retour (on dclare ce type void). programmation (procdurale, structure, imprative) Cest le type de programmation habituelle avec les langages comme FORTRAN, Pascal, Ada, et bien sr C. Le ot de contrle y est squentiel sauf si lon utilise des instructions de contrle (slection, boucles, goto...); les procdures et fonctions permettent de structurer le ot de contrle. La programmation structure est en fait un ensemble de rgles ou de conseils pour structurer le ot de contrle. Lradication du goto en est son trait le plus connu. prototype de fonction Voir signature dune fonction et type. rfrence Un rfrence est la dsignation dun objet. La forme la plus simple de rfrence est donc le nom dun objet (un identicateur). Mais C permet bien dautres formes qui seront tudies au fur et mesure de leur introduction. Une rfrence ne doit pas tre confondue avec la valeur de lobjet rfrenc. Dans le jargon de C, on designe une rfrence par le terme lvalue qui rappelle quune rfrence est requise en partie gauche dun affectation (lvalue comme abrviation de left value); la valeur de lobjet est alors dsigne, pour la mme raison, par le vocable rvalue (comme right value). Lobjet rfrenc peut tre une variable ou une constante (dans le second cas, la lvalue ne peut pas apparaitre en partie gauche dune affectation!). Bien sr une rfrence rencontre en partie droite dun affectation est drfrence pour rendre la valeur de lobjet. Ainsi dans laffectation suivant entre deux entiers (type int)
int i, j; ... i = j;
i est une rfrence (lvalue) alors que j est une valeur (rvalue).
c Jean-Paul R IGAULT
202
GLOSSAIRE
rfrence externe En cas de compilation spare, une rfrence externe est une rfrence dans un chier, disons fic.c, un objet possiblement dni dans un autre chier de compilation. Si lobjet est effectivement dni ailleurs, alors le chier-objet fic.o contiendra une rfrence externe non rsolue. rfrence (externe) non rsolue Voir rfrence externe et dition de liens. smantique dexcution La signication du programme, ce quil est cens faire, la fonction quil calcule, la transformation quil effectue sur ses donnes dentre pour produire les rsultats en sortie, etc... smantique statique Voir analyse statique. signature dune fonction Cest le type dune fonction qui est dni par le type de la valeur de retour (cest--dire du rsultat) de la fonction, le nombre et le type de chacun des arguments. En ANSI C, la signature dune fonction est dclare grce au prototype (qui prcise en outre le nom de la fonction). spcication dun module La partie dun module qui contient les dclarations des objets exports par le module (leur dnitions sont dans le corps du module). Si un autre module veut utiliser ces objets, il doit rendre visible (importer) la spcication. En C, ceci est ralis par le directive #include du prprocesseur. source Voir chier-source. statique Voir dynamique. typage fort Voir type. type Un type dnit un ensemble dobjets partageant un mme ensemble doprations. Par exemple, lensemble des entiers avec les oprations usuelles (arithmtiques, logiques...). On distingue en C les types de base (entiers et rels) et les types utilisateur qui sont obtenus par des oprations de construction de type. Ces types utilisateur sont des agrgats (tableaux, structures, unions). En ANSI C, tout objet a un type qui doit tre connu avant lutilisation de lobjet. Le compilateur vrie (donc de manire statique) le bon usage des types (typage fort). valeur (dun objet) Voir objet. variable Un objet modiable, le contraire dune constante.
c Jean-Paul R IGAULT
Index
Symboles
, 71 ., 94, 97, 110 ..., 144 -, 32, 57, 64, 107 unaire, 57 , 60 #, 130, 134 ##, 134 #define, 84, 131135, 155 #elif, 136 #else, 136 #endif, 136 #error, 138 #if, 136 #ifdef, 136 #ifndef, 137 #define, 26, 130, 131, 157, 202 #line, 138 #pragma, 138 #undef, 135, 155 %, 33, 57 %=, 64 & unaire, 104 , 32, 57, 119 * unaire, 105 =, 64 * +, 32, 57, 107 unaire, 57 ++, 37, 64, 107 +=, 64, 107 ,, 66, 68 ,=<=, 106 /, 32, 57 /=, 64 1 complment , 60 2 complment , 69 ;, 27, 71 <, 28, 58, 106 <<=, 64 <=, 28, 58 <assert.h>, 61 <stdbool.h>, 58 =, 28, 63 -=, 64, 107 ==, 28, 58, 106 >, 28, 58, 106 ->, 94, 97, 110 >=, 28, 58, 106 >>=, 64 [], 119 __DATE__, 135 __FILE__, 135 __LINE__, 135 __STDC__, 135 __STDC_VERSION__, 135 __TIME__, 135 {}, 26
A
abort, 172 accolade, 26 Ada, 18 addition, 57 de pointeurs, 108 dun entier un pointeur, 107 adresse dun objet, 31 oprateur , 104 affectation, 195 compose, 63 de tableau, 85 simple, 63 conversion lors dune , 70 expression d, 28 agrgat, 50, 83, 195, 202 initialisation d, 85, 86, 91, 96 alignement, 93 allocation dynamique, 113, 158, 161 alternative, 32 analyse lexicale, 195 statique, 195 syntaxique, 195 and, 176
203
c Jean-Paul R IGAULT
204
INDEX
and_eq, 176 anjuta, 184 anonyme type , 55, 91 ansi, 1, 17 -ansi, 21 a.out, 196 appel de fonction, 145 dune fonction, 26 aquamacs, 178 archive, 196 argc, 39 argument de fonction, 195 effectif, 142, 195 formel, 142, 195 passage d, 142 pointeur en , 147 arguments de main, 39 liste variable d, 144 nombre variable d, 31 ordre dvaluation des , 145 type des dune fonction, 142 argv, 39 arithmtique oprateur , 32 ascii, 43 assembleur, 195 assert, 61, 135 <assert.h>, 135, 171 associativit des oprateurs, 66 atof, 172 atoi, 40, 172 auto, 159 automatique objet , 158 avant dclaration en , 30
binaire excutable, 196 objet, 199 bit bit et , 60 oprateur , 60 ou , 60 bitand, 176 bitor, 176 bits champ de , 93 bloc, 26, 71, 145, 158 _Bool, 46, 58, 172 bool, 58, 172 boolen, 58 bord effet de , 28, 38, 63, 68 boucle instruction de , 75 Bourne, Stephen, 20, 197 break, 74, 77
C
C traditionnel, 17 calloc, 161, 172 caractre multi-octet, 43 constante , 52 caractres chane de , 88 chane de littrale, 26, 48, 120, 143 case, 73 case-sensitivity, 43 cast, 64, 108 CC, 21, 115, 180 cc, 197 CDT, 183 CCFLAGS, 21, 180 CFLAGS, 115 champ de bits, 93 dune structure, 90 slection de de structure, 94 slection de dunion, 97 chane de caractres, 88 de caractres littrale, 26, 48, 120, 143 chanes concatnation des littrales adjacentes, 48, 129
B
b, 17 backquote, 43 base type de , 202 types de , 50 types scalaires de , 5056 bash, 20 bcpl, 17 bibliothque, 196 standard, 19, 26
c Jean-Paul R IGAULT
INDEX
205
char, 51, 68 char*, 120 code gnration de , 199 Code::Blocks, 183 CodeLite, 183 coloration syntaxique, 41 commentaire, 26, 31 comparaison de pointeurs, 106 compilation, 196 conditionnelle, 135137 spare, 18, 156, 198 unit de , 21, 156 compl, 176 complment 1, 60 2, 69 _Complex, 46 complex, 173 <complex.h>, 173 compose affectation , 63 concatnation des chanes littrales adjacentes, 48, 129 oprateur de , 134 conditionnelle compilation, 135137 expression , 40, 65, 67 conjonction, 60 const, 53, 83, 85 constante, 198 caractre, 52 dcimale, 48, 52 entire, 48, 52 hexadcimale, 48, 52 lexicale, 198 littrale, 4650, 198 octale, 48, 52 relle, 48 statique, 198 dnition de , 132 pointeur et , 109 pointeur sur une , 148 continue, 78 contrle ot de , 198 instruction de , 7279, 198 conversion, 68 de pointeur, 108 entre entier et rel, 69 entre entiers, 69 entre rels, 69
explicite, 64 lors dune affectation, 70 usuelle, 70 spcication de , 33 copie de tableau, 85 corps dun module, 164, 198 dune fonction, 26, 145, 199 cpp, 201 <ctype.h>, 72, 152, 173 Cygwin, 20
D
ddd, 1, 180, 185
dcalage oprateur de , 61 dcimale constante , 48, 52 dclaration, 158 de pointeur simple, 103 dexterne, 160 dun objet, 198 en avant, 30 dclaration d, 160 declarationdeclaration forward forward , 30 dcrmentation oprateur de , 64 defined, 136 dnition, 53, 158 de constante, 132 de fonction, 26, 144 de macro, 131 dun objet, 198 rcursive de macro, 134 diffrence entre tableau et pointeur, 120 digraph, 44 dimension dun tableau, 83 tableau variable, 88 directive au prprocesseur, 26 disjonction, 60 division, 57 do ... while, 75 dot, 94 double, 31, 53, 69, 70 doxygen, 178 dure de vie, 158 dynamique
c Jean-Paul R IGAULT
206
INDEX
E
ebcdic, 43 chappement squence d, 129 eclipse, 183, 185 ed, 178 diteur de liens, 158 dition de liens, 196, 198 effectif argument , 142, 195 effet de bord, 28, 38, 63, 68 lment lexical, 44 ellipse, 144 else, 72 else if, 72 emacs, 178, 180, 185 encapsulation, 199 entte de fonction, 145 dune fonction, 26, 199 chier d, 131, 137, 165 entier addition dun un pointeur, 107 conversion entre et rel, 69 entire promotion, 68 constante , 48, 52 entiers, 51 conversion entre , 69 enum, 55, 68, 155, 163 numr type , 54 environnement variable d, 21 EOF, 29 quivalence entre tableau et pointeur, 120 erreur standard, 32 <errno.h>, 171 espace, 45 de nommage, 155 et bit bit, 60 logique, 38, 60 squentiel, 60 tiquette, 78, 163
valuation oprateur d squentielle, 66, 68 ordre d des arguments, 145 excutable binaire , 196 excution smantique d, 202 exit, 31, 79, 172 EXIT_FAILURE, 172 EXIT_SUCCESS, 172 explicite conversion , 64 exponentiation, 57 expression conditionnelle, 40, 65, 67 daffectation, 28 relationnelle, 28 statique, 65, 73, 199 valeur de vrit dune , 58 expressions, 5671 externe objet , 160 rfrence , 202
F
F, 53 f, 53 fabs, 32 false, 172 fclose, 174 <fenv.h>, 172 ff, 45 Fibonacci, Leonardo Pisano dit, 38 chier dentte, 131, 137, 165 -objet, 158 inclusion de , 130, 131 chier-objet, 196, 199 chier-source, 196, 199 float, 53, 69, 70 <float.h>, 53 ot de contrle, 198 fonction, 141153, 199 en ligne, 132, 146 local, 145 rcursive, 38 retournant un pointeur, 146 appel de , 145 appel dune , 26 argument de , 195 corps dune , 26, 145, 199 dnition de , 26, 144
c Jean-Paul R IGAULT
INDEX
207
entte de , 145 entte dune , 26, 199 invocation dune , 26 nom de , 149 pointeur sur , 148 prototype de , 201 signature dune , 26, 202 type de retour dune , 141 type des arguments dune , 142 fonctions imbrication de , 145 fopen, 174 for, 37, 75, 115 form feed, 34 format, 33 formel argument , 142, 195 fort typage , 202 forward declaration, 30 fread, 174 free, 158, 172 Free Software Foundation, 1 fseek, 174 fuite de mmoire, 180 fwrite, 174
G
gcc, 1, 21, 177, 183 gdb, 1, 178, 180, 183 gedit, 178
gnration de code, 199 gnrique pointeur , 108 getchar, 29, 174 global objet , 159 gnu, 1 goto, 78, 155, 201 gprof, 1, 180
H
header-le, 131, 137, 165 hexadcimale constante , 48, 52 ht, 45
I
V 2.1 7 fvrier 2010
-I, 130 identicateur, 45, 200 ieee, 17 if, 31, 72 _Imaginary, 46 imbrication de fonctions, 145 imprative programmation , 201 inclusion de chier, 130, 131 unique, 137 incrmentation postxe, 64 prxe, 64 oprateur d, 64 indexation, 85 de pointeur, 119 indirection oprateur d, 105, 119 initialisation, 53 dagrgat, 85, 86, 91, 96 de pointeur, 104 de tableau, 84 dune union, 96 inline, 46, 133, 146, 166 static , 146 instruction, 26 de boucle, 75 de contrle, 7279, 198 de rupture de squence, 77 de slection, 31, 72 simple, 29, 71 instructions, 7179 int, 27, 51, 70 sous-types de , 51 int8_t, 172 int32_t, 172 integral type, 51 interface dun module, 164 <inttypes.h>, 172 Invariant Code Set, 44 invite, 197 invocation dune fonction, 26 isalpha, 72, 173 isdigit, 72, 173 iso, 17 iso 646-1983, 44 <iso646.h>, 176 iso-9899, 17 ispuct, 173 isspace, 72, 173 itration, 75
c Jean-Paul R IGAULT
208
INDEX
J
Java, 18
long, 51, 52, 70 long double, 53, 70 lvalue, 63, 142, 201
K
kate, 178 kde, 184 kdevelop, 184
M
machine langage- , 200 MacOs X, 20 macro, 58, 61, 84, 131135 dnition de , 131 dnition rcursive de , 134 variadic , 132 main, 26, 151, 172 arguments de , 39 majuscule et minuscule, 43 make, 21, 27, 115, 178, 179, 183, 185 Makefile, 21 malloc, 113, 125, 158, 161, 172, 180 <math.h>, 173 mlange de types, 70 memcmp, 174 memcpy, 85, 174 mmoire partage, 54 fuite de , 180 taille , 65 Microsoft Windows, 20 minuscule majuscule et , 43 modieur de type, 53 modulaire programmation , 164 modularit, 18, 200 module, 158, 164, 200 corps dun , 164, 198 interface dun , 164 spcication dun , 202 modulo, 57 mono-dimensionnel tableau, 83 multi-dimensionnel tableau, 86 multi-octet caractre , 43 multiple pointeur , 105 multiplication, 57
L
L, 52 l, 52 LANG, 20 langage-machine, 200 ld, 196, 199 lexical ment , 44 lexicale analyse , 195 constante , 198 library, 200 standard , 19 liens diteur de , 158 dition de , 196, 198 ligne fonction en , 132, 146 ligne-suite, 129 <limits.h>, 52 Linux, 1 liste, 111 variable darguments, 144 littrale chane de caractres , 26, 48, 120, 143 constante , 4650, 198 local fonction , 145 objet , 159 locale variable , 71, 88, 92 <locale.h>, 174, 175 logique et, 60 oprateur, 58 ou, 60 et , 38 ou , 38
c Jean-Paul R IGAULT
INDEX
209
,NDEBUG, 171 ngation, 60 newline, 27 Newton, Sir Isaac, 29 bit eld, 93 nl, 45 nom de fonction, 149 de tableau, 120 de type, 64 dun objet, 200 nombre variable darguments, 31 nommage espace de , 155 not_eq, 176 NULL, 104106, 112 nul, 34, 49 NULLNULL, 171
asociativit des , 66 prcdence des , 28, 66 priorit des , 66 optimisation, 200 or, 176 or_eq, 176 ordre dvaluation des arguments, 145 ou bit bit, 60 exclusif, 60 logique, 38, 60 squentiel, 60
P
padding, 93 panorama des types, 50 partage mmoire , 54 Pascal, 18 passage dargument, 142 par valeur, 142 pointeur, 18, 31, 103122, 201 en argument, 147 et constante, 109 gnrique, 108 multiple, 105 simple, 103 sur fonction, 148 sur une constante, 148 sur void, 108 addition dun entier un , 107 conversion de , 108 dclaration de simple, 103 diffrence entre tableau et , 120 quivalence entre tableau et , 120 fonction retournant un , 146 indexation de , 119 initialisation de , 104 tableau de , 118 pointeurs addition de , 108 comparaison de , 106 soustraction de , 107 point-virgule, 71 portabilit, 18 Posix, 171, 175 postxe incrmentation , 64 #pragma, 172 prcdence
O
objet, 200 automatique, 158 externe, 160 global, 159 local, 159 priv, 160 rmanent, 159 statique, 158 adresse dun , 31 binaire , 199 dclaration dun , 198 dnition dun , 198 chier- , 158, 196, 199 nom dun , 200 octale constante , 48, 52 odr, 158 Onedenition Rule, 158 oprateur adresse, 104 arithmtique, 32 bit bit, 60 de concatnation, 134 de dcalage, 61 de dcrmentation, 64 de slection, 110 dvaluation squentielle, 66, 68 dincrmentation, 64 dindirection, 105, 119 logique, 58 relationnel, 58 oprateurs, 5671
c Jean-Paul R IGAULT
210
INDEX
des oprateurs, 28, 66 prxe incrmentation , 64 prprocesseur, 26, 129138, 201 directive au , 26 prtraitement, 129 printf, 26, 32, 144, 174 priorit des oprateurs, 66 priv objet , 160 procdurale programmation , 18, 201 procdure, 37, 141, 201 programmation imprative, 201 modulaire, 164 procdurale, 18, 201 structure, 18, 201 promotion entire, 68 prompt, 197 prototype, 26, 143, 144 de fonction, 201 putchar, 29, 174
relationnel oprateur, 58 relationnelle expression , 28 rmanent objet , 159 rsolue rfrence non , 202 reste signe du , 58 restrict, 46 retour type de dune fonction, 141 return, 32, 35, 79 Ritchie, Dennis, 1, 17 rupture instruction de de squence, 77 rvalue, 201
S
scalaire type , 50 scalaires types scalaires de base, 5056 scanf, 31, 174 scope, 159 slection de champ de structure, 94 de champ dunion, 97 instruction de , 31, 72 oprateur de , 110 smantique dexcution, 202 statique, 195 spare compilation , 18, 156, 198 squence dchappement, 129 instruction de rupture de , 77 squentiel et, 60 ou, 60 squentielle oprateur dvaluation , 66, 68 sh, 20 shell, 20, 31, 39 redirection du , 29 short, 51, 68 <signal.h>, 175 signature dune fonction, 26, 202 signe du quotient, 58
Q
quotient signe du , 58
R
rand, 172 record, 18, 90, 97 rcursif type , 111 rcursive dnition de macro, 134 fonction , 38 rcursivit, 39 redirection du shell, 29 rel conversion entre entier et , 69 relle constante , 48 rels, 53 conversion entre , 69 rfrence, 63, 85, 94, 97, 201 externe, 202 non rsolue, 202 register, 159
c Jean-Paul R IGAULT
INDEX
211
du reste, 58 signed, 51 simple affectation , 63 dclaration de pointeur , 103 instruction , 29, 71 pointeur , 103 size_t, 171 sizeof, 85, 89, 93, 171 sortie standard, 26 source chier- , 196, 199 soustraction, 57 de pointeurs, 107 sous-types de int, 51 spcication de conversion, 33 dun module, 202 srand, 172 Stallman, Richard, 1 standard library, 19 bibliothque , 19, 26 erreur , 32 sortie , 26 static, 146, 159161, 166 inline, 146 statique analyse , 195 constante , 198 expression , 65, 73, 199 objet , 158 smantique , 195 -std=c99, 21 <stdarg.h>, 144 <stdbool.h>, 172 <stddef.h>, 171 stderr, 32 stdin, 27 <stdint.h>, 172 <stdio.h>, 26, 174 <stdlib.h>, 104, 172 stdout, 26, 27 strcat, 173 strcmp, 173 strcpy, 173 <string.h>, 173 stringication, 134 strlen, 88 struct, 90 structure, 9095 avec variante, 97 champ dune , 90
slection de champ de , 94 tableau de , 95 structure programmation , 18, 201 surcharge, 173 switch, 73, 98 syntaxique analyse , 195 coloration , 41
T
tab, 34, 45 tableau, 8390 dimension variable, 88 de pointeur, 118 de structure, 95 mono-dimensionnel, 83 multi-dimensionnel, 86 (, 83 affectation de , 85 copie de , 85 diffrence entre et pointeur, 120 dimension dun , 83 quivalence entre et pointeur, 120 initialisation de , 84 nom de , 120 tag, 55, 90, 111, 155, 163 taille mmoire, 65 <tgmath.h>, 173 Thomson, Ken, 17 <time.h>, 175 token, 129, 195 tolower, 152, 173 toupper, 173 traditionnel C , 17 trigraph, 44, 129 true, 172 toupper, 152 typage fort, 202 type, 202 anonyme, 55, 91 de base, 202 de retour dune fonction, 141 des arguments dune fonction, 142 enumr, 54 rcursif, 111 scalaire, 50 utilisateur, 83, 202
c Jean-Paul R IGAULT
212
INDEX
integral , 51 modieur de , 53 nom de , 64 typedef, 55, 155, 163 types de base, 50 scalaires de base, 5056 utilisateur, 50 mlange de , 70 panorama des , 50
U
U, 52 u, 52 uint64_t, 172 unaire - , 57 & , 104 * , 105 + , 57 Unicode, 174 Unicode, 20, 43, 45 union, 9699 initialisation dune , 96 slection de champ de , 97 unique inclusion , 137 unsigned, 51 unit de compilation, 21, 156 Unix, 17 unsigned, 52, 69 unsigned int, 70 unsigned long, 70 usuelle conversion , 70 utf-8, 20 utilisateur type , 83, 202 types , 50
nombre darguments, 31 tableau dimension , 88 variadic macro, 132 variant, 97 variante structure avec , 97 vrit valeur de , 72 valeur de dune expression, 58 vi, 178 vie dure de , 158 vim, 178 visibilit, 159 Visual Studio, 20, 23 void, 37, 50, 79, 141, 143, 201 pointeur sur , 108 void*, 108 volatile, 54, 85 vt, 45
W
-Wall, 21
X
X Window, 1 x3j11, 1, 17 XCode, 184 xemacs, 178 xor, 176 xor_eq, 176 xxgdb, 180, 185
V
valeur, 202 de vrit, 72 de vrit dune expression, 58 passage par , 142 valgrind, 2 variable, 202 denvironnement, 21 locale, 71, 88, 92 liste darguments, 144
zsh, 20
c Jean-Paul R IGAULT