You are on page 1of 129

Universit du Luxembourg e

20052006

Cours de programmation avance. e Le langage C

Sbastien Varrette <Sebastien.Varrette@imag.fr> e Nicolas Bernard


<n.bernard@lafraze.net>

Version : 0.4

Table des mati`res e


1 Les bases 1.1 Un langage compil . . . . . . . . . . . . . . . . . . . . e 1.1.1 Gnralits sur les langages de programmation e e e 1.1.2 Le C comme langage compil . . . . . . . . . . e 1.1.3 Notes sur la normalisation du langage C . . . . 1.1.4 C en pratique : compilation et debuggage . . . 1.2 Les mots-cls . . . . . . . . . . . . . . . . . . . . . . . e 1.3 Les commentaires . . . . . . . . . . . . . . . . . . . . . 1.4 Structure gnrale dun programme C . . . . . . . . . e e 1.5 Notion didenticateur . . . . . . . . . . . . . . . . . . 1.6 Conventions dcritures dun programme C . . . . . . e 1.7 Les types de base . . . . . . . . . . . . . . . . . . . . . 1.7.1 Les caract`res . . . . . . . . . . . . . . . . . . . e 1.7.2 Les entiers . . . . . . . . . . . . . . . . . . . . 1.7.3 Les ottants . . . . . . . . . . . . . . . . . . . 1.7.4 Le type void . . . . . . . . . . . . . . . . . . . 2 La syntaxe du langage 2.1 Expressions et Oprateurs . . . . . . . . e 2.1.1 Oprateurs arithmtiques . . . . e e 2.1.2 Oprateurs daectation . . . . . e 2.1.3 Oprateurs relationnels . . . . . e 2.1.4 Oprateurs logiques . . . . . . . e 2.1.5 Oprateurs bit a bit . . . . . . . e ` 2.1.6 Oprateurs dacc`s ` la mmoire e e a e 2.1.7 Autres oprateurs . . . . . . . . e 2.2 Les structures de contrle . . . . . . . . o 2.2.1 Instruction if...else . . . . . . . 2.2.2 Instruction for . . . . . . . . . . 2.2.3 Instruction while . . . . . . . . 2.2.4 Instruction do...while . . . . . 2.2.5 Instruction switch . . . . . . . . 2.2.6 Instruction goto . . . . . . . . . 2.2.7 Instruction break . . . . . . . . 2.2.8 Instruction continue . . . . . . 2.3 La rcursivit . . . . . . . . . . . . . . . e e 3 2 2 2 3 4 4 5 6 7 9 9 10 10 11 13 14 15 15 15 16 16 17 18 18 19 19 20 20 20 21 22 23 23 24 24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2.4

2.5

Les conversions de types . . . . . . . . . . . . . . . . . . 2.4.1 Les situations de la conversion de type . . . . . . 2.4.2 La r`gle de promotion des entiers . . . . . . . . e 2.4.3 Les conversions arithmtiques habituelles . . . . e 2.4.4 Les surprises de la conversion de type . . . . . . Principales fonctions dentres-sorties standard . . . . . e 2.5.1 La fonction getchar . . . . . . . . . . . . . . . . 2.5.2 La fonction putchar . . . . . . . . . . . . . . . . 2.5.3 La fonction puts . . . . . . . . . . . . . . . . . . 2.5.4 La fonction dcriture ` lcran formatte printf e a e e 2.5.5 La fonction de saisie scanf . . . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

25 25 26 26 27 27 28 28 28 29 31 33 34 34 34 35 36 37 38 40 41 41 41 42 44 45 47 49 50 51 52 52 52 53 53 55 55 56 57 57 58

3 Les pointeurs 3.1 Dclaration dun pointeur . . . . . . . . . . e 3.2 Oprateurs de manipulation des pointeurs . e 3.2.1 Loprateur adresse de & . . . . . . e 3.2.2 Loprateur contenu de : * . . . . . e 3.3 Initialisation dun pointeur . . . . . . . . . 3.4 Arithmtique des pointeurs . . . . . . . . . e 3.5 Allocation dynamique de mmoire . . . . . e 3.6 Libration dynamique avec la fonction free e

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

4 Les types drivs e e 4.1 Les numrations . . . . . . . . . . . . . . . . . . . . . . e e 4.2 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Initialisation dun tableau . . . . . . . . . . . . . 4.2.2 Tableaux multidimensionnels . . . . . . . . . . . 4.2.3 Passage de tableau en param`tre . . . . . . . . . e 4.2.4 Relation entre tableaux et pointeurs . . . . . . . 4.2.5 Cas des tableaux de cha nes de caract`res . . . . e 4.2.6 Gestion des arguments de la ligne de commande 4.3 Les structures . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Initialisation et aectation dune structure . . . . 4.3.2 Comparaison de structures . . . . . . . . . . . . 4.3.3 Tableau de structures . . . . . . . . . . . . . . . 4.3.4 Pointeur vers une structure . . . . . . . . . . . . 4.3.5 Structures auto-rfres . . . . . . . . . . . . . . eee 4.4 Les unions . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 Dclaration dune union . . . . . . . . . . . . . . e 4.4.2 Utilisation pratique des unions . . . . . . . . . . 4.4.3 Une mthode pour allger lacc`s aux membres . e e e 4.5 Les champs de bits . . . . . . . . . . . . . . . . . . . . . 4.6 Dnition de synonymes de types avec typedef . . . . . e 4

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

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

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

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

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

5 Retour sur les fonctions 5.1 Dclaration et dnition dune fonction . . e e 5.2 Appel dune fonction . . . . . . . . . . . . . 5.3 Dure de vie des identicateurs . . . . . . . e 5.4 Porte des variables . . . . . . . . . . . . . e 5.4.1 Variables globales . . . . . . . . . . 5.4.2 Variables locales . . . . . . . . . . . 5.5 Rcursivit . . . . . . . . . . . . . . . . . . e e 5.6 Passage de param`tres ` une fonction . . . e a 5.6.1 Gnralits . . . . . . . . . . . . . . e e e 5.6.2 Passage des param`tres par valeur . e 5.6.3 Passage des param`tres par adresse . e 5.6.4 Passage de tableau en param`tre . . e 5.7 Fonction ` nombre variable de param`tres . a e 5.8 Pointeurs sur une fonction . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

59 59 61 61 62 62 63 64 64 64 65 65 66 67 69 71 71 71 73 73 73 73 74 74 74 75 76 77 79 79 79 80 80 81 82 82 83 83 84 85 85 87 88 88

6 Gestion des chiers 6.1 Ouverture et fermeture de chiers . . . . . . . . . . . . . . . . . . 6.1.1 Ouverture de chiers : la fonction fopen . . . . . . . . . . 6.1.2 Fermeture de chiers : la fonction fclose . . . . . . . . . 6.2 Les entres-sorties formates . . . . . . . . . . . . . . . . . . . . . e e 6.2.1 La fonction dcriture en chier fprintf . . . . . . . . . . . e 6.2.2 La fonction de saisie en chier fscanf . . . . . . . . . . . 6.3 Impression et lecture de caract`res dans un chier . . . . . . . . e 6.3.1 Lecture et criture par caract`re : fgetc et fputc . . . . e e 6.3.2 Lecture et criture optimises par caract`re : getc et putc e e e 6.3.3 Relecture dun caract`re . . . . . . . . . . . . . . . . . . . e 6.3.4 Les entres-sorties binaires : fread et fwrite . . . . . . . e 6.3.5 Positionnement dans un chier : fseek, rewind et ftell 7 Les directives du prprocesseur e 7.1 La directive #include . . . . . . . . . . . . . . . 7.2 La directive #define . . . . . . . . . . . . . . . . 7.2.1 Dnition de constantes symboliques . . . e 7.2.2 Les macros avec param`tres . . . . . . . . e 7.3 La compilation conditionnelle . . . . . . . . . . . 7.3.1 Condition lie a la valeur dune expression e ` 7.3.2 Condition lie a lexistence dun symbole e ` 7.3.3 Loprateur defined . . . . . . . . . . . . e 7.3.4 La commande #error . . . . . . . . . . . 7.3.5 La commande #pragma . . . . . . . . . . 8 La programmation modulaire 8.1 Principes lmentaires . . . . . . . . . . . . . . ee 8.2 Eviter les erreurs dinclusions multiples . . . . 8.3 La compilation spare . . . . . . . . . . . . . . e e 8.4 Rsum des r`gles de programmation modulaire e e e 5

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

8.5

Loutils make . . . . . . . . . . . . . . . . . 8.5.1 Principe de base . . . . . . . . . . . 8.5.2 Cration dun Makefile . . . . . . . e 8.5.3 Utilisation de macros et de variables

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

89 89 89 90 93 93 94 94 95 95 95 96 96 96 96 97 97 97 98 98 98 98 98 99 99 99 99 100 100 100 100 100 101 101 101 101 102 102 102 103 103 104 104

9 La biblioth`que standard e 9.1 Diagnostics derreurs <assert.h> . . . . . . . . . . . . . . . . . . 9.2 Gestion des nombres complexes <complex.h> . . . . . . . . . . . 9.3 Classication de caract`res et changements de casse <ctype.h> . e 9.4 Valeur du dernier signal derreur <errno.h> . . . . . . . . . . . . 9.5 Gestion dun environnement ` virgule ottante <fenv.h> . . . . a 9.5.1 Gestion des exceptions . . . . . . . . . . . . . . . . . . . . 9.5.2 Gestion des arrondis . . . . . . . . . . . . . . . . . . . . . 9.5.3 Gestion des environnements en virgule ottante. . . . . . 9.6 Intervalle et prcision des nombres ottants <float.h> . . . . . . e 9.7 Dnitions de types entiers de taille xe <inttypes.h> . . . . . e e 9.8 Alias doprateurs logiques et binaires <iso646.h> . . . . . . . . e 9.9 Intervalle de valeur des types entiers <limits.h> . . . . . . . . . 9.10 Gestion de lenvironnement local <locale.h> . . . . . . . . . . . 9.11 Les fonctions mathmatiques de <math.h> . . . . . . . . . . . . . e 9.11.1 Fonctions trigonomtriques et hyperboliques . . . . . . . . e 9.11.2 Fonctions exponentielles et logarithmiques . . . . . . . . . 9.11.3 Fonctions diverses . . . . . . . . . . . . . . . . . . . . . . 9.12 Branchements non locaux <setjmp.h> . . . . . . . . . . . . . . . 9.13 Manipulation des signaux <signal.h> . . . . . . . . . . . . . . . 9.14 Nombre variable de param`tres <stdarg.h> . . . . . . . . . . . . e 9.15 Dnition du type boolen <stdbool.h> . . . . . . . . . . . . . . e e 9.16 Dnitions standards <stddef.h> . . . . . . . . . . . . . . . . . . e 9.17 Dnitions de types entiers <stdint.h> . . . . . . . . . . . . . . e 9.18 Entres-sorties <stdio.h> . . . . . . . . . . . . . . . . . . . . . . e 9.18.1 Manipulation de chiers . . . . . . . . . . . . . . . . . . . 9.18.2 Entres et sorties formates . . . . . . . . . . . . . . . . . e e 9.18.3 Impression et lecture de caract`res . . . . . . . . . . . . . e 9.19 Utilitaires divers <stdlib.h> . . . . . . . . . . . . . . . . . . . . 9.19.1 Allocation dynamique . . . . . . . . . . . . . . . . . . . . 9.19.2 Conversion de cha nes de caract`res en nombres . . . . . . e 9.19.3 Gnration de nombres pseudo-alatoires . . . . . . . . . e e e 9.19.4 Arithmtique sur les entiers . . . . . . . . . . . . . . . . . e 9.19.5 Recherche et tri . . . . . . . . . . . . . . . . . . . . . . . . 9.19.6 Communication avec lenvironnement . . . . . . . . . . . 9.20 Manipulation de cha nes de caract`res <string.h> . . . . . . . . e 9.21 Macros gnriques pour les fonctions mathmatiques <tgmath.h> e e e 9.22 Date et heure <time.h> . . . . . . . . . . . . . . . . . . . . . . . 9.23 Manipulation de caract`res tendus <wchar.h> et <wctype.h> . e e 6

A Etude de quelques exemples 106 A.1 Gestion des arguments de la ligne de commande . . . . . . . . . 106 A.2 Exemple de liste cha ee . . . . . . . . . . . . . . . . . . . . . . . 110 n B Makele gnrique e e C Le btisier e C.1 Erreur avec les oprateurs . . . . . . . . . . . . e C.1.1 Erreur sur une comparaison . . . . . . . C.1.2 Erreur sur laectation . . . . . . . . . . C.2 Erreurs avec les macros . . . . . . . . . . . . . C.2.1 Un #define nest pas une dclaration . e C.2.2 Un #define nest pas une initialisation C.2.3 Erreur sur macro avec param`tres . . . e C.2.4 Erreur avec les eets de bord . . . . . . C.3 Erreurs avec linstruction if . . . . . . . . . . . C.4 Erreurs avec les commentaires . . . . . . . . . . C.5 Erreurs avec les priorits des oprateurs . . . . e e C.6 Erreur avec linstruction switch . . . . . . . . C.6.1 Oubli du break . . . . . . . . . . . . . . C.6.2 Erreur sur le default . . . . . . . . . . C.7 Erreur sur les tableaux multidimensionnels . . C.8 Erreur avec la compilation spare . . . . . . . e e C.9 Liens utiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 116 . 116 . 116 . 116 . 116 . 117 . 117 . 117 . 117 . 117 . 118 . 118 . 119 . 119 . 119 . 119 . 119 . 121

Avant-propos
Ce polycopi vient en complment au cours de programmation avance dispens e e e e au sein de luniversit du Luxembourg en DUT dinformatique. En aucun cas il e ne dispense de la prsence en cours. Ce document na pas pour vocation dtre e e un ouvrage de rfrence du Langage C (pour cela, voir par exemple [KR88]). Il ee doit simplement permettre au lecteur dapprhender rapidement ce langage. e Pour la petite histoire, le langage de programmation C a t dvelopp dans les ee e e annes 70 par Dennis Ritchie aux laboratoires Bell. Il puise ses origines dans e le langage de programmation sans type BCPL (Basic Combined Programming Language, developp par M. Richards) et dans B (developp par K. Thompson). e e En 1978, Brian Kernighan et Dennis Ritchie produisent la premi`re description e ocielle de C. Le design de C lui conf`re un certain nombre davantages : e Le code source est hautement portable Le code machine est tr`s ecace e les compilateurs C sont disponibles dans tous les syst`mes courants. e La version courante de ce document ainsi que le code source des exemples abords dans ce polycopi sont disponibles sur mon site e e http://www-id.imag.fr/~svarrett/

Chapitre 1

Les bases
1.1
1.1.1

Un langage compil e
Gnralits sur les langages de programmation e e e

Dans le domaine de la programmation, on utilise aujourdhui des langages de programmation de haut niveau (i.e facilement comprhensibles par le proe grammeur) par opposition aux langages de bas niveau (de type assembleur) qui sont plus orients vers le langage machine. Parmi les exemples de langages de e haut niveau, on peut citer les langages C, C++, Java, Cobol etc... Il est nanmoins ncessaire doprer une opration de traduction du langage e e e e de haut niveau vers le langage machine (binaire). On distingue deux types de traducteurs : 1. linterprteur qui traduit les programmes instruction par instruction dans e le cadre dune intraction continue avec lutilisateur. Ainsi, le programme e est traduit a chaque excution. ` e 2. le compilateur qui traduit les programmes dans leur ensemble : tout le programme doit tre fourni en bloc au compilateur pour la traduction. Il e est traduit une seule fois. Il en rsulte deux grands types de langages de programmation : e les langages interprts : dans ce cas, lexcution des programmes se fait sous ee e un terminal ` la vole. Ce type de langage est donc adapt au dveloppement a e e e rapide de prototypes (on peut tester immdiatement ce que lon est en train e de faire) On citera par exemple les langages Scheme, Ruby, etc... les langages compils : les source du code est dans un premier temps pass e e dans une moulinette (le compilateur) qui va gnrer, sauf erreur, un excue e e table qui est un chier binaire comprhensible par votre machine. Ce come portement est illustr dans la gure 1.1. Ce type de langage est donc adapt e e a ` la ralisation dapplications plus ecaces ou de plus grande envergure (il y e a une optimisation plus globale et la traduction est eectue une seule fois et e non ` chaque excution). a e En outre, un langage compil permet de diuser les programmes sous forme e binaire, sans pour autant imposer la diusion le source du code, permettant 2

ainsi une certaine protection de la proprit intellectuelle. Quelques exemples ee de langages compils : C, C++, Java etc... e A noter que certains langages peuvent tre ` la fois compils et interprts, e a e ee comme par exemple CAML.
prog.c
void f(int a){} int main(){ ... }

a.out

COMPILATION

source du programme

executable binaire

Fig. 1.1 Compilation dun code source

1.1.2

Le C comme langage compil e

Le langage C est donc un langage compil (mme sil existe des interprteurs e e e plus ou moins exprimentaux). Ainsi, un programme C est dcrit par un chier e e texte, appel chier source. La compilation se dcompose en fait en 4 phases e e successives1 : 1. Le traitement par le prprocesseur : le chier source (portant lextension e .c) est analys par le prprocesseur qui eectue des transformations puree e ment textuelles (remplacement de cha nes de caract`res, inclusion dautres e chiers sources, compilation conditionnelle ...). Les direntes commandes e qui peuvent tre fournies au prprocesseur seront exposes dans le chae e e pitre 7. 2. La compilation : la compilation proprement dite traduit le chier gnr e ee par le prprocesseur (dextension .i) en assembleur, cest-`-dire en une e a suite dinstructions du microprocesseur qui utilisent des mnmoniques rene dant la lecture possible. 3. Lassemblage : cette opration transforme le code assembleur (extension e .s) en un chier binaire, cest-`-dire en instructions directement compra e hensibles par le processeur. Gnralement, la compilation et lassemblage e e se font dans la foule, sauf si lon spcie explicitement que lon veut e e le code assembleur. Le chier produit par lassemblage est appel chier e objet (et porte lextension .o). Les chiers objets correspondant aux librairies pr-compiles ont pour suxe .a. e e 4. Ldition de liens : un programme est souvent spar en plusieurs chiers e e e sources (voir le chapitre 8 consacr ` la programmation modulaire), pour ea des raisons de clart mais aussi parce quil fait gnralement appel ` des e e e a librairies de fonctions standards dj` crites. Une fois chaque chier de ea e code source assembl, il faut donc lier entre eux les dirents chiers e e objets. Ldition de liens produit alors un chier dit excutable (a.out e e par dfaut). e
1

Les extensions mentionnes peuvent varier selon le compilateur utilis (gcc ici). e e

1.1.3

Notes sur la normalisation du langage C

[KR88], publi initialement en 1978 par Brian W. Kernighan et Denis M. Ritchie e a fait oce de norme pendant longtemps (on parle alors de C K&R, en rfrence ee aux 2 auteurs). Devant la popularit du C, lAmerican National Standard Institut2 charge en e 1983 le comit X3J11 de standardiser le langage C. Le fruit de ce travail est e nalement t approuv en dcembre 1989, le standard ANSI C89 est n. ee e e e LInternational Organization for Standardization3 a adopt en 1990 ce standard e en tant que standard international sous le nom de ISO C90. Ce standard ISO remplace le prcdent standard ANSI (C89) mme ` lintrieur des USA, car e e e a e rappelons-le, ANSI est une organisation nationale, et non internationale comme lISO. Les standards ISO, en tant que tel, sont sujets ` des rvisions, notamment en a e 1995 (C95) et en 99 (on parle alors de C99). Comme on le verra dans la suite, certaines fonctionnalits dcrites dans ce document hritent de la norme C99 e e e et ntait pas prsentes avant. e e

1.1.4

C en pratique : compilation et debuggage

Compilation dun programme C Il existe videmment un grand nombre de compilateurs C pour chaque platee forme (Windows, Linux, MAC etc...). Le compilateur dtaill dans cette section e e correspond au compilateur libre gcc, disponible sur quasiment toutes les plateformes UNIX. La compilation du chier prog.c se droule en gnral en deux tapes : e e e e 1. traduction du langage C dans le langage de la machine (binaire) sous forme dun chier objet prog.o : gcc -O3 -Wall -c prog.c 2. regroupement des variables et des fonctions du programme avec les fonctions fournies par des bibliotheques du systeme ou faites par lutilisateur (edition de liens) : gcc -o prog prog.o Ces deux commandes ont donc permis de gnrer lexcutable prog qui peut e e e alors est lanc (./prog dans une fentre de shell). e e Si un seul chier source est utilis pour gnrer lexcutable, les deux come e e e mandes prcdentes peuvent tre regroupes en une seule : e e e e gcc -O3 -Wall prog.c -o prog Quelques explications sur les options de gcc simposent : -O : eectue des optimisations sur le code gnr. La compilation peut alors e ee prendre plus de temps et utiliser plus de mmoire. Un niveau, variant de 0 ` 3 e a spcie le degr doptimisation demand. Ainsi, loption -O3 permet dotenir e e e le code le plus optimis. e -Wall : active des messages davertissements (warnings) supplmentaires ; e -c : demande au compilateur de ne faire que la premiere tape de compilation ; e
2 3

www.ansi.org www.iso.ch

-o : permet de prciser le nom du chier produit. e il est important de noter que loption -Wall est indispensable puisquelle permet de detecter la plupart des erreurs dinattention, de mauvaise pratique du langage ou dusage involontaire de conversion implicite. Il existe bien s r de nombreuses autres options disponibles4 . En voici certaines u qui pourront tre utiles : e -Idir : ajoute le rpertoire dir dans la liste des rpertoires ou se trouvent e e des chiers de header .h (voir chapitre 8). Exemple : -Iinclude/ -Werror : consid`re les messages davertissements (warnings) comme des ere reurs -g : est ncessaire pour lutilisation dun debugger. Il est possible de foure nir plus ou moins dinformations au debugger. Ce crit`re est caractris par e e e un niveau variant entre 1 et 3 (2 par dfaut). Ainsi, lutilisation du niveau e maximal (option -g3) assure que le maximum dinformations sera fourni au debugger ; -pg : ncessaire pour lutilisation du proler GNU : gprof5 (qui permet de e dterminer par exemple quelles sont les parties du code qui prennent le plus e de temps dexcution et qui doivent donc tre optimises). e e e Enn, il est possible dautomatiser la compilation de lensemble des chiers dun projet ` laide de lutilitaire make qui sera prsent au 8.5. a e e Note sur les erreurs de compilation : A ce stade, on peut classer les erreurs de compilation en deux categories : les erreurs de syntaxe produites lors de la premi`re phase lorsque le code nest e pas crit en C correct. e les erreurs de resolution des symboles produites lors de la deuxi`me phase e lorsque des variables ou des fonctions utilises ne sont denies nulle part. e Outils de debuggage Il existe un certain nombre de debugger qui peuvent tre utiliss pour corriger e e les erreurs dun programme : en ligne de commande : gdb (http://www.gnu.org/software/gdb/gdb.html) sous forme dune application graphique : xxgdb (http://www.cs.uni.edu/Help/xxgdb.html) ddd (http://www.gnu.org/software/ddd/) A noter galement lexistence de valgrind qui permet de dtecter les fuites de e e mmoire6 e

1.2

Les mots-cls e

Un certains nombres de mots sont rservs pour le langage C. Avant de come e mencer, il convient donc den donner la liste exhaustive :
4 5

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

auto break case char const continue default do

double else enum extern oat for goto if

int long register return short signed sizeof static

struct switch typedef union unsigned void volatile while

1.3

Les commentaires

Les commentaires sont tr`s important dans nimporte quel langage de programe mation car ils permettent de documenter les chiers de sources. /* Ceci est un commentaire qui peut staler sur plusieurs lignes */ e // Ceci est un commentaire qui ne peut staler que sur une ligne e Attention car le commentaire sur une ligne est une fonctionnalit qui na t e ee dnie quavec la norme C99 ! e Une bonne habitude ` prendre dans le domaine gnral de la programmation a e e est de dcouper chaque chier source selon plusieurs niveaux de commentaires : e 1. le chier : pour indiquer le nom de lauteur, du chier, les droits de copyright, la date de cration, les dates et auteurs des modications ainsi que e la raison dtre du chier ; e 2. la procdure : pour indiquer les param`tres et la raison dtre de la proe e e cdure e 3. groupe dinstruction : pour exprimer ce que ralise une fraction signicae tive dune procdure. e 4. dclaration ou instruction : le plus bas niveau de commentaire. e On considrera ainsi lexemple fourni en annexe A.1 page 106. e e Attention : Une erreur classique est doublier la squence fermante */. Dans lexemple suivantes, les instructions 1 ` n seront ignores ` la compilation : a e a /* premier commentaire Instruction 1 ... Instruction n /* deuxi`me commentaire */ e Il convient galement de prendre la bonne habitude de comment son code e e source ` la mode Doxygen7 . Doxygen est un syst`me de documentation pour a e de nombreux langages de programmation (C++, C, Java, Objective-C, Python, IDL etc...). Il permet de gnrer facilement une documentation compl`te sur un e e e code source en utilisant un format de commentaires particulier.
7

www.doxygen.org

1.4

Structure gnrale dun programme C e e

De mani`re gnrale, un programme C consiste en la construction de blocs indie e e viduels appeles fonctions qui peuvent sinvoquer lun lautre. Chaque fonction e ralise une certaine tche8 . e a Pour pouvoir sexcuter, un programme C doit contenir une fonction spciale e e appele main qui sera le point dentre de lexcution (cest ` dire la premi`re e e e a e fonction invoque au dmarrage de lexcution). Toutes les autres fonctions sont e e e en fait des sous-routines. Un programme C comporte galement la dclaration de variables qui correse e pondent ` des emplacements en mmoire. Ces emplacements permettent de a e stocker des valeurs qui peuvent tre utilises/modies par une ou plusieurs e e e fonctions. Les noms des variables sont des identicateurs quelconques (voir 1.5). Voici deux exemples signicatifs : 1. Dabord, le petit classique qui ache ` lcran Hello World ! : a e
#include <stdio.h> // Directive de preprocesseur void main() { printf("Hello world!"); }

2. Un exemple un peu plus complet, faisant appara tre toutes les composantes dun programme C :
#include <stdio.h> // Directive de preprocesseur #define TVA 15 // idem - la TVA au Luxembourg float prix_TTC; //dclaration dune variable externe e /* Exemple de dclaration dune fonction secondaire */ e /* ajoute la TVA au prix HT; renvoie le prix TTC */ float ajoute_TVA(float prix_HT) { return prix_HT*(1 + (TVA/100)); } /* Fonction main: point dentree de lexcution*/ e void main() { float HT; //dclaration dune variable interne e puts("Entrer le prix H.T. : "); // appel de fonctions scanf("%f",&HT); // dfinies dans stdio.h e prix_TTC = ajoute_TVA(HT); //appel de notre fonction printf("prix T.T.C. : %.2f\n",prix_TTC); }

On voit sur ces deux exemples les direntes composantes dun programme C : e 1. les directives du prprocesseur : elles permettent deectuer des mae nipulations sur le texte du programme source, avant la compilation. Une
A noter quil existe en C des fonctions prcompiles qui peuvent tre incluses dans le e e e programme, ces fonctions tant contenues dans des chiers spciaux appels library les (exe e e tension *.lib). Pour accder ` ces fonctions, une directive doit tre issue au compilateur e a e indiquant dinclure les header les (extension *.h) contenant les dclarations correspondantes e ` ces fonctions. Dans tous les cas, les autres fonctions doivent tre crites par le programmeur. a e e
8

directive du prprocesseur est une ligne de programme source commene cant par le caract`re di`se (#). Ces instructions seront dtailles dans le e e e e chapitre 7 mais on distingue dj` les deux directives les plus utilises : ea e #include qui permet dinclure un chier. Ici, le chier stdio.h dnit e (on prf`re dire dclare) les fonctions standards dentres/sorties (en ee e e anglais STanDard In/Out), qui feront le lien entre le programme et la console (clavier/cran). Dans cet exemple, on utilise les fonctions puts, e printf et scanf (voir 2.5). #define qui dnit une constante. Ici, a chaque fois que le compilateur e rencontrera le mot TVA, il le remplacera par 15. e 2. les dclarations de variables : Ces dclarations sont de la forme : e type nom variable [= <valeur>] ; Les variables peuvent tre dclares soit de faon externe (cest ` dire en e e e c a dehors dune fonction), soit de faon interne (on dit aussi locale) ` une c a fonction. Par exemple, dans la dclaration float prix_TTC ; on a dni la variable e e externe identie par prix_TTC, de type float (le type des nombres rels e e dit ` virgule ottante, do` ce nom). Les trois types scalaires de base du C a u sont lentier (int), le rel (float) et le caract`re (char) et seront abords e e e au 1.7. Toute variable C est type. Cela permet notamment de savoir quelle e place la variable occupera en mmoire et comment il faudra interprter la e e suite de bits stocke en mmoire. On peut bien s r dnir ses propres types e e u e (voir chapitre 4), mais il existe un certain nombre de types disponibles de faon native. Ces types de base sont dtaills dans la section 1.7. c e e Enn, on ne peut jamais utiliser de variable sans lavoir dclare. e e 3. la dnition de fonctions. Ce sont des sous-programmes dont les inse tructions vont dnir un traitement sur des variables. La dclaration dune e e fonction est de la forme : type_resultat nom fonction (type1 arg1 . . . typen argn ) { <dclaration de variables locales > e <liste dinstructions > } En C, une fonction est dnie par : e (a) une ligne dclarative qui contient : e type_resultat : le type du rsultat de la fonction. e nom fonction : le nom qui identie la fonction. type1 arg1 . . . typen argn : les types et les noms des param`tres de e la fonction (b) un bloc dinstructions dlimit par des accolades { ... }, contee e nant : 8

e e e 4. Des commentaires (voir 1.3) : ils sont limins par le prprocesseur. A noter que pour ignorer une partie de programme il est prfrable dutiee liser une directive du prprocesseur (#ifdef ... #endif : voir le chae pitre 7). Dautres exemples de chiers source sont fournis en annexe A page 106.

<dclaration de variables locales > : les dclarations des donnes e e e locales (cest ` dire des donnes qui sont uniquement connues ` a e a lintrieur de la fonction) e e <liste dinstructions > : la liste des instructions qui dnit laction qui doit tre excute. e e e Une instructions est une expression termine par un ;. Cest un e ordre lmentaire que lon donne ` la machine et qui manipulera ee a les donnes (variables) du programme. Dans notre exemple, on a e vu deux types de manipulation : lappel de fonctions dune part (puts, printf, scanf ou ajoute_TVA) et une aectation (=). puts ache ` lcran le texte fourni en argument. a e scanf attend que lon entre une valeur au clavier, puis la met dans la variable HT, sous format rel (%f). e printf ache ` lcran un texte formatt. a e e Ces fonctions seront dtailles au 2.5 page 27. e e Par dnition, toute fonction en C fournit un rsultat dont le type e e doit tre dni. Le retour du rsultat se fait en gnral ` la n de e e e e e a la fonction par linstruction return. Le type dune fonction qui ne fournit pas de rsultat est dclar comme void (voir 1.7.4). e e e

1.5

Notion didenticateur

Un identicateur, comme son nom lindique, permet de donner un nom ` une a entit du programme (quil sagisse dune variable ou dune fonction). Ils sont e sujets aux r`gles suivantes : e 1. Ils sont forms dune suite de lettres (a ` z et A ` Z), de chires (0 e a a a ` 9) et du signe _. En particulier, les lettres accentues sont interdites ; e 2. le premier caract`re de cette suite ne peut pas tre un chire ; e e 3. les identicateurs sont case-sensitive. Ainsi, les noms var1, S_i, _start et InitDB sont des identicateurs valides, tandis que i:j ou 1i ne le sont pas.

1.6

Conventions dcritures dun programme C e

Avant daller plus loin, il convient dtablir un certain nombre de r`gles de e e prsentation que devra suivre tout bon programmeur qui souhaite crire des e e programmes C lisibles9 : ne jamais placer plusieurs instructions sur une mme ligne ; e
9

Ces r`gles peuvent largement tre tendues ` tout langage de programmation e e e a

utiliser des identicateurs signicatifs ; grce ` lindentation des lignes, faire ressortir la structure syntaxique du a a programme ; laisser une ligne blanche entre la derni`re ligne des dclarations et la premi`re e e e ligne des instructions ; arer les lignes de programme en entourant par exemple les oprateurs avec e e des espaces ; bien commenter les listings tout en vitant les commentaires triviaux. e A ce propos, la lecture de [Her01] devrait permettre au lecteur daqurir de bon e rexes de programmation. e

1.7
1.7.1

Les types de base


Les caract`res e

On utilise le mot-cl char pour dsigner une variable de type char. Il sagit e e en fait dun entier cod sur 8 bits interprt comme un caract`re utilis sur la e ee e e machine (il sagit en gnral du code ASCII de ce caract`re). e e e Ex : char c1 = a; // Dclaration dune variable c1 de type char e // a laquelle on affecte la valeur a // A noter lutilisation du simple quote char c2 = 97; //c2 correspond galement au caract`re a e e Le tableau 1.1 donne la liste des principaux codes ASCII en dcimal (pour e tre prcis, il sagit de la premi`re moiti des caract`re ASCII, lautre moiti e e e e e e correspondants en gnral ` des caract`res tendus comme A). e e a e e code 0 10 20 30 40 50 60 70 80 90 100 110 120 0 NUL LF DC4 RS ( 2 < F P Z d n x 1 SOH VT NAK US ) 3 = G Q [ e o y 2 STX NP SYN SP * 4 > H R \ f p z 3 ETX CR ETB ! + 5 ? I S ] g q { 4 EOT SO CAN , 6 @ J T ^ h r | 5 ENQ SI EM # 7 A K U i s } 6 ACK DLE SUB $ . 8 B L V j t 7 BEL DC1 ESC % / 9 C M W a k u DEL 8 BS DC2 FS & 0 : D N X b l v 9 HT DC3 GS 1 ; E O Y c m w

Tab. 1.1 Codes ASCII en dcimal e

10

Caract`res particuliers e Il existe un certain nombre de caract`res particuliers dont les principaux sont e rsums dand le tableau 1.2. e e Caract`re e Smantique e \n retour a la ligne ` \t tabulation \b backspace \ \" \? ?

Tab. 1.2 Quelques caract`res spciaux e e Ces caract`res ont une signication particuli`re pour le langage C. Par exemple, e e le caract`re " doit tre utilis dans les cha e e e nes de caract`re (et la squence \" e e permet de reprsenter ce caract`re dans une cha e e ne). De mme, le caract`re ? e e est utilis dans les trigraphes qui sont des squences de trois caract`res permete e e tant de dsigner les caract`res # [ ] \ ^ { } | ~. Le tableau 1.3 dtaille les e e e trigraphes disponibles. Trigraphe Signication ??= # ??( [ ??/ \ ??) ] ?? ^ ??< { ??! | ??> } ??~

Tab. 1.3 Les 9 trigraphes disponibles en norme ANSI

Et les cha nes de caract`res ? e En empitant un peu sur la suite, les cha e nes de caract`res sont vues comme un e pointeur sur des caract`res et sont donc de type char *. Ex : e char * chaine = "Hello World !";// une chaine de caract`re e // noter lutilisation du double // quote Plus de dtails dans le chapitre 3 consacr aux pointeurs. e e

1.7.2

Les entiers

On utilise alors le mot-cl int. Exemple : e /* dclaration la plus courante dune variable de type int */ e int a = 14; // la variable a est initialise ` la valeur 14 e a /* Utilisation des prcisions (cas le + gnral)*/ e e e short int b; // b est cod sur 16 bits e int c; // c est cod sur 16 ou 32 bits e long int d; // d est cod sur 32 bits e // la possibilit de lcriture suivante dpend du compilateur e e e long long int e; // d est cod sur 64 bits. e 11

/* Prcision du signe */ e unsigned long int n; //n est un entier non sign sur 32 bits e Par dfaut, les entiers sont en reprsentation signe (principalement en repre e e e sentation par complment ` 2 pour les acharns). e a e Ainsi, un short int peut contenir tout entier donc la valeur est comprise entre 215 et 215 1. Au contraire, un unsigned short int (non sign) pourra e 16 1. contenir un entier compris entre 0 et 2 Le tableau 1.4 regroupe les types entiers standards avec quelques informations supplmentaires (la taille utilise en mmoire et lintervalle des valeurs pose e e sibles. Voici quelques valeurs numriques pour avoir un meilleur ordre dide e e des intervalles utiliss : e 215 = 32.768 216 = 65.536 31 = 2.147.483.648 2 232 =4.294.967.295 . 63 =9.223.372.036.854.775.808 64 =18.446.744.073.709.551.616 2 2 Type char unsigned char signed char int unsigned int short int unsigned short int long unsigned long long long(*) unsigned long long(*) Taille mmoire e 1 octet 1 octet 1 octet 2 ou 4 octets 2 ou 4 octets 2 octets 2 octets 4 octets 4 octets 8 octets 8 octets Intervalle de valeurs [-128 ;127] ou [0,255] [0 ;255] [-128 ;127] [-215 ;215 1] ou [231 ; 231 1] [0 ;216 1] ou [0 ;232 1] [215 ; 215 1] [0; 216 1] [231 ; 231 1] [0; 232 1] [263 ; 263 1] [0; 264 1]

Tab. 1.4 Les types entiers standards. (*) : dpend du compilateur utilis e e Remarque : les valeurs limites des dierents types sont indiques dans le chier e header <limits.h> (voir 9.9). En principe, on a sizeof(short) <= sizeof(int) <= sizeof(long) Cas des constantes enti`res e On distingue 3 notations : dcimale (criture en base 10) : cest lcriture usuelle. Ex : 372 ; e e e octale (base 8) : on commence par un 0 suivi de chires octaux. Ex : 0477 ; hxadcimale (base 16) : on commence par 0x (ou 0X) suivis de chires hxae e e dcimaux (0-9 a-f). Ex : 0x5a2b, 0X5a2b, 0x5A2b. e Par dfaut, le type des constantes enti`res est le type int. On peut aussi spcier e e e explicitement le type en ajoutant lun des suxes L ou l (pour le type long), LL or ll (pour le type long long). On peut aussi ajouter le suxe U ou u (pour unsigned). Des exemples sont fournis dans le tableau suivant : 12

Dcimal e 15 32767 10U 32768U 16L 27UL

Octal 017 077777 012U 0100000U 020L 033ul

Hexa Oxf 0x7FFF 0xAU 0x8000u 0x10L 0x1BUL

Type int int unsigned int unsigned int long unsigned long

1.7.3

Les ottants

On distingue trois types de ottants allant de la prcision la plus faible ` la e a plus forte (qui dpend de limplmentation) : float, double et long double. e e Ex : double Pi = 3.14159; Representation Interne La taille de stockage et la reprsentation interne des nombres ottant dpend e e du compilateur utilis mais la plupart utilise le standard IEEE 754-1985[Ste90]. e Un nombre ottant x est ainsi compos dun signe s (s = 1 pour un nombre e ngatif, 0 sinon) , dune mantisse m et un exposant exp en base 2 tels que : e x = s m 2exp avec 1.0 m < 2 ou m=0

La prcision du nombre ottant dtermine le nombre de bits utilise pour la e e e mantisse, alors lintervalle de valeur est dtermin par le nombre de bits utie e liss par lexposant. Le tableau 1.5 dtaille ces informations pour chaque type e e ottant. Type oat double long double Taille mmoire e 4 octets 8 octets 10 octets Intervalle de valeurs [1, 2 1038 ; 3, 4 1038 ] [2, 3 10308 ; 1, 7 10308 ] [3, 4 104932 ; 1, 1 104932 ] Prcision e 6 chires dcimaux e 15 chires dcimaux e 19 chires dcimaux e

Tab. 1.5 Les types ottant pour les nombres rels. e La gure 1.2 montre le format de stockage dun nombre de type float dans la reprsentation IEEE : e
s
Position du bit:
31

Exposant exp
22

Mantisse m
0

Fig. 1.2 Format de stockage IEEE pour un nombre de type oat En binaire, le premier bit de la mantisse non nulle sera toujours 1 : il nest donc pas reprsent. Lexposant est stock avec un biais (qui vaut 127 pour le type e e e float) Un petit exemple pour bien comprendre : 2, 5 = 1 1, 25 21 . Les valeurs stockes seront donc : S = 1 ; exp = 127 + 1 ; m = 0.25 e 13

Cas des constantes ottantes Des exemples dutilisation des constantes ottantes sont rsumes dans le tae e bleau suivant : notation C 2. 1.4 2.e4 1.4e-4 correspondance 2 1.4 2 104 1.4 104 notation C .3 2e4 .3e4 correspondance 0.3 2 104 0.3 104

1.7.4

Le type void

On a vu que toute variable C tait type, de mme que toute valeur de retour e e e dun fonction. Mais il peut arriver quaucune valeur ne soit disponible. Pour exprimer lide de aucune valeur, on utilise le mot-cl void. Ce type est utilis e e e dans trois situations : 1. les expressions de type void : on les rencontre principalement dans la dclaration de fonctions qui nont pas de valeur de retour. e Ex : void exit (int status); On utilise aussi dans le cadre dune conversion de type (on parle de cast, voir 2.4) vers le type void, ce qui na de sens que si la valeur rsultat e nest pas utilise. Ex : (void)printf("Un exemple."); e 2. le prototype de fonctions sans param`tres : int rand(void); e A noter quen pratique, on crira plus simplement : int rand(); e 3. les pointeurs vers le type void : le type void * est le type pointeur ge nrique, cest ` dire quil est capable de pointer vers nimporte quel type e a dobjet. Il reprsente donc ladresse de lobjet et non son type. Plus de e prcisions dans la section3 page 33 consacre aux pointeurs. e e

14

Chapitre 2

La syntaxe du langage
2.1 Expressions et Oprateurs e

Une expression est une combination doprateurs et doprandes. Dans les cas les e e plus simples, une expression se rsume ` une constante, une variable ou un appel e a de fonction. Des expressions peuvent galement tre utilises comme oprandes e e e e et tre jointes ensembles par des oprateurs pour obtenir des expressions plus e e complexes. Toute expression a un type et si ce type nest pas void, une valeur. Quelques exemples dexpressions : 4 * 512 // Type: int 4 + 6 * 512 // Type: int; equivalent to 4 + (6 * 512) printf("Un exemple!\n") // Appel de fonction, type: int 1.0 + sin(x) // Type: double srand((unsigned)time(NULL)) // Appel de fonction; type: void (int*)malloc(count*sizeof(int)) // Appel de fonction; type: int * Les oprateurs peuvent tre unaires (` une oprande) ou binaires (` deux ope e a e a e randes). Il existe un seul oprateur ternaire (il sagit de ?:) Les paragraphes e qui suivent dtaillent les oprateurs disponibles. e e

2.1.1

Oprateurs arithmtiques e e

Les principaux oprateurs arithmtiques sont rsums dans le tableau 2.1. Les e e e e oprandes de ces oprateurs peuvent appartenir ` tout type arithmtique1 e e a e Quelques remarques : On peut eectuer une conversion de type aux oprandes. Le rsultat de lope e e ration prend le type de cette conversion. Ainsi ; 2.0/3 est quivalent ` 2.0/3.0 e a et le rsultat est de type float. e Le rsultat dune division dentiers est aussi un entier ! Ex : e 6 / 4 // Resultat: 1 6 % 4 // Resultat: 2 6.0 / 4.0 // Resultat: 1.5
1

Seul loprateur % requiert des types entiers e

15

Oprateur e + * / % +(unaire) -(unaire) ++(unaire)

Traduction Addition Soustraction Produit Division Reste Signe positif Signe ngatif e Incrment e

Exemple x + y x - y x * y x / y x % y +x -x x++ ou ++x

--(unaire)

Decrment e

x-- ou --x

Rsultat e laddition de x et y la soustraction de x et y la multiplication de x et y le quotient de x et y Reste de la division euclidienne de x par y la valeur de x la ngation arithmtique de x e e x est incrment (x = x + 1). Loprateur e e e prxe ++x (resp. suxe x++) incrmente e e x avant (resp. apr`s) de lvaluer e e x est dcrement (x = x 1). Loprateur e e e prxe --x (resp. suxe x--) dcrmente e e e x avant (resp. apr`s) de lvaluer e e

Tab. 2.1 Les principaux oprateurs arithmtiques e e Concernant lincrmentation pr/postxe, voici un petit exemple pour bien e e comprendre : Supposons que la valeur de N soit gale ` 5 : e a Incrmentation postxe : X = N++ ; Rsultat : N = 6 et X = 5 e e Incrmentation prxe : X = ++N ; Rsultat : N = 6 et X = 6 e e e

2.1.2

Oprateurs daectation e
Traduction aectation simple aectation compose e Exemple x = y x += y Rsultat e assigne la valeur de y ` x a x (op)=y est quivalent e x = x (op) y

Oprateur e = (op)=

a `

Tab. 2.2 Les oprateurs daectation e On distingue deux types doprateurs dassignement qui sont rsums dans le e e e tableau 2.2. Loprande de gauche doit tre une expression qui dsigne un objet e e e (une variable par exemple). Dans lcriture x(op)=y, la variable x nest value quune seule fois. (op) est un e e e oprateur arithmtique ou de manipulation de bits Les oprateurs daectation e e e composs sont donc : e += -= *= /= %= &= ^= |= <<= >>=

2.1.3

Oprateurs relationnels e

Toute comparaison est une expression de type int qui renvoie la valeur 0 (false) ou 1 (true). Il faut que les oprandes soient du mme type arithmtique (ou des e e e pointeurs sur des objets de mme type). e 16

Oprateur e < <= > >= == !=

Traduction infrieur e infrieur ou gal e e suprieur e suprieur ou gal e e galit e e ingalit e e

Exemple x < y x <= y x > y x >= y x == y x != y

Rsultat e 1 si x est infrieur ` y e a 1 si x est infrieur ou gal ` y e e a 1 si x est suprieur ` y e a 1 si x est suprieur ou gal ` y e e a 1 si x est gal ` y e a 1 si x est dirent de y e

Tab. 2.3 Les oprateurs relationnels e Les dirents oprateurs relationnels sont dtaills dans le tableau 2.3. e e e e Attention : Une erreur ultra classique est de confondre loprateur dgalit e e e (==) avec celui daectation (=). Ainsi, considrons le code suivant2 : e
/* Fichier: erreur.c */ #include <stdio.h> int main () { int x=14,y=1; // x est diffrent de y e if (x = y) //erreur!!! il faudrait crire if (x == y) e printf("x est gal ` y (%i=%i)\n",x,y); e a else printf("x est diffrent de y (%i!=%i)\n",x,y); e return 0; }

A priori, x et y sont dirents et on sattend ` obtenir un message le conrmant. e a Nanmoins, lerreur decriture a permis deectuer une aectation de la valeur e de x si bien que ce programme renvoit la ligne3 : x est gal ` y (1=1)) (not e a e la valeurs de x) Avec une criture correcte, on aurait obtenu : x est diffrent de y (14!=1) e e Remarque : en initialisant y a 0 (et non a 1), on aurait obtenu le message x est diffrent de y (0 !=0) car le rsultat de laectation est la valeur aecte e e e (ici 0). Cette valeur est considre comme fausse pour la condition teste dans ee e le if si bien que les instruction de la branche else sont excutes (voir 2.2.1). e e

2.1.4

Oprateurs logiques e

Les oprateurs logiques, lists dans le tableau 2.4 permettent de combiner le e e rsultat de plusieurs expressions de comparaison en une seule expression logique. e Les oprandes des oprateurs logiques peuvent tre nimporte quel scalaire (i.e e e e arithmtique ou pointeur). Toute valeur dirente de 0 est interprte comme e e ee vraie (et 0 correspond ` faux). Comme pour les expressions relationnelles, les a expressions logiques renvoient une valeur enti`re (0=false ; 1=true). e Remarque : les oprateurs && et || valuent les oprandes de gauche ` droite et e e e a le rsultat est connu d`s loprande de gauche. Ainsi, loprande de droite nest e e e e
2 3

Compiler ce code avec la commande gcc -Wall erreur.c -o erreur loption de compilation -Wall a quand mme soulev un warning e e

17

Oprateur e && || !

Traduction ET logique OU logique NON logique

Exemple x && y x || y !x

Rsultat e 1 si x et y sont dirents de 0 e 1 si x et/ou y sont dirents de 0 e 1 si x est gal ` 0. Dans tous les autres e a cas, 0 est renvoy. e

Tab. 2.4 Les oprateurs logiques e value que si celle de gauche est vraie dans le cas de loprateur && (respece e e tivement fausse dans le cas de loprateur ||). Par exemple, dans lexpression e (i < max) && (f(14) == 1), la fonction f nest appele que si i < max. e

2.1.5

Oprateurs bit ` bit e a


Traduction ET bit ` bit a OU bit ` bit a XOR bit ` bit a NON bit ` bit a dcalage ` gauche e a scalage ` droite e a Exemple x & y x | y x ^ y ~x x << y x >> y Rsultat (pour chaque position e de bit) 1 si les bits de x et y valent 1 1 si le bit de x et/ou de y vaut 1 1 si le bit de x ou de y vaut 1 1 si le bit de x est 0 dcale chaque bit de x de y positions e vers la gauche dcale chaque bit de x de y positions e vers la droite

Oprateur e & | ^ ~ << >>

Tab. 2.5 Les oprateurs de manipulation des bits e Les oprateurs bits ` bits nop`rent que sur des entiers. Les oprandes sont ine a e e terprtes bits par bits (le bit 1 correspondant ` une valeur vraie, 0 est considr ee a ee comme une valeur fausse). Quelques exemples pour bien comprendre (chaque oprande est fournie sous forme dcimale et binaire) : e e 14 14 14 14 14 14 x 1110 1110 1110 1110 1110 1110 y 9 1001 9 1001 9 1001 2 2 0010 0010 Opration e x & y x | y x ^ y ~x x << y x >> y Rsultat e 8 1000 15 1111 7 0111 1 0001 56 111000 3 11

2.1.6

Oprateurs dacc`s ` la mmoire e e a e

Loprande de loprateur dadresse & doit tre une expression qui dsigne un e e e e objet ou une expression. Ainsi, &x renvoie ladresse mmoire de x et est donc e un pointeur vers x. Plus de dtails dans le chapitre 3 ddi aux pointeurs. e e e 18

Op. & * [ ] . ->

Traduction Adresse de Indirection Elment de tableau e Membre dune structure ou dune union Membre dune structure ou dune union

Exemple &x *p t[i] s.x p->x

Rsultat e ladresse mmoire de x e lobjet (ou la fonction) pointe par p e Lquivalent de *(x+i), llment e ee dindice i dans le tableau t le membre x dans la structure ou lunion s le membre x dans la structure ou lunion pointe par p e

Tab. 2.6 Les oprateurs dacc`s ` la mmoire e e a e Les oprateurs dacc`s aux membres dune structure ou dune union seront plus e e amplement dtaills dans les 4.3 et 4.4. e e

2.1.7

Autres oprateurs e

Il existe un certain nombre dautres oprateurs qui sont lists dans le tableau 2.7. e e On y retrouve notamment lappel de fonction et la conversion de type (qui ne sapplique quaux types scalaires et qui sera aborde plus en dtail dans la e e section 2.4 page 25). Op. () (type) sizeof ? : , Traduction Appel de fonction cast taille en bits Evaluation conditionnelle squencement e Exemple f(x,y) (long)x sizeof(x) x?:y:z x,y Rsultat e Excute la fonction f avec les argue ments x et y la valeur de x avec le type spci e e nombre de bits occup par x e si x est dirent de 0, alors y sinon z e Evalue x puis y

Tab. 2.7 Les autres oprateurs e Loprateur sizeof renvoie le nombre de bits requis pour stocker un objet du e type spci. Le rsultat est une constante de type size_t. e e e Loprateur ?: permet une ecriture plus compacte de lvaluation conditionnelle e e if...then...else. Ainsi lexpression : (x >= 0) ? x : -x renvoie la valeur absolue de de x. Plus de dtails au 2.2.1. e

2.2

Les structures de contrle o

Comme pour la plupart des langages de programmation, le langage C dnit e un certain nombre de structures de contrle (boucles et branchements). o 19

2.2.1

Instruction if...else

Syntaxe : 1. if ( expression ) Instruction1 2. if ( expression ) Instruction1 else Instruction2 La valeur de expression est value et si elle est dirente de 0, Instruction1 est e e e excute, sinon Instruction2 est excute (si elle existe). Exemple : e e e e
if (x > y) else max = x; // Assigne la plus grande valeur entre x et y max = y; // ` la variable max. a

Remarque : lexemple prcdent aurait pu scrire plus simplement (mais moins lisiblee e e ment) : (x > y)?(max = x):(max = y); ou mme max = (x>y)? x : y; e Attention aussi au fait que expression doit tre correctement parenthse. e ee la partie then nest pas introduite par le mot-cl then comme dans certains e langages.

2.2.2

Instruction for

Syntaxe : for (expression1 ; expression2 ; expression3 ) Instruction expression1 et expression3 peuvent tre nimporte quelle expression. expression2 e est une expression de contrle et doit donc tre de type scalaire. Le droulement o e e de cette instruction est illustr par lorganigramme de la gure 2.1. e
Expression1 Expression2 != 0 ?
oui non

fin du for

Instruction Expression3

Fig. 2.1 Organigramme de linstruction for Un exemple pour bien comprendre :


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

2.2.3

Instruction while

Syntaxe : while (expression) Instruction Il sagit dune boucle : tant que expression est vraie, Instruction est excute, e e conformment ` lorganigramme de la gure 2.2. e a Exemple : 20

Expression != 0 ?
oui

non

fin du while

Instruction

Fig. 2.2 Organigramme de linstruction while


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

2.2.4

Instruction do...while

Syntaxe : do instruction while (expression) ; linstruction do...while a un fonctionnement globalement similaire ` celui a dune boucle while mise ` part le fait que le corps de la boucle est excut a e e avant que lexpression de contrle soit value, comme dans lorganigramme de o e e la gure 2.3. Ainsi, le corps de la boucle est toujours excut au moins une e e fois.

Instruction

Expression != 0 ?
oui

non

fin du do

Fig. 2.3 Organigramme de linstruction do Lexemple suivant imprimera Valeur de i : 14 (alors que i 14) :
#include <stdio.h> #define MAX 14 int main() { int i=MAX; do { printf("Valeur de i : %i\n",i); i++; } while (i < MAX); return 0; }

21

2.2.5

Instruction switch

Cette instruction est un if gnralis. Sa syntaxe est la suivante : e e e switch (expression) { case constante1 : liste d instructions1 break ; ... case constanten : liste d instructionsn break ; default : liste d instructions } 1. expression est value, puis le rsulat est compar avec constante1 , constante2 e e e e etc... 2. A la premi`re constantei dont la valeur est gale ` expression, la (ou les4 ) e e a instructions correspondante(s) est excute jusqu` la rencontre e e a liste d dune instruction break. La rencontre dun break termine linstruction swith. 3. sil nexiste aucune constantei dont la valeur soit gale ` celle de expression, e a on excute la liste d instructions de lalternative default si celle-ci existe, e sinon rien nest fait. ATTENTION : expression est ncessairement une valeur enti`re (voir le tae e bleau 1.4 page 12). Cest pourquoi on utilisera souvent dans les exemples qui suivent des enumrations (voir 4.1 page 41). e Compte tenu du nombre dlments optionnels, on peut distinguer 3 types dutiee lisations possibles : 1. pour chaque alternative case, on trouve un break. Exemple :
enum {FALSE, TRUE}; void print_boolean(int bool) { switch (bool) { case FALSE: printf("faux"); break; case TRUE: printf("vrai"); break; default: printf("Erreur interne"); } }

2. on peut avoir une ou plusieurs alternatives ne possdant ni liste d instructions, e ni break. Par exemple, si on veut compter le nombre de caract`res qui e sont des chires et ceux qui ne le sont pas, on peut utiliser le switch suivant :
switch (c) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
4

linstruction break est optionnelle

22

case 9: nb_chiffres++; break; default: nb_non_chiffres++; }

3. enn on peut ne pas avoir de break comme dans lexemple suivant :


enum {POSSIBLE, IMPOSSIBLE}; void print_cas(int cas) { switch (cas) { case IMPOSSIBLE: printf("im"); case POSSIBLE: printf("possible"); } }

Linstruction break peut tre tendue ` dautres cas de branchements (ou de e e a sauts) comme on le verra au 2.2.7. Les paragraphes qui suivent abordent les branchement non conditionnels (par opposition aux branchements conditionnels correspondant aux instructions if...else et switch) qui permettent de naviguer au sein des fonctions du programme.

2.2.6

Instruction goto

Syntaxe : goto etiquette ; La directive goto permet de brancher directement ` nimporte quel endroit de la a e e e fonction courante identie par une tiquette. Une tiquette est un identicateur suivi du signe :. Exemple :
for ( ... ) for ( ... ) if ( erreur ) goto TRAITEMENT_ERREUR; ... TRAITEMENT_ERREUR: // le traitement derreur est effectu ici e printf("Erreur: ...."); ...

Remarque : Encore une fois, linstruction goto et la dclaration de letiquette doivent tre e e contenu au sein de la mme fonction. e Nutiliser cette instruction que lorsque vous ne pouvez pas faire autrement. Le plus souvent, vous pouvez vous en passer alors nen abusez pas ! Pour une saut en dehors dune mme fonction, on pourra utiliser les fonctions e setjmp et longjmp.

2.2.7

Instruction break

Syntaxe : break; On a vu le rle de linstruction break au sein de la directive de branchement o multiple switch. On peut lutiliser plus gnralement au sein de nimporte e e 23

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

En cas de boucles imbriques, break fait sortir de la boucle la plus interne e

2.2.8

Instruction continue

Syntaxe : continue ; Linstruction continue ne peut tre utilise que dans le corps dune boucle e e (instruction for, while ou do). Elle permet de passer directement ` litration a e suivante. Exemple :
for (i = -10; i < 10; i++) { if (i == 0) continue; // passer ` 1 sans excuter la suite a e ... }

2.3

La rcursivit e e

Comme pour beaucoup de langages, la rcursivit est possible en C. Queste e ce que la rcursivit ? Cest la proprit qua une fonction de sauto-appeler, e e ee cest-`-dire de se rappeler elle-mme plusieurs fois. La notion de rcurrence est a e e largement prsente dans le domaine mathmatique, notamment dans la dnie e e tion des suites. Exemple : u0 = 1 un = 2un1 + 1

n1

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

24

* Contrainte: n>=0 ************************************************************/ int ma_suite(unsigned int n) { if (n == 0) return 1; //u_0 = 1 else return 2*ma_suite(n-1) + 1; //appel rcursif e } /*** Fonction main: point dentre de lexcution ***/ e e int main() { unsigned int n; puts("Entrer la valeur de n : "); scanf("%u",&n); printf("valeur de la suite : %u\n",ma_suite(n)); return 0; }

2.4

Les conversions de types

Une conversion de type renvoie la valeur dune expression (de type A) dans un nouveau type B. Les conversions ne peuvent tre eectues que sur les types e e scalaires (cest ` dire les types arithmtiques et les pointeurs). a e Une conversion de type conserve toujours la valeur originale dans la mesure ou le nouveau type est capable de la reprsenter. Ainsi, un nombre ottant peut e tre arrondi dans une conversion de double vers float. Enn, la conversion e peut tre soit implicite, cest ` dire eectue par le compilateur (par exemple, e a e si i est une variable de type float et j de type int, alors lexpression i+j est automatiquement de type float). Elle peut tre galement explicite grce ` loprateur de cast (transtypage en e e a a e franais). Il est de bon go t et tr`s important (pour la clart du programme) c u e e dutiliser loprateur de cast d`s quune conversion de type est requise. Cela e e vite aussi les warnings du compilateur. Exemple : e
void f(float i, float j) { // fonction avec 2 param`tres de type e ... // float } int main() { int i = 14; float j = 2.0; ... f( (float)i, j); // appel de f avec conversion de i vers // le type float }

2.4.1

Les situations de la conversion de type

En C, les situations o` se produisent les conversions sont les suivantes : u 1. une valeur dun certain type est utilise dans un contexte qui en requiert e un autre : passage de param`tre : le param`tre eectif na pas le type du param`tre e e e formel (ctait le cas dans lexemple prcdent) ; e e e 25

aectation : la valeur ` aecter na pas le mme type que la variable ; a e valeur rendue par une fonction : loprande de return na pas le type e indiqu dans la dclaration de la fonction. e e 2. oprateur de conversion : le programmeur demande explicitement une e conversion. 3. un oprateur a des oprandes de types dirents. e e e Dans ce dernier cas, et contrairement aux deux cas prcdents, cest le compie e lateur qui eectue la conversion selon des r`gles soigneusement dnies qui sont e e dtailles dans les deux paragraphes qui suivent. e e

2.4.2

La r`gle de promotion des entiers e

Cette r`gle est applique aux oprandes des oprateurs unaires + et -, ainsi e e e e quaux oprateurs binaires de dcalage << et >> et dans les conversions arithe e mtiques habituelles, abordes au 2.4.3. Elle a pour but damener les petits e e entiers ` la taille des int. a Ainsi, toute oprande de type char, unsigned char, short , unsigned short e ou champs de bits sur les oprateurs prcdent est automatiquement convere e e tie en int (ou en unsigned int si le type int ne permet pas de reprsenter e loprande). e

2.4.3

Les conversions arithmtiques habituelles e

Cette r`gle est applique ` tous les oprateurs arithmtiques binaires, excepts e e a e e e les oprateurs de dcalage << et >> et les seconde et troisi`me oprandes de e e e e loprateur ?:. e La r`gles qui sapplique est la suivante : e Dans le cas doprandes enti`res, on applique dj` la r`gle de promotion des e e ea e entiers, ce qui permet de se dbarrasser des petits entiers. Ensuite, si les ope e randes sont toujours de types dirents, on les convertit dans le type le plus e haut dans la hirarchie expose dans la gure 2.4. e e
long double double float unsigned long long long long unsigned long long unsigned int int

Fig. 2.4 Hirarchie des conversions arithmtiques habituelles e e

26

2.4.4

Les surprises de la conversion de type

En gnral, les conversions fonctionnent de faon satisfaisante vis ` vis du proe e c a grammeur. Cependant, il existe une situation permettant dobtenir des rsule tats surprenants : lors de la comparaison entre des entiers signs et non signs. e e Considrer ainsi le programme suivant : e
#include <stdio.h> int main () { unsigned int i = 0; if (i < -1) printf("Cha chest louche\n"); else printf("On est super content!\n"); return 0; }

Celui ci imprimera le message Cha chest louche, laissant croire que 0 < 1... Lexplication est la suivante : loprateur < poss`de une oprande de type e e e unsigned int (i) et lautre de type int (la constante -1). Dapr`s la gure 2.4, e cette derni`re est convertie en unsigned int et le compilateur ralise la compae e raison non signe entre 0 et 4294967295 (puisque -1 = 0xFFFFFFFF = 4294967295). e CQFD. Pour que tout revienne dans lordre, il sut dutiliser loprateur de conversion e dans le test qui devient : if ( (int) i < -1) Attention car on peut aussi utiliser des unsigned int sans sen rendre compte ! Ainsi :
#include <stdio.h> int main () { if ( sizeof(int) < -1) printf("cha chest louche\n"); else printf("On est super content!\n"); return 0; }

imprimera le message... Cha chest louche. Pourtant, les int nont pas une longueur ngative... Il se trouve simplement qui dans la dclaration de la fonce e tion sizeof (dnie dans stddef.h) renvoie un lment de type size_t, un e ee entier non sign... e Conclusion : 1. Ne jamais mlanger entiers signs et non signs dans les comparaisons e e e (loption -Wall du compilateur le signale normalement) ! Utiliser loprae teur de conversion pour amener les oprandes de la comparaison dans le e mme type. e 2. bien noter que sizeof renvoit une valeur de type entier non sign. e

2.5

Principales fonctions dentres-sorties standard e

Il sagit des fonctions de la librairie standard stdio.h (voir aussi le 9.18 et le chapitre 6) utilises avec les units classiques dentres-sorties, qui sont respece e e tivement le clavier et lcran. Sur certains compilateurs, lappel ` la librairie e a stdio.h par la directive au prprocesseur #include <stdio.h> nest pas ne e cessaire pour utiliser les fonctions prsentes ici, notamment printf et scanf. e e 27

2.5.1

La fonction getchar

La fonction getchar permet la rcupration dun seul caract`re ` partir du e e e a clavier. La syntaxe dutilisation de getchar est la suivante : var=getchar(); Notez que var doit tre de type char. Exemple : e
#include <stdio.h> int main() { char c; printf("Entrer un caract`re:"); e c = getchar(); printf("Le caract`re entr est %c\n",c); e e return 0; }

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

2.5.2

La fonction putchar

La fonction putchar permet lachage dun seul caract`re sur lcran de lore e dinateur. putchar constitue alors la fonction complmentaire de getchar. La e syntaxe dutilisation est la suivante : putchar(var); o` var est de type char. Exemple : u
#include <stdio.h> int main() { char c; printf("Entrer un caract`re:"); e c = getchar(); putchar(c); return 0; }

2.5.3

La fonction puts
puts(ch);

Syntaxe :

Cette fonction ache, sur stdout, la cha de caract`res ch puis positionne ne e le curseur en dbut de ligne suivante. puts retourne EOF en cas derreur. e Exemple :
#include <stdio.h> int main() { char * toto = "on est super content!"; puts(toto); return 0; }

28

2.5.4

La fonction dcriture ` lcran formatte printf e a e e

La fonction printf est une fonction dimpression formate, ce qui signie que e les donnes sont converties selon le format particulier choisi. Sa syntaxe est la e suivante : printf("cha^ne de contr^le", expression1 , . . . , expressionn ); o La cha de contrle contient le texte ` acher et les spcications de format ne o a e correspondant ` chaque expression de la liste. Les spcications de format ont a e pour but dannoncer le format des donnes ` visualiser. Elles sont introduites e a par le caract`re %. Le i-`me format de la cha de contrle sera remplac par e e ne o e la valeur eective de expressioni . Pour tre plus prcis, une spcication de i-`me format est de la forme : e e e e %[flag][largeur][.prcision][modificateur]type e Les lments entre crochet sont facultatifs. Les dirents lments de cette spee e ee e cication sont les suivants : 1. [ag] fournit des options de cadrage (par dfaut, expressioni est justi e e a ` droite) et peut prendre les valeurs suivantes : expressioni sera justi ` gauche (ajout de blancs si ncessaire) ea e + achage du signe (+ ou -) avant la valeur numrique e espace impression dun espace devant un nombre positif, ` la place du a signe Il existe dautres valeurs possibles mais qui sont moins utiles. 2. [largeur] est le nombre minimal de caract`res ` crire (des blancs sont e ae ajouts si ncessaire). Si le texte ` crire est plus long, il est nanmoins e e ae e crit en totalit. En donnant le signe * comme largeur, expressioni foure e nira la largeur (et expressioni+1 la variable ` acher) a Exemple : printf("%*f",14,var). 3. [.prcision] dnit, pour les rels, le nombre de chires apr`s la virgule e e e e (ce nombre doit tre infrieur ` la largeur). Dans le cas de nombres entiers, e e a ce champ indique le nombre minimal de chires dsirs (avec lajout de 0 e e sinon), alors que pour une cha (%s), elle indique la longueur maximale ne imprime (tronque si trop longue). Comme pour la largeur, on peut crire e e e .* pour que expressioni+1 dsigne la prcision ` appliquer. e e a Valeur par dfaut : 6. e 4. [modicateur] modie linterprtation de expressioni et peut valoir h e (pour short), l (long pour les entiers, double pour les rels), ou encore e L (long double pour les rels). e 5. type prcise linterprtation ` donner ` expressioni . Les valeurs possibles e e a a sont dtailles dans la table 2.8 e e printf retourne le nombre de caract`res crits, ou EOF en cas de probl`me. e e e Voici plusieurs exemples didactiques :
printf("|%d|\n",14); printf("|%d|\n",-14); printf("|%+d|\n",14); |14| |-14| |+14|

29

format %d %ld %u %lu %o %lo %x %lx %f %lf %e %le %g %lg %c %s

conversion en int long int unsigned int unsigned long unsigned int unsigned long unsigned int unsigned long double long double double long double double long double unsigned char char*

criture e dcimale signe e e dcimale signe e e dcimale non signe e e dcimale non signe e e octale non signe e octale non signe e hexadcimale non signe e e hexadcimale non signe e e dcimale virgule xe e dcimale virgule xe e dcimale notation exponentielle e dcimale notation exponentielle e dcimale, reprsentation la plus courte parmi %f et %e e e dcimale, reprsentation la plus courte parmi %lf et %le e e caract`re e cha de caract`res ne e

Tab. 2.8 Les dirents formats de la fonction printf e

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

30

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

2.5.5

La fonction de saisie scanf

La fonction scanf permet de rcuprer les donnes saisies au clavier, dans le e e e format spci. Ces donnes sont stockes aux adresses spcies par les argue e e e e e ments de la fonction scanf (on utilise donc loprateur dadressage & pour les e variables scalaires). scanf retourne le nombre de valeurs eectivement lues et mmorises (en ommettant les %*). La syntaxe est la suivante : e e scanf("cha^ne de contr^le", arg1 , . . . , argn ); o La cha de contrle indique le format dans lequel les donnes lues sont converne o e ties. Elle ne contient pas dautres caract`res (notamment pas de \n). Comme e pour printf, les conversions de format sont spcies par un caract`re prcd e e e e e e du signe %. Les formats valides pour la fonction scanf di`rent lg`rement de e e e ceux de la fonction printf. Les donnes ` entrer au clavier doivent tre spares par des blancs ou des e a e e e <RETURN> sauf sil sagit de caract`res. e Pour tre plus prcis, les spcications de format ont la forme suivante : e e e %[*][larg][modif]type * larg modif la valeur sera lue mais ne sera pas stocke e prcise le nombre maximal de caract`res ` lire e e a spcie une taille dirente pour les donnes pointes par largument : e e e e h : short int l : long int (pour les entier) ou double (pour les nombres ottants). L : long double spcie le type de donne lue et comment il faut le lire. e e

type

La plupart des types de scanf sont les mmes que pour printf : e Type c d e,E, f,g,G Description caract`re simple (espace inclu) e entier : nombre optionnellement prcd par un signe e e e Floating point : nombre dcimal prcd eventuellee e e e ment dun signe et suivi ventuellement du caract`re e e e ou E et dun nombre dcimal. Exemple :-732.103 e ou 7.12e4 entier en notation octale. Cha de caract`res (jusqu` la lecture dun espace) ne e a entier non sign e entier hexadecimal Argument requis char * int * float *

o s u x

int * char * unsigned int * int *

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

31

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

Les autres fonctions dentres/sorties (permettant notamment la manipulation e des chiers) seront abordes dans le chapitre 6. e

32

Chapitre 3

Les pointeurs
Toute variable manipule dans un programme est stocke quelque part en me e e moire centrale. Cette mmoire est constitue doctets qui sont identis de e e e mani`re univoque par un numro quon appelle adresse comme lillustre la e e gure 3.1.
Adresse
0x5E00 32 bits

p (un pointeur vers une zone mmoire)

adresses croissantes

0x5E08 0x5E0C 0x5E10 0x5E14 0x5E18 0x5E1C 0x5E20

Fig. 3.1 Illustration de ladressage de la mmoire centrale e Par analogie, on peut voir la mmoire centrale comme une armoire constitue e e de tiroirs numrots. Un numro de tiroir correspond ` une adresse. e e e a Ainsi, lorsquon dclare une variable var de type T, lordinateur rserve un e e espace mmoire (de sizeof(T) octets) pour y stocker les valeurs de var. e Pour retrouver cette variable, il sut donc de conna tre ladresse du premier octet o` elle est stocke (ou, sil sagit dune variable qui recouvre plusieurs u e octets contigus, ladresse du premier de ces octets). Un pointeur est une variable de type adresse. Les pointeurs prsentent de nombreux avantages : e Ils permettent de manipuler de faon simple des donnes de taille importante c e (comme les tableaux, les structures etc...). Ainsi, au lieu de passer en param`tre ` une fonction un lment tr`s grand (en taille), on pourra se contenter e a ee e 33

MEMOIRE CENTRALE

0x5E04

de lui fournir un pointeur vers cet lment... On gagne videmment alors en ee e ecacit dans lexcution du programme. e e Comme on le verra au chapitre 4, les tableaux ne permettent de stocker quun nombre x dlments de mme type. Si les composantes du tableau sont des e ee e pointeurs, il sera possible de stocker des lments de tailles diverses (comme ee des cha nes de caract`res : voir 4.2.5) e Il est possible de crer des structures cha ees (on dit aussi structures autoe n rfres) qui sont utilises pour dnir des listes chaines. De telles listes sont eee e e e beaucoup utilises en programmation (le nombre dlments de cette liste e ee peut voluer dynamiquement, ce qui permet une utilisation plus souple que e celle des tableaux). Cette notion sera aborde en dtail au 4.3.5. e e

3.1

Dclaration dun pointeur e

En C, chaque pointeur est limit ` un type de donne. En eet, mme si la e a e e valeur dun pointeur (une adresse) est toujours un entier (ou ventuellement e un entier long), le type dun pointeur dpend du type de lobjet point. Cette e e distinction est indispensable ` linterprtation (en fait la taille) de la valeur a e pointe. e On dclare un pointeur par linstruction : e type *nom-du-pointeur ; o` type est le type de lobjet point. Exemple : u e int *pi; // pi est un pointeur vers un int short int *psi; // psi est un pointeur vers un short int char *pc; // pc pointeur vers un char A noter aussi lexistence de pointeurs gnriques, cest ` dire capable de pointer e e a vers nimporte quel type dobjet. On utilise pour cela le type void *. Sans un tel type, il ne serait pas possible par exemple dindiquer le type dobjet rendu par les fonctions dallocation de mmoire qui rendent un pointeur vers e lobjet allou, puisque ce type varie dune invocation ` lautre de la fonction. e a Par exemple, la fonction malloc de la biblioth`que standard est dnie de la e e mani`re suivante : void *malloc(size_t size); e

3.2

Oprateurs de manipulation des pointeurs e

Lors du travail avec des pointeurs, nous avons besoin : dun oprateur adresse de & pour obtenir ladresse dune variable. e dun oprateur contenu de * pour accder au contenu dune adresse. e e

3.2.1

Loprateur adresse de & e

Loprateur & permet daccder ` ladresse dune variable. La syntaxe est la e e a suivante : 34

&nom-variable Cette adresse peut alors tre utilise pour initialiser la valeur dun pointeur. e e Dans lexemple suivant, on dnit un pointeur p qui pointe vers un entier i : e int * p; //tape (1): pointeur vers un entier non initialis e e int i = 14; //tape (2): variable enti`re initialise a 14 e e e p=&i; //tape (3): p pointe vers i e Les direntes tapes de ce scnario sont illustres dans la gure 3.2. e e e e
(1)
int * p;

(2)
int i = 14;

(3)
p = &i;

Ox1A30

Ox1A30

Ox1A30

Ox352C

(dclaration dun pointeur p vers un entier)


Ox352C

14

Ox352C

14

(dclaration et initialisation dune variable entire i)

(affectation de ladresse de i p)

Fig. 3.2 Illustration mmoire dune aectation par loprateur & e e Loprateur & peut seulement tre appliqu ` des objets qui se trouvent dans e e ea la mmoire interne, cest ` dire ` des variables et des tableaux. Il ne peut pas e a a tre appliqu ` des constantes ou des expressions. e ea

3.2.2

Loprateur contenu de : * e

Loprateur unaire dindirection * permet daccder directement ` la valeur de e e a lobjet point (on dit quon drfrence un pointeur). La syntaxe est la suivante : e eee *nom-pointeur Ainsi, si p est un pointeur vers un entier i, *p dsigne la valeur de i. Exemple : e
int main() { int i = 14; int *p; p = &i; //p contient ladresse de i (0x352C) printf("*p = %d \n",*p); //affiche "*p = 14" }

Pour rsumer, apr`s les instructions prcdentes : e e e e i dsigne le contenu de i (soit 14) e &i dsigne ladresse de i (soit 0x352C) e p dsigne ladresse de i (soit 0x352C) e *p dsigne le contenu de i (soit 14) e En outre : 35

&p dsigne ladresse de p (soit 0x1A30) e *i est en gnral illgal (puisque i nest pas un pointeur mais il peut arriver e e e que la valeur *i ait un sens) Petites devinettes (Merci Bernard Cassagne [Cas98]) : Soient i et j deux pointeurs vers des entiers, 1. A quoi correspond lexpression *i**j ? 2. Et *i/*j ?

3.3

Initialisation dun pointeur

Par dfaut, lorsque lon dclare un pointeur p sur un objet de type T, on ne e e sait pas sur quoi il pointe. En eet, la case mmoire quil occupe contient une e certaine valeur qui risque de le faire pointer vers une zone hasardeuse de la mmoire. e Comme toute variable, un pointeur doit tre initialis ! e e Cette initialisation peut seectuer de trois faons : c 1. aectation ` ladresse dune autre variable de p. Si la variable est un poina teur, on peut faire laectation directement, sinon on doit utiliser lope rateur &. Exemple : int *p1, *p2;//dclaration de 2 pointeurs vers des entiers e int i = 14; //supposons que i se trouve ` ladresse 0x352C a p1 = &i; //affectation ` ladresse de i de p1 , soit 0x352C a p2 = p1; //affectation de p2 ` p1: a //p2 contient aussi ladresse de i 2. aectation de p ` la valeur NULL : on peut dire quun pointeur ne pointe a sur rien en lui aectant la valeur NULL (cette valeur est dnie dans le e chier stddef.h). Exemple : int * p = NULL; e e 3. aectation directe de *p (la zone mmoire pointe par p). Pour cela, il faut dabord rserver ` *p un espace-mmoire de taille adquate (celui du type e a e e point par p, soit sizeof(T) octets). Ladresse de cet espace-mmoire sera e e la valeur de p. Cette opration consistant ` rserver un espace-mmoire e a e e pour stocker lobjet point sappelle une allocation dynamique et sera e aborde plus en dtail au 3.5. e e

Pour bien montrer lintrt de linitialisation de tout pointeur, reprenons lexemple ee prcdent dans lequel on remplace laectation p2 = p1 par *p2 = *p1 : e e int * p1, *p2; int i = 14; p1 = &i; *p2 = *p1; Que va t il se passer ? 36

p1 est bien initialis et pointe sur i (*p1=14) e mais p2 na pas t initialis : *p2 dsigne une adresse mmoire a priori inee e e e connue. Linstruction *p2 = *p1 force lcriture de la valeur *p1=14 dans e la case mmoire pointe par p2 ce qui pourrait avoir des consquences de e e e sastreuses ! Cette ecriture est en gnral sanctionn par le message derreur e e e Segmentation fault ` lexcution. a e Il faut quand mme noter que certain compilateurs ont la bonne ide dinitialiser e e automatiquement ` la valeur NULL un pointeur non aect. Mais cela ne doit a e pas empcher de prendre la bonne habitude dinitialiser tout pointeur. e

3.4

Arithmtique des pointeurs e

La valeur dun pointeur tant un entier, on peut lui appliquer un certain nombre e doprateurs arithmtiques classiques. Les seules oprations arithmtiques vae e e e lides sur les pointeurs sont : laddition dun entier ` un pointeur p + i a Le rsultat est un pointeur de mme type que le pointeur de dpart. e e e la soustraction dun entier ` un pointeur p i a Le rsultat est un pointeur de mme type que le pointeur de dpart. e e e la dirence de deux pointeurs p1 p2 e Il faut absolument que les deux pointeurs pointent vers des objets de mme e type T. Le rsultat est un entier dont la valeur est gale ` (p1 - p2)/sizeof(T). e e a e Attention : la somme de deux pointeurs nest pas autorise ! Ainsi, soit i un entier et p un pointeur sur un objet de type T (donc dclar e e par linstruction : T *p ;). Alors p+i (respectivement p-i) dsigne un poine teur sur un objet de type T. Sa valeur est gale ` celle de p incrmente e a e e (respectivement dcrmente) de i*sizeof(T). Exemple : e e e
#include <stdio.h> int main() { int i = 2; int *p1, *p2; p1 = &i; p2 = p1 + 1; printf("Adresse p1 = Ox%lx \t Adresse p2 = 0x%lx\n", (unsigned long)p1, (unsigned long)p2); printf("Diffrence des adresses: %lu \t sizeof(int)=%u\n", e (unsigned long)p2-(unsigned long)p1, sizeof(int)); printf("MAIS p2-p1 = %i !\n",p2-p1); return 0; }

Ce programme renverra : Adresse p1 = Oxbffffb24 Diffrence des adresses: 4 e MAIS p2-p1 = 1 ! Adresse p2 = 0xbffffb28 sizeof(int)=4

37

On peut faire la mme exprience avec le type double (au lieu du type int). e e On obtiendrait alors : Adresse p1 = Oxbffffb20 Diffrence des adresses: 8 e MAIS p2-p1 = 1 ! Adresse p2 = 0xbffffb28 sizeof(double)=8

A noter quon peut galement utiliser les oprateurs ++ et -- avec des poine e teurs. En gnral, on les utilise pour raliser des parcours de tableaux et plus e e e particuli`rement dans les cha e nes de caract`res. Exemple (comme on le verra e au 4.2.1, toute cha se termine par un caract`re null, le caract`re \0) : ne e e
#include <stdio.h> int main() { char * mess = "On est super content!"; char *p; for (p = &mess[0]; *p != \0; p++) { printf("Adresse: %ld | Contenu: %c\n",(long)p,*p); } // Autre mthode classique, avec while e p = mess; // quivalent de p = &mess[0] dans ce cas e puts("========================================"); while (*p != \0) { printf("Adresse: %ld | Contenu: %c\n",(long)p,*p); p++; } return 0; }

Les cha nes de caract`res seront plus amplement dtailles au 4.2.1 page 43. e e e A noter que les oprateurs de comparaison sont galement applicables aux poine e teurs1 .

3.5

Allocation dynamique de mmoire e

Nous avons vu que lutilisation de pointeurs permet de mmoriser conomiquee e ment des donnes de direntes grandeurs (puisquon se contente de mmoriser e e e ladresse de ces donnes). Pour permettre une utilisation ecace de la mmoire, e e il est primordial de disposer de moyens pour rserver et librer de la mmoire e e e dynamiquement au fur et ` mesure de lexcution. Cest ce quon appelle un a e allocation dynamique de la mmoire. Les fonctions pour la gestion dynamique e de la mmoire sont dclares dans le chier stdlib.h (` inclure). e e e a Lopration consistant ` rserver un espace-mmoire est ralis par la fonction e a e e e e malloc. Sa syntaxe est : malloc(nb octets) Cette fonction retourne un pointeur de type char * pointant vers une zone mmoire de nb octets octets. Pour initialiser des pointeurs vers des objets qui e
1

` condition bien s r de comparer des pointeurs qui pointent vers des objets de mme type. a u e

38

ne sont pas de type char, il faut convertir le type de la sortie de la fonction malloc ` laide dun cast (dj` tudi au 2.4). a eae e A noter enn quen pratique, on utilise la fonction sizeof() pour dterminer e e la valeur nb octets. Ainsi, pour initialiser un pointeur vers un entier, on crit :
#include <stdlib.h> int main() { int *p; p = (int*)malloc(sizeof(int)); // allocation dynamique *p = 14; }

Il est primordial de bien comprendre quavant lallocation dynamique (et plus gnralement avant toute initialisation de p), *p na aucun sens ! e e En particulier, toute manipulation de la variable *p gnrerait en gnral une e e e e violation mmoire, dtectable ` lexcution par le message derreur Segmentation e e a e fault. La fonction malloc permet galement dallouer un espace pour plusieurs objets e contigus en mmoire. On peut crire par exemple e e
#include <stdlib.h> #include <stdio.h> int main() { int *p; p = (int*)malloc(2 * sizeof(int)); //allocation pour 2 int *p = 14; *(p + 1) = 10; printf("p = Ox%lx \t *p = %i \t p+1 = 0x%lx \t *(p+1)=%i\n", (unsigned long)p, *p, (unsigned long)(p+1), *(p+1)); return 0; }

Lappel ` la fonction malloc a ainsi permis de rserver 8 octets en mmoire a e e (qui permettent de stocker 2 objets de type int) et daecter ` p ladresse de a cette zone mmoire. Le programme ache : e p = Ox8049718 *p = 14 p+1 = 0x804971c *(p+1)=10. La fonction calloc a le mme rle que la fonction malloc mais elle permet e o a e de rserver nb-objets objets de nb octets octets et de les initialiser ` zro. Sa e syntaxe est : calloc(nb-objets,nb octets) Ainsi, si p est de type int*, linstruction p = (int*)calloc(N,sizeof(int)); est smantiquement quivalente ` e e a p = (int*)malloc(N * sizeof(int)); for (i = 0; i < N; i++) *(p + i) = 0; Lemploi de calloc est simplement plus pratique et plus rapide. 39

3.6

Libration dynamique avec la fonction free e

Lorsque lon na plus besoin de lespace-mmoire allou dynamiquement par e e malloc ou calloc (cest-`-dire quand on nutilise plus le pointeur p initialis a e par ces fontions), il faut librer ce bloc de mmoire. Ceci se fait ` laide de e e a linstruction free qui a pour syntaxe : free(nom-du-pointeur ); Cette instruction lib`re le bloc de mmoire dsign par nom-du-pointeur mais e e e e na pas deet si le pointeur a la valeur NULL. A toute instruction de type malloc ou calloc doit tre associe une instruction e e de type free. Attention : La fonction free peut aboutir ` un dsastre si on essaie de librer de la a e e mmoire qui na pas t alloue par malloc ou calloc. e ee e free ne change pas le contenu du pointeur. Si la mmoire nest pas libre ` laide free, alors elle lest automatiquement e ee a a ` la n du programme. Cependant, cela ne doit pas dispenser de lutilisation de cette fonction. Lexemple des listes cha ees (dtaill au 4.3.5) est tout n e e a ` fait adapt : si la mmoire nest pas libre ` chaque suppression dun e e ee a lment de la liste, on aboutira rapidement ` une violation de mmoire et au ee a e traditionnel message derreur Segmentation fault.

40

Chapitre 4

Les types drivs e e


A partir des types prdnis en C (caract`res, entiers, ottants), on peut crer e e e e de nouveaux types, appels types drivs, qui permettent de reprsenter des e e e e ensembles de donnes organises. e e

4.1

Les numrations e e

Les numrations permettent de dnir un type pour des variables qui ne sont e e e aectes qua un nombre ni de valeurs. e Un objet de type numration est dni par le mot-clef enum et un identicateur e e e de mod`le, suivi de la liste des valeurs que peut prendre cet objet : e enum modele {constante1 , constante2 ,. . . ,constanten } ; En ralit, les objets de type enum sont reprsents comme des int. Les valeurs e e e e possibles constante1 , constante2 ,. . . ,constanten sont codes par des entiers de e 0 ` n-1. a Dans lexemple suivant, le type enum boolean associe lentier 0 ` la valeur a FALSE et lentier 1 ` la valeur TRUE. a
#include <stdio.h> enum boolean {FALSE, TRUE}; //dfinition de lenumration boolean e e int main () { enum boolean b1 = TRUE; //declaration printf("b = %d\n",b1); return 0; }

On peut modier le codage par dfaut des valeurs de la liste lors de la dclaration e e du type numr, par exemple : e ee enum boolean {FALSE = 12, TRUE = 23};

4.2

Les tableaux

Un tableau est un ensemble ni dlments de mme type, stocks en mmoire ee e e e a ` des adresses contigus. e 41

La dclaration dun tableau a une dimension se fait de la faon suivante : e ` c type nom-du-tableau[nombre-lments]; e e o` nombre-lments est une expression constante enti`re positive correspondant u ee e au nombre maximal dlment dans le tableau. On lappelle aussi la dimension ee (ou taille) du tableau. Chaque lment du tableau est une composante de celuiee ci. Par exemple, la dclaration int tab[10]; indique que tab est un tableau de e 10 lments de type int. En faisant le rapprochement avec les mathmatiques, ee e on dit encore que t est un vecteur de dimension 10. Cette dclaration alloue e donc en mmoire pour lobjet tab un espace de 10*4 octets conscutifs. e e Pour plus de clart, il est recommand de donner un nom ` la constante nombree e a lments par une directive au prprocesseur (voir chapitre 7), par exemple : ee e #define TAILLE 20 int t[TAILLE]; //noter labsence de ";" //t tableau de TAILLE int

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

Un tableau correspond en fait ` un pointeur vers le premier lment du tableau. a ee Ce pointeur est constant, ce qui implique en particulier quaucune opration gloe bale nest autorise sur un tableau. Notamment, un tableau ne peut pas gurer e a ` gauche dun oprateur daectation : on ne peut pas crire tab1 = tab2;. e e Il faut eectuer laectation pour chacun des lments du tableau. ee

4.2.1

Initialisation dun tableau

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

42

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

La gure 4.1 montre ce quil se passe en pratique au niveau de la mmoire e lors de linitialisation du tableau t. En supposant quune variable du type int occupe 4 octets (cest ` dire : sizeof(int)=4), alors le tableau t rservera a e N 4 = 5 4 = 20 octets en mmoire. e
int t[5]={14,27,3,18,81}; Adresse
0x5E00 32 bits

t adresses croissantes
0x5E04 0x5E08 0x5E0C 0x5E10 0x5E14 0x5E18 0x5E1C 0x5E20

(pointeur vers le premier lment du tableau)

14 27 3 18 81

t[0] t[1] t[2] t[3] t[4] t[i]=*(t+i)

Fig. 4.1 Expression dun tableau en mmoire physique e On peut donner moins de valeurs dinitialisations que le tableau ne comporte dlments. Dans ce cas, les premiers lments seront initialiss avec les valeurs ee ee e indiques, tandis que les autres seront initalises ` zro. Exemple : e e a e #define N 10 int t[N] = {14,27}; Les lments dindice 0 et 1 seront initialiss respectivement avec les valeurs 14 ee e et 27, les autres lments (dindices 2 ` 9) seront initialiss ` zro. ee a e a e Rservation automatique e Si la dimension nest pas indique explicitement lors de linitialisation, alors le e compilateur rserve automatiquement le nombre doctets ncessaires. Exemple : e e int A[] = {1, 2, 3, 4, 5}; //rservation de 5*sizeof(int) octets e float B[] = {-1.05, 3.3, 87e-5, -1.3E4}; //rservation de e //4*sizeof(float) octets Cas particulier des tableaux de caract`res e La reprsentation interne dune cha de caract`res est termine par le symbole e ne e e nul \0. Ainsi, pour un texte de n caract`res, il faut prvoir n + 1 octets. e e 43

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

e
Adresses:
1E04

x
1E05

e
1E06

m
1E07

p
1E08

l
1E09

e
1E0A

\0
1E0B

on peut dclarer une taille suprieure ` celle de la cha littrale : e e a ne e char ch[100]="exemple"; enn, on peut ne pas indiquer la taille et le compilateur comptera le nombre de caract`res de la cha littrale pour dimensionner correctement le tableau e ne e (sans oublier le caract`re null \0). Exemple : e char ch[]="ch aura 22 caract`res"; e Il est galement possible de donner une taille gale au nombre de caract`res e e e de la cha Dans le cas, le compilateur comprend quil ne faut pas rajouter ne. le null en n de cha Exemple : char ville[10]="luxembourg"; ne. Mais dans ce cas, attention aux surprises ! Considrer lexemple suivant : e
#include <stdio.h> int main() { char t1[10]="luxembourg"; //sans \0 char t2[]="luxembourg"; //avec \0 printf("t1 (de taille %i)=%s\n",sizeof(t1)/sizeof(char),t1); printf("t2 (de taille %i)=%s\n",sizeof(t2)/sizeof(char),t2); return 0; }

Ce programme renverra : t1 (de taille 10)=luxembourgP @d@ ^XA^-@ u A a u u t2 (de taille 11)=luxembourg En eet, pour t1, toute la zone mmoire est ache jusqu` ce que le carat`re e e a e nul soit rencontr. e

4.2.2

Tableaux multidimensionnels

En C, un tableau multidimensionnel est considr comme un tableau dont les ee lments sont eux-mme des tableaux. Un tableau ` deux dimensions se dclare ee e a e donc de la mani`re suivante : e int mat[10][20]; En faisant le rapprochement avec les mathmatiques, on peut dire que mat est e une matrice de 10 lignes et de 20 colonnes. Les mmes considrations que celles e e que nous avons dvelopp sur les tableaux unidimensionnels sappliquent : e e 1. A la dclaration, le compilateur allouera une zone mmoire permettant de e e stocker de mani`re contig e 10 tableaux de 20 entiers, soit 200 entiers ; e u 44

2. toute rfrence ultrieure a mat sera convertie en ladresse de sa premi`re ee e ` e ligne, avec le type pointeur vers un tableau de 20 int. On acc`de ` un lment du tableau par lexpression mat[i][j]. e a ee Pour initialiser un tableau ` plusieurs dimensions ` la compilation, on utilise a a une liste dont chaque lment est une liste de constantes : ee
#include <stdio.h> #define L 3 //nombre de lignes #define C 2 //nombre de colonnes short tab[L][C] = {{1, 2}, {14, 15}, {100, 200}}; int main() { int i, j; for (i = 0 ; i < L; i++) { for (j = 0; j < C; j++) printf("tab[%d][%d]=%d\n",i,j,tab[i][j]); } return 0; }

On vient de dnir la matrice e 1 2 tab = 14 15 100 200

La gure suivante montre le contenu de la mmoire ` la suite de cette initialie a sation.


tab

1
Adresses:
1E04 1E08

2
1E0C

14
1E10

15
1E14

100
1E18

200
1E1C 1E20

On comprend bien avec cette gure que si lon souhaite utiliser la rservation e automatique, il faudra quand mme spcier toutes les dimensions sauf la pree e mi`re (le nombre de lignes dans le cas dun tableau bi-dimentionnel. e short int mat[][3] = {{1, 0, 1}, {0, 1, 0}}; rservation de 2*3*2 = 12 octets et e mat = 1 0 1 0 1 0

4.2.3

Passage de tableau en param`tre e

Puisquun identicateur de type tableau correspond ` ladresse du premier la ee ment du tableau (voir gure 4.1), cest cette adresse qui est passe en param`tre e e formel. Considrons par exemple une fonction print_tab qui ache le contenu e dun tableau dentiers : 45

#include <stdio.h> /* Affiche le contenu du tableau dentiers tab ayant nb_elem composantes */ void print_tab(int tab[], int nb_elem) { int i; //compteur for (i=0; i < nb_elem; i++) printf("tab[%i]=%i\n",i,tab[i]); } #define TAILLE 4 int main() { int t[TAILLE] = {1, 2, 3, 4}; print_tab(t, TAILLE); return 0; }

Dans le prototype dune fonction, lcriture int tab[] est strictement quivae e lente ` int * tab (mais on prferera la premi`re criture pour des raisons de a e e e lisibilit). e Modication des lments dun tableau pass en param`tre ee e e Puisque nalement on passe un pointeur en param`tre, on peut modier au e besoin le contenu du tableau. On peut crire par exemple : e
#include <stdio.h> void print_tab(int tab[], int nb_elem) {...} /* incrmente chaque composantes d tableau */ e void incr_tab(int tab[], int nb_elem) { int i; for (i=0; i < nb_elem; i++) tab[i]++; } #define TAILLE 4 int main() { int t[TAILLE] = {1, 2, 3, 4}; incr_tab(t, TAILLE); print_tab(t, TAILLE); return 0; }

Interdire la modication des lments dun tableau pass en paraee e m`tre e On utilise pour cela le mot-cl const qui spcie que la variable associe ne e e e peut pas tre modie1 . Exemple : e e const int i = 14; //cette variable ne pourra plus etre modifie ^ e On voit tout de suite lintrt de const pour les param`tres de fonction. Ainsi, ee e dans la procdure print_tab, on peut exprimer le fait que cette procdure ne e e doit pas modier le contenu du tableau pass en param`tre. Le prototype de la e e procdure deviendra pour cela : e void print_tab(const int tab[], int nb_elem);
En pratique, la mmoire nest pas protge en criture et seule lutilisation directe de la e e e e variable constante empche lcriture. Il est toujours possible dcrire ` ladresse de la variable e e e a par des moyens dtourns. e e
1

46

4.2.4

Relation entre tableaux et pointeurs

Pointeurs et tableaux ` une dimension a On rappelle que tout tableau en C est en fait un pointeur constant. Ainsi, la dclaration int tab[10]; dnit un tableau de 10 valeurs enti`res dont e e e les indices varient entre les valeurs 0 et 9 et tab est un pointeur constant (non modiable) dont la valeur est ladresse du premier lment du tableau. ee Autrement dit, tab a pour valeur &tab[0]. On peut donc utiliser un pointeur initialis ` tab pour parcourir les lments du tableau, comme dans lexemple ea ee suivant :
#define N 5 int tab[5] = {1, 2, 6, 0, 7}; int main() { int i; int *p; p = tab; for (i = 0; i < N; i++) { printf(" %d \n",*p); p++; } return 0; }

Comme on acc`de ` llment dindice i du tableau tab grce ` loprateur e a ee a a e dindexation [] par lexpression tab[i], on peut dduire la relation entre cet e oprateur dindexation et loprateur * : e e tab[i] = *(tab + i) Pour rsumer, on a les quivalences suivantes : e e tab + 1 &(tab[1]) (adresse du 2i`me lment) e ee *(tab + 1) tab[1] (valeur du 2i`me lment) e ee *tab tab[0] (valeur du 1er lment) ee *(tab + k) tab[k] (valeur du (k+1)`me lment) e ee tab + k &(tab[k]) (adresse du (k+1)`me lment) e ee Pointeurs et tableaux se manipulent donc exactement de mme mani`re. Attene e tion cependant puisquaucun contrle nest eectu pour sassurer que llment o e ee du tableau adress existe eectivement. En outre, la manipulation de tableaux e poss`de certains inconvnients par rapport ` la manipulation des pointeurs d s e e a u au fait quun tableau est un pointeur constant : On ne peut pas crer de tableaux dont la taille est une variable du proe gramme2 ; on ne peut pas crer de tableaux bidimensionnels dont les lignes nont pas e toutes le mme nombre dlments. e ee Ces oprations deviennent possibles d`s que lon manipule des pointeurs allous e e e dynamiquement. Ainsi, pour crer un tableau dentiers ` n lments o` n est e a ee u une variable du programme, on crit : e
#include <stdlib.h> int main() {
2

En fait, on peut le faire depuis la norme C99 mais on ignorera cette possibilit ici. e

47

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

Si on veut en plus que tous les lments du tableau tab soient initialiss ` 0, on ee e a remplace lallocation dynamique avec malloc par : tab = (int*)calloc(n, sizeof(int)); On pourra aussi utiliser la fonction memset. Les lments de tab sont manipuls avec loprateur dindexation [], exacteee e e ment comme pour les tableaux. Pour conclure, on retiendra que les deux principales dirence entre un tableau e et un pointeur sont les suivantes : un pointeur doit toujours tre initialis, soit par une allocation dynamique, e e soit par aectation ` une expression de type adresse, par exemple p = &i ; a un tableau est un pointeur constant ne peut donc pas gurer a gauche dun ` oprateur daectation. En particulier, un tableau ne pas tre utilis direce e e tement dans une expression arithmtique (on ne peut pas crire tab++ ; par e e exemple). Pointeurs et tableaux ` plusieurs dimensions a Un tableau ` deux dimensions peut tre vu de deux faons : a e c soit comme un pointeur sur une zone mmoire de taille le produit des deux e dimensions, comme cest le cas dans lcriture : e int tab[L][C]; Avec cette criture, tab a une valeur constante gale ` ladresse du premier e e a lment du tableau, cest ` dire &tab[0][0]. ee a soit comme un tableau de pointeurs. Chaque lment du tableau est alors luiee mme un pointeur sur un tableau. On dclare un pointeur qui pointe sur un e e objet de type type * (deux dimensions) de la mme mani`re quun pointeur, e e cest-`-dire par la dclaration : a e type **nom-du-pointeur ; De mme un pointeur qui pointe sur un objet de type type ** (quivalent ` e e a un tableau ` 3 dimensions) se dclare par a e type ***nom-du-pointeur ; Lexemple suivant illustre la dclaration dun tableau ` deux dimensions (une e a matrice de k lignes et n colonnes ` coecients entiers) sous forme dun tableau a de pointeurs :
int main() { int k=14, n=5; int **tab; // pointeur vers la matrice tab = (int**)malloc(k * sizeof(int*)); for (i = 0; i < k; i++) tab[i] = (int*)malloc(n * sizeof(int));

48

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

La premi`re allocation dynamique rserve pour lobjet point par tab lespacee e e mmoire correspondant ` k pointeurs sur des entiers. Ces k pointeurs correse a pondent aux lignes de la matrice. Les allocations dynamiques suivantes rservent e pour chaque pointeur tab[i] lespace-mmoire ncessaire pour stocker n entiers. e e Si on dsire en plus que tous les lments du tableau soient initialiss ` 0, il e ee e a sut dutiliser la fonction calloc au lieu de malloc (voir 3.5 page 38) Lun des avantages des pointeurs de pointeurs sur les tableaux multi-dimensionns e est que cela permet de choisir par exemple des tailles direntes pour chacune e des lignes tab[i].

4.2.5

Cas des tableaux de cha nes de caract`res e

On peut initialiser un tableau de ce type par : char * jour[] = {"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"}; Lallure du tableau jour est illustr dans la gure 4.2. e
jour

l m m j v s d

u a e e e a i

n r r u n m m

d d c d d e a

i i r i r d n

\0 \0 e \0 e i c d \0 h e \0 i \0 d i \0

Fig. 4.2 Illustration de la dclaration dun tableau de cha e nes de caract`res e A noter que cette criture directe nest possible quavec les char. Il est impose sible dcrire par exemple : e int * tab[] = {{1}, {1,2}, {1,2,3}}; (on a ici un tableau de pointeurs vers des tableaux dentiers de taille direntes). e Une boucle dimpression des valeurs du tableau jour pourra scrire : e #define NB_JOUR 7 int i; for (i=0 ; i < NB_JOUR; i++) printf("%s\n",jour[i]); 49

4.2.6

Gestion des arguments de la ligne de commande

Les tableaux de pointeurs vers des cha nes de caract`res sont une structure de e donne tr`s importante puisquelle est utilise dans la transmission de parae e e m`tres lors de lexcutution dun programme. e e Lorsquun utilisateur lance lexcution du programme prog avec les param`tres e e param_1, param_2, . . . , param_n, linterprteur collecte tous ces mots sous forme e de cha de caract`res, cre un tableau de pointeurs vers ces cha ne e e nes et lance la procdure main en lui passant deux param`tres : e e un entier contenant la taille du tableau (appel classiquement argc) ; e le tableau des pointeurs vers les cha nes (traditionnellement argv). Pour que le programmeur puisse exploiter ces lments, la fonction main doit ee tre dnie de la faon suivante : e e c int main(int argc, char * argv[]) {...} Voici un exemple dutilisation de ces param`tres : e
#include <stdio.h> int main(int argc, char * argv[]) { int i; printf("Nom du programme: %s\n", argv[0]); for (i=1 ; i < argc ; i++) printf("Param`tre %i: %s\n",i,argv[i]); e return 0; }

En compilant ce programme par la commande gcc -Wall -g3 toto.c, lappel ./a.out nif naf nouf renvoit : Nom du programme: ./a.out Param`tre 1: nif e Param`tre 2: naf e Param`tre 3: nouf e La librairie getopt.h permet galement de grer plus simplement les arguments e e de la ligne de commande. Supposons par exemple vouloir compiler un programme prog au format dappel suivant (les lments entre crochets sont optionee nels) : prog [-i val_i] [-f val_f] [-s string] [-k key] [-h] file sachant que : 1. loption -i permet de spcier la valeur de la variable val_i, de type int e (14 par dfaut) ; e 2. loption -f permet de spcier la valeur de la variable val_f, de type e float (1.5 par dfaut). e 3. loption -s permet de spcier la valeur de la variable string, une cha e ne de caract`res possdant au plus 256 caract`res (On est super content ! e e e par dfaut). e 4. loption -k permet de spcier au format hexadcimal la valeur de la e e variable key, de type unsigned long (0x90ABCDEF par dfaut). e 50

5. loption -h ache laide (auteur, but du programme et usage) et sort du programme. 6. file est un param`tre obligatoire du programme qui spcie la valeur de e e la cha de caract`res input_file. ne e Pour grer ces options, on peut soit adapter lexemple de code prcdent et e e e traiter individuellement les lments du tableau argv ainsi que les cas derreurs, ee soit utiliser la librairie <getopt.h>3 qui permet de grer facilement les options e de la ligne de commande. Cet exemple est trait avec <getopt.h> en annexe A.1 page 106. e

4.3

Les structures

e Une structure est une suite nie dobjets de types dirents. Ce mcanisme pere met de grouper un certain nombre de variables de types dirents au sein dune e mme entit. Contrairement aux tableaux, les dirents lments dune struce e e ee ture noccupent pas ncessairement des zones contigus en mmoire. Chaque e e e lment de la structure, appel membre ou champ, est dsign par un identiee e e e cateur. Lutilisation pratique dune structure se droule de la faon suivante : e c 1. On commence par dclarer la structure elle-mme. Le mod`le gnral de e e e e e cette dclaration est le suivant : e struct nom structure { type_1 membre1 ; type_2 membre2 ; ... type_n membren ; }; 2. Pour dclarer un objet de type structure correspondant au mod`le prce e e e dent, on utilise la syntaxe : struct nom structure identicateur-objet ; Ou bien, si le mod`le na pas encore t dclar au pralable : e ee e e e struct nom structure { type_1 membre1 ; type_2 membre2 ; ... type_n membren ; } identicateur-objet ;

3. On acc`de aux dirents membres dune structure grce ` loprateur e e a a e membre de structure, not .. Le i-`me membre de objet est dsign par e e e e lexpression : identicateur-objet.membrei On peut eectuer sur le i-`me membre de la structure toutes les oprations e e valides sur des donnes de type type_i. e
3 informations : man 3 getopt ou plus simplement le tutorial disponible ` ladresse : a http://www.stillhq.com/extracted/howto-getopt/output.ps

51

Ainsi, le programme suivant dnit la structure complexe, compose de deux e e champs de type double et calcule la norme dun nombre complexe.
#include <math.h> struct complexe { double reelle; //partie relle e double imaginaire; //partie imaginaire }; // ne pas oublier le ";" int main() { struct complexe z; //dclaration dun objet de type struct complexe e double norme; ... norme = sqrt(z.reelle * z.reelle + z.imaginaire * z.imaginaire); printf("norme de (%f + i %f) = %f \n",z.reelle,z.imaginaire,norme); }

4.3.1

Initialisation et aectation dune structure

Les r`gles dinitialisation dune structure lors de sa dclaration sont les mmes e e e que pour les tableaux. On crit par exemple : e struct complexe i = {0. , 1.}; A noter qua la dirence des tableaux, on peut appliquer loprateur daece e tation aux structures. Dans le contexte prcdent, on peut crire : e e e
int main() { struct complexe z1, z2; ... z2 = z1; }

4.3.2

Comparaison de structures

Aucune comparaison nest possible sur les structures (en particulier, on ne peut appliquer les oprateurs == ou !=). e

4.3.3

Tableau de structures

On dclare un tableau dont les composantes sont des structures de la mme e e faon quon dclarerait un tableau dlments de type simple. Exemple : c e ee struct personne { char nom[20]; char prenom[20]; }; ... struct personne t[100]; //tableau de 100 personnes Pour rfrencer le nom de la personne qui a lindex i, on crira : ee e t[i].nom 52

4.3.4

Pointeur vers une structure

En reprenant la structure personne prcdentes, on dclarera une variable de e e e type pointeur vers cette structure de la mani`re suivante : e struct personne *p; On peut aecter ` ce pointeur des adresses sur des struct personne. Exemple : a struct personne {...}; int main() { struct personne pers; // pers = variable de type struct personne struct personne * p; // p = pointeur vers une struct personne p = &pers; // p pointe maintenant vers pers } Pour acc`der aux membres de la structure pointe : e e 4; il faudrait crire normalement (*p).nom e mais on utilise en pratique lcriture simplie p->nom. e e

4.3.5

Structures auto-rfres ee e

Il sagit de cas particulier de structures dont un des membres pointe vers une structure du mme type. Cette reprsentation permet en particulier de e e construire des listes chanes. e En eet, il est possible de reprsenter une liste dlments de mme type par un e ee e tableau (ou un pointeur). Toutefois, cette reprsentation, dite contigu, impose e e que la taille maximale de la liste soit connue a priori (on a besoin du nombre dlments du tableau lors de lallocation dynamique). Pour rsoudre ce proee e bl`me, on utilise une reprsentation cha ee : llment de base de la cha e e n ee ne est une structure appele cellule qui contient la valeur dun lment de la liste e ee et un pointeur sur llment suivant. Le dernier lment pointe sur le pointeur ee ee NULL (dni dans stddef.h). La liste est alors dnie comme un pointeur sur e e le premier lment de la cha ee ne. Considrons par exemple la structure toto possdant 2 champs : e e 1. un champ data de type int ; 2. un champ next de type pointeur vers une struct toto. La liste est alors un objet de type pointeur sur une struct toto. Grce au a mot-clef typedef, on peut dnir le type liste, synonyme du type pointeur e sur une struct toto (voir 4.6). On peut alors dnir un objet liste l quil e convient dinitialiser ` NULL. Cette reprsentation est illustre dans la gure 4.3. a e e Un des avantages de la reprsentation cha ee est quil est tr`s facile dinsrer e n e e un lment ` un endroit quelconque de la liste. Ainsi, pour insrer un lment ee a e ee en tte de liste, on utilise la fonction suivante : e
4 et non *p.nom qui sera interprt (compte tenu des priorits entre oprateurs) par ee e e *(p.nom) ce qui na de sens que si le champ nom est un pointeur !

53

l struct toto data 14 next

#include <stddef.h> struct toto { int data; struct toto *next; }; typedef struct toto *liste; liste l = NULL; //ajout dlments ... struct toto data 10 next

NULL

Fig. 4.3 Representation dune liste cha ee n


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

De faon gnral, lajout dun nouvel lment dans une liste cha ee seectue c e e ee n en trois tapes : e 1. recherche de lendroit o` la nouvelle cellule devra tre insre ; u e ee 2. cration de la nouvelle cellule (par malloc) et mise ` jour de ses champs. e a Au niveau de lallocation en mmoire, on prendra garde : e (a) de rserver le bon nombre doctets (en reprenant lexemple prcdent, e e e sizeof(struct toto) et non sizeof(struct toto *)) (b) de convertir le rsultat du malloc vers le bon type (pointeur vers la e structure). 3. mise ` jour des champs des cellules voisines. a Supposons par exemple que lon souhaite insrer un lment en queue de liste : e ee
liste insereInTail(int element, liste Q) { liste L, tmp=Q; // creation de la nouvelle cellule L = (liste)malloc(sizeof(struct toto)); // allocation de la zone // mmoire necssaire e e L->data = element; L->next = NULL; if (Q == NULL) return L; // maintenant Q est forcement non vide ==> champ tmp->next existe // recherche de lendroit ou insrer e while (tmp->next != NULL) tmp = tmp->next; // dplacement jusquau e // dernier lment de la liste e e

54

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

Pour supprimer le premier lment de la liste par exemple, il est important de ee librer lespace mmoire allou : e e e
liste supprime_tete(liste L) { liste suivant = L; if (L != NULL) { // pour etre s^r que L->next existe u suivant= L->next; free(L); //libration de lespace allou pour une cellule e e } return suivant; }

Il est primordial dutiliser free dans le cas des listes cha ees. Sinon, la mn e moire risque dtre rapidement sature (par exemple lors dajouts et de supprese e sions successives qui ne lib`rent pas lespace allou). Ainsi, de faon similaire e e c a ` lajout, la suppression dune cellule dans une liste cha ee seectue en trois n tapes : e 1. Recherche de la cellule ` supprimer ; a 2. mise ` jour des champs des cellules voisines ; a 3. libration de la cellule avec free. e Un exemple plus complet de lutilisation des listes cha ees est founi en ann nexe A.2 page 110.

4.4

Les unions

Il est parfois ncessaire de manipuler des variables auxquelles on dsire aecter e e des valeurs de types dirents. Une union dsigne un ensemble de variables e e de types dirents susceptibles doccuper alternativement une mme zone me e e moire. Une union permet donc de dnir un objet comme pouvant tre dun e e type au choix parmi un ensemble ni de types. Si les membres dune union sont de longueurs direntes, la place rserve en mmoire pour la reprsenter e e e e e correspond ` la taille du membre le plus grand. a

4.4.1

Dclaration dune union e

La dclaration et la dnition dune union ne di`rent de celles dune structure e e e que par lutilisation du mot-cl union (qui remplace le mot-cl struct). e e Dans lexemple suivant, la variable hier de type union jour peut tre soit un e caract`re, soit un entier (mais pas les deux ` la fois) : e a
#include <stdio.h> union jour { char lettre; int numero;

55

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

Si on se rf`re au tableau 1.4 page 12, la zone mmoire alloue pour une variable ee e e de type union jour sera de sizeof(int) (2 ou 4 octets). On aura compris quon acc`de aux lments dune union avec le mme oprateur e ee e e de slection (. ou ->) que celui utilis dans les structures. e e

4.4.2

Utilisation pratique des unions

Lorsquil manipule des unions, le programmeur na malheureusement aucun moyen de savoir ` un instant donn quel est le membre de lunion qui poss`de a e e une valeur. Pour tre utilisable, une union doit donc toujours tre associe ` une variable e e e a dont le but sera dindiquer le membre de lunion qui est valide. En pratique, une union et son indicateur dtat sont gnralement englobs ` lintrieur dune e e e e a e structure. Exemple :
#include <stdio.h> enum etat {CARAC, ENTIER}; /* struct jour a deux membres indicateur de type int, et day de type union de char et de int */ struct jour { enum etat indicateur; //indique ce qui est dans j union { char lettre; int numero; } day; }; /* Affiche le contenu de la variable d, nomme name */ e void print_jour(struct jour d, char * name){ if (d.indicateur == CARAC) printf("%s = %c\n",name,d.day.lettre); else if (d.indicateur == ENTIER) printf("%s = %i\n",name,d.day.numero); else printf("Erreur!\n"); } int main() { struct jour ex1, ex2; //dclaration e //utilisation ex1.indicateur = CARAC; ex1.day.lettre = j; //jeudi print_jour(ex1, "ex1"); ex2.indicateur = ENTIER; ex2.day.numero = 4;

56

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

Lexcution de ce programme renverra : e ex1 = j ex2 = 4

4.4.3

Une mthode pour allger lacc`s aux membres e e e

Quand une union est dans une structure (comme cest la cas dans lexemple prcdent), on aura noter que lacc`s aux membres de lunion est relativement e e e lourd (ex : d.day.numero pour accder au membre numero). On peut allger e e cette criture en utilisant les facilits du prprocesseur (voir le chapitre 7) : e e e #define L day.lettre #define N day.numero ... ex1.indicateur = CARAC; ex1.L = j; //initialisation de ex1 ` jeudi a

4.5

Les champs de bits

Il est possible en C de spcier la longueur des champs dune structure au bit e pr`s si ce champ est de type entier (int ou unsigned int). Cela se fait en e prcisant le nombre de bits du champ avant le ; qui suit sa dclaration : e e type [membre] : nb_bits ; On utilise typiquement les champs de bits en programmation syst`me, pour e manipuler des registres particuliers de la machine etc... Par exemple, imaginons le registre suivant :
ov Position:
15 14 13

(inutilis)
12 11 10 9 8 7

privilege
6 5 4 3 2

masque
1 0

Fig. 4.4 Exemple daectation de bits dans un registre Ce registre peut se dcrire de la mani`re suivante : e e struct registre { unsigned int signed int unsigned int unsigned int }; masque : 3; privilege : 6; : 6; /* inutilis */ e ov : 1;

Le champ masque sera cod sur 3 bits, privilege sur 6 bits etc... A noter e que lordre dans lequel les champs sont placs ` lintrieur de ce mot dpend e a e e de limplmentation. On voit que le C accepte que lon ne donne pas de nom e 57

aux champs de bits qui ne sont pas utiliss. Le champ ov de la structure ne e peut prendre que les valeurs 0 ou 1. Aussi, si r est un objet de type struct registre, lopration r.ov += 2 ; ne modie pas la valeur du champ. e Voici les quelques contraintes ` respecter : a La taille dun champ de bits doit tre infrieure au nombre de bits dun entier e e (long). un champ de bits na pas dadresse ; on ne peut donc pas lui appliquer lope rateur &.

4.6

Dnition de synonymes de types avec typedef e

Pour allger lcriture des programmes, on peut aecter un nouvel identicateur e e a ` un type ` laide du mot-cl typedef : a e typedef type synonyme ; Exemple : typedef unsigned char UCHAR; typedef struct { double x, y } POINT; typedef POINT *P_POINT; //pointeur vers un POINT A partir de ce moment, lidenticateur UCHAR peut tre utilis comme une abe e brviation pour le type unsigned char, lidenticateur POINT peut tre utilis e e e pour spcier le type de structure associ et P_POINT permet de caractriser un e e e pointeur vers un POINT : UCHAR c1, c2, tab[100]; POINT point; P_POINT pPoint;

58

Chapitre 5

Retour sur les fonctions


La structuration de programmes en sous-programmes se fait en C ` laide de a fonctions. Lutilisation de fonctions a dj` t vu, par exemple avec celle prdeae e e e nies dans des biblioth`ques standards (comme printf de <stdio.h>, malloc e de <stdlib.h> etc...) ou encore la fonction dentre main. Lorsque les listes e cha ees ont t abordes au 4.3.5, on a galement dni des fonctions dinsern ee e e e tion et de suppression dlments. ee On laura compris : comme dans la plupart des langages, on peut (doit ?) en C dcouper un programme en plusieurs fonctions. Il existe une unique fonction e obligatoire : la fonction principale appele main. e

5.1

Dclaration et dnition dune fonction e e

Comme on la vu au 1.4, le format gnral dune dnition de fonction est de e e e la forme : type_resultat nom fonction (type1 arg1 , . . . , typen argn ) { <dclaration de variables locales > e <liste dinstructions > } Dans cette dnition de fonction, on rappelle que : e type_resultat correspond au type du rsultat de la fonction. e nom fonction est le nom qui identie la fonction. type1 arg1 . . . typen argn dnit les types et les noms des param`tres de e e la fonction. Ces arguments sont appels param`tres formels, par opposition e e aux param`tres eectifs qui sont les param`tres avec lesquels la fonction est e e eectivement appele (voir 5.2). e On peut se contenter de dclarer le prototype dune fonction (sans le corps) : e type_resultat nom fonction (type1 arg1 , . . . , typen argn ) ; Dans ce cas, la dnition de la fonction (avec le corps des instructions qui la e 59

compose) peut tre dclare plus loin dans le programme1 . e e e Contrairement ` la dnition de la fonction, le prototype nest donc pas suivi a e du corps de la fonction (contenant les instructions ` excuter). a e ATTENTION ! Le prototype est une instruction, il est donc suivi dun pointvirgule ! Quelques remarques complmentaires : e 1. Une fonction peut fournir comme rsultat : e un type arithmtique, e une structure (voir 4.3), une union (voir 4.4), un pointeur (voir chapitre 3), void (voir ci-apr`s). e Une fonction ne peut pas fournir comme rsultat des tableaux, des cha e nes de caract`res ou des fonctions, mais il est cependant possible de renvoyer e un pointeur sur le premier lment dun tableau ou dune cha de caee ne ract`res. e 2. Si une fonction ne fournit pas de rsultat, il faut indiquer void comme e type du rsultat. e Ex : void print_liste(liste L){...} 3. Si une fonction na pas de param`tres, on peut dclarer la liste des parae e m`tres comme (void) ou simplement comme (). e Ex : int main(){...} // equivalent a int main(void){...} 4. Il est interdit de dnir des fonctions ` lintrieur dune autre fonction e a e (comme en Pascal). 5. En principe, lordre des dnitions de fonctions dans le texte du proe gramme ne joue pas de rle, mais chaque fonction doit tre dclare ou o e e e dnie avant dtre appele. e e e Dans le cas o` type_resultat est dirent du type void, le corps de la foncu e tion doit obligatoirement comporter une instruction return, dite instruction de retour a la fonction appelante. La syntaxe de cette instruction est : ` return expression ; La valeur de expression correspond ` la valeur que retourne la fonction et doit a donc tre de type type_resultat. e Plusieurs instructions return peuvent appara dans une fonction. Le retour tre au programme appelant sera alors provoqu par le premier return rencontr e e lors de lexcution (voir la fonction puissance de lexemple qui suit). Ex : e
/***Renvoit le produit de 2 entiers ***/ int produit (int a, int b) { return(a*b); } /*** Renvoit la valeur de a^n (version nave) ***/ int puissance (int a, int n) {
1 mais cette dnition doit respecter la dclaration du prototype : nhsiter pas ` utiliser le e e e a copier/coller du prototype !

60

if (n == 0) return 1; return a*puissance(a, n-1); //noter labsence du else }

Enn, il faut noter que les variables ventuellement dclares au sein dune fonce e e tion seront locales ` celle-ci et nauront de sens que dans le corps de la fonction. a De mme, les identicateurs des arguments de la fonction (arg1 ,. . . ,argn ) nont e dimportance qu` lintrieur de celle-ci. a e

5.2

Appel dune fonction

Lappel dune fonction se fait par lexpression : nom fonction ( arg1 , . . . ,argn ) Lordre et le type des param`tres eectifs de la fonction doivent concorder avec e ceux donns dans la dnition de len-tte de la fonction. Les param`tres eectifs e e e e 2. peuvent tre des expressions e De plus, lordre dvaluation des param`tres eectifs nest pas assur et dpend e e e e du compilateur. Il est donc dconseill, pour une fonction ` plusieurs parae e a m`tres, de faire gurer des oprateurs dincrmentation ou de dcrmentation e e e e e (++ ou --) dans les expressions dnissant les param`tres eectifs. e e

5.3

Dure de vie des identicateurs e

Les variables manipules dans un programme C ne sont pas toutes traites de e e la mme mani`re. En particulier, elles nont pas toutes la mme dure de vie. e e e e On distingue deux catgories de variables. e 1. Les variables permanentes (ou statiques) Une variable permanente occupe un emplacement en mmoire qui reste le mme durant toute lexcution e e e du programme. Cet emplacement est allou une fois pour toutes lors de la e compilation. La partie de la mmoire contenant les variables permanentes e est appele segment de donnes. Par dfaut, les variables permanentes e e e sont initialises ` zro par le compilateur. Elles sont caractrises par le e a e e e mot-clef static. 2. Les variables temporaires se voient allouer un emplacement en mmoire de e faon dynamique lors de lexcution du programme. Elles ne sont pas inic e tialises par dfaut. Leur emplacement en mmoire est libr par exemple e e e ee a ` la n de lexcution dune fonction secondaire. e Par dfaut, les variables temporaires sont situes dans la partie de la mmoire e e e appele segment de pile. Dans ce cas, la variable est dite automatique. Le spcie e cateur de type correspondant, auto, est rarement utilis puisquil ne sapplique e quaux variables temporaires qui sont automatiques par dfaut. e
2 La virgule qui spare deux param`tres eectifs est un simple signe de ponctuation ; il ne e e sagit pas de loprateur virgule. e

61

Une variable temporaire peut galement tre place dans un registre de la mae e e chine. Un registre est une zone mmoire sur laquelle sont eectues les oprae e e tions machine. Il est donc beaucoup plus rapide daccder a un registre qu` e ` a toute autre partie de la mmoire. On peut demander au compilateur de rane ger une variable tr`s utilise dans un registre, ` laide de lattribut de type e e a register. Le nombre de registres tant limit, cette requte ne sera satisfaite que sil e e e reste des registres disponibles. Cette technique permettant dacclrer les proee grammes a aujourdhui perdu tout son intrt. Grce aux performances des opee a timiseurs de code intgrs au compilateur (cf. options -O de gcc : voir 1.1.4), e e il est maintenant plus ecace de compiler un programme avec une option doptimisation que de placer certaines variables dans des registres. La dure de vie des variables est lie ` leur porte, cest-`-dire ` la portion du e e a e a a programme dans laquelle elles sont dnies. e

5.4

Porte des variables e

Selon lendroit o` on dclare une variable, celle-ci pourra tre accessible (visible) u e e partout dans le code ou seulement dans une partie de celui-ci (` lintrieur dune a e fonction typiquement), on parle de la porte (ou visibilit) de la variable. e e Lorsquune variable est dclare dans le code mme, cest-`-dire ` lextrieur e e e a a e de toute fonction ou de tout bloc dinstruction, elle est accessible de partout dans le code (nimporte quelle fonction du programme peut faire appel ` cette a variable). On parle alors de variable globale. Lorsque lon dclare une variable ` lintrieur dun bloc dinstructions (entre des e a e accolades), sa porte se limite ` lintrieur du bloc dans lequel elle est dclare. e a e e e On parle de variable locale.

5.4.1

Variables globales

Un variable globale est donc une variable dclare en dehors de toute fonction, e e au dbut du chier, ` lextrieur de toutes les fonctions et sont disponibles ` e a e a toutes les fonctions du programme. En gnral, les variables globales sont dclae e e res immdiatement derri`re les instructions #include au dbut du programme. e e e e Elles sont systmatiquement permanentes. e Remarque : Les variables dclares au dbut de la fonction principale main ne e e e sont pas des variables globales, mais locales ` main ! a Exemple : La variable STATUS est dclare globalement pour pouvoir tre utilise dans les e e e e procdures A et B. e
#include <stdio.h> int STATUS; void A(...){ ... if (STATUS>0) STATUS--;

62

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

Conseils : Les variables globales sont ` utiliser avec prcaution, puisquelles crent des a e e liens invisibles entre les fonctions. La modularit dun programme peut en e sourir et le programmeur risque de perdre la vue densemble. Il faut faire attention ` ne pas cacher involontairement des variables globales a par des variables locales du mme nom. (voir section suivante) e Je vous conseille donc dcrire nos programmes aussi localement e que possible.

5.4.2

Variables locales

Les variables dclares dans un bloc dinstructions sont uniquement visibles ` e e a lintrieur de ce bloc. On dit que ce sont des variables locales ` ce bloc. e a Exemple : la variable nom est dnie localement dans le bloc extrieur de la e e fonction hello. Ainsi, aucune autre fonction na acc`s ` la variable nom : e a
void hello() { char nom[20]; printf("Introduisez votre nom : "); scanf("%s",&nom); printf("Bonjour %s !\n", nom); }

Attention ! Une variable dclare a lintrieur dun bloc cache toutes les vae e ` e riables du mme nom des blocs qui lentourent. Exemple : e int X; //dclaration dune variable globale e int fonction(int A) { double X; //variable locale qui cache la variable X globale ... } Remarque : bien quil soit possible en C3 de dclarer des variables ` lintrieur e a e dune boucle ou dun bloc conditionnel, il est dconseill de le faire et toutes les e e dclarations locales devront se faire au dbut des fonctions. e e
3

depuis la norme C99 en fait

63

5.5

Rcursivit e e

Les dnitions par rcurrence sont assez courantes en mathmatiques. Par e e e exemple, on peut considrer la fameuse suite de Fibonacci : e u0 = 0 u =1 1 un = un1 + un2 n 2 Le fonctions dnies en C ont la proprit de rcursivit, cest ` dire la capacit e ee e e a e de sappeler elle-mme. e Tous les dtails de la rcursivit ont t fournis au 2.3 page 24. e e e ee

5.6

Passage de param`tres ` une fonction e a

Les param`tres ou arguments sont les bo aux lettres dune fonction. Elles e tes acceptent les donnes de lextrieur et dterminent les actions et le rsultat de e e e e la fonction.

5.6.1

Gnralits e e e

En ce qui concerne le passage de param`tres ` une procdure, le programmeur e a e a deux besoins fondamentaux : soit il dsire passer une valeur qui sera exploite par lalgorithme de la proe e cdure (cest ce dont on a besoin quand on crit par exemple sin(x)). Une e e telle faon de passer un param`tre sappelle du passage par valeur ; c e soit il dsire passer une rfrence ` une variable, de mani`re ` permettre e ee a e a a ` la procdure de modier la valeur de cette variable. Cest ce dont on a e besoin quand on crit une procdure ralisant le produit de deux matrices e e e prodmat(a,b,c) o` lon veut quen n dexcution, la matrice c soit gale u e e au produit matriciel des matrices a et b. prodmat a besoin des valeurs des matrices a et b, et dune rfrence vers la matrice c. Une telle faon de passer ee c un param`tre sappelle du passage par adresse. e Conversion automatique Lors dun appel, le nombre et lordre des param`tres doivent ncessairement e e correspondre aux indications de la dclaration de la fonction. Les param`tres e e sont automatiquement convertis dans les types de la dclaration avant dtre e e passs ` la fonction. e a Exemple : Le prototype de la fonction pow (biblioth`que <math.h>) est dclar e e e comme suit : double pow(double x, double y); Au cours des instructions, int A, B; ... A = pow (B, 2); 64

On assiste ` trois conversions automatiques : avant dtre transmis ` la fonction, a e a la valeur de B est convertie en double ; la valeur 2 est convertie en 2.0 . Comme pow est du type double, le rsultat de la fonction doit tre converti en int avant e e dtre aecte ` A. e e a

5.6.2

Passage des param`tres par valeur e

En C, le passage des param`tres se fait toujours par valeur, autrement dit les e fonctions nobtiennent que les valeurs de leurs param`tres et nont pas dacc`s e e aux variables elles-mmes. e Les param`tres dune fonction sont ` considrer comme des variables locales e a e qui sont initialises automatiquement par les valeurs indiques lors dun appel. e e A lintrieur de la fonction, On peut donc changer les valeurs des param`tres e e sans inuencer les valeurs originales dans les fonctions appelantes. Exemple : La fonction ETOILES dessine une ligne de N toiles. Le param`tre N est modi e e e a ` lintrieur de la fonction mais pas ` lextrieur : e a e
#include <stdio.h> void ETOILES(int N) { while (N>0) { printf("*"); N--; } printf("\n"); } int main() { int compteur=14; ETOILES(compteur); printf("Valeur de compteur: %i\n",compteur); return 0; }

Lappel de ce programme renvoit : ************** Valeur de compteur: 14 la valeur de compteur na donc pas t modi par lappel de la fonction ee e ETOILES.

5.6.3

Passage des param`tres par adresse e

Comme on vient de le voir, tout param`tre est pass par valeur, et cette r`gle e e e ne soure aucune exception. Cela pose le probl`me de raliser un passage de e e param`tre par adresse lorsque le programmeur en a besoin. e Pour changer la valeur dune variable de la fonction appelante, on proc`de e comme suit : la fonction appelante doit fournir ladresse de la variable ; 65

la fonction appele doit dclarer le param`tre comme pointeur. e e e On peut alors atteindre la variable ` laide du pointeur. a Exemple : Supposons quon dsire crire une procdure add, admettant trois pae e e ram`tres a, b et c. On dsire que le rsultat de lexcution de add soit daecter e e e e au param`tre c la somme des valeurs des deux premiers param`tres. e e Le param`tre c ne peut videmment pas tre pass par valeur, puisquon dsire e e e e e modier la valeur du param`tre eectif correspondant. Il faut donc programmer e add de la mani`re suivante : e
void add(int a, int b, int *c) { /* c rep`re lentier o` on veut mettre le rsultat e u e */ *c = a + b; } int main() { int i=10,j=14,k; /* on passe les valeurs de i et j comme premiers param`tres */ e /* on passe ladresse de k comme troisi`me param`tre e e */ add(i,j,&k); }

5.6.4

Passage de tableau en param`tre e

Comme il est impossible de passer la valeur de tout un tableau ` une fonction, a on fournit ladresse dun lment du tableau4 . ee En gnral, on fournit ladresse du premier lment du tableau, qui est donne e e ee e par le nom du tableau. Dans la liste des param`tres dune fonction, on peut dclarer un tableau par le e e nom suivi de crochets, <type> <nom>[] ou simplement par un pointeur sur le type des lments du tableau : ee <type> *<nom> On prf`rera la premi`re criture car il nest pas possible dans la seconde de ee e e savoir si le programmeur a voulu passer en param`tre un pointeur vers <type> e (cest ` dire un pointeur vers un seul <type>), ou au contraire si il a voulu a passer un tableau, cest ` dire un pointeur vers une zone de n <type>. a Exemple :
#include <stdlib.h> /* Initialise void init_tab int i; for (i = 0; tab[i] = return; } int main() {
4

les lments dun tableau */ e e (int tab[], int n){ i < n; i++) i;

Rappelons quun tableau est un pointeur constant (sur le premier lment du tableau) ee

66

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

Remarque : Quand une fonction admet un param`tre de type tableau, il y ` e a deux cas possibles : 1. soit les dirents tableaux qui lui sont passs en param`tre eectif ont des e e e tailles direntes, et dans ce cas la taille doit tre un param`tre supple e e e mentaire de la fonction, comme dans lexemple prcdent ; e e 2. soit les dirents tableaux qui lui sont passs en param`tre eectif ont e e e tous la mme taille, et dans ce cas la taille peut appara dans le type e tre du param`tre eectif : e #define NB_ELEM 10 void init_tab (int tab[NB_ELEM]){{ ... }

5.7

Fonction ` nombre variable de param`tres a e

Il est possible de dclarer une fonction comme ayant un nombre variable de e param`tres en dclarant les param`tres optionnels par lunit lexicale ... e e e e (3 points ` la suite). Une fonction peut avoir ` la fois des param`tres obligaa a e toires et des param`tres optionnels, les param`tres obligatoires apparaissant en e e premier et lunit lexicale ... apparaissant en derni`re position dans la liste e e de dclaration des param`tres formels. e e Dans le corps de la fonction on ne dispose pas de nom pour dsigner les parae m`tres. Lacc`s ` ceux-ci ne peut se faire quen utilisant les macros suivantes e e a dnies dans la biblioth`que standard <stdarg.h> : e e e a va list permet de dclarer une variable opaque au programmeur, ` passer en param`tre aux autres macros. Cette variable sappelle traditionnellement e ap (pour argument pointer), et a pour but de reprer le param`tre eectif e e courant. e e va start doit tre appele avant toute utilisation de va_arg. La macro va_start a deux param`tres : la variable ap et le nom du dernier param`tre oblie e gatoire de la fonction. e e a e va arg dlivre le param`tre eectif courant : le premier appel ` va_arg dlivre le premier param`tre, puis chaque nouvel appel ` va_arg dlivre le parae a e m`tre suivant. La macro va_arg admet deux param`tres : la variable ap e e et le type du param`tre courant. e e e e va end doit tre appele apr`s toutes les utilisations de va_arg. La macro va_end admet un seul param`tre : la variable ap. e Rien nest prvu pour communiquer ` la fonction le nombre et le type des pae a ram`tres eectivement passs : cest un probl`me ` la charge du programmeur. e e e a 67

Exemple Voici la dnition dune fonction de dbuggage sur le mod`le de e e e printf admettant un nombre variable darguments. Lachage ne seectue que si le niveau spci ` lappel (par le parametre level) est infrieur ` une e ea e a variable globale DEBUG_LEVEL.
#define DEBUG_LEVEL ... /** * Fonction de debuggage */ int trace (int level, char * chaine, ...) { va_list args;//les arguments du println int length = 0; va_start(args, chaine);//initialisation de la structure va_list a partir //dun parametre dappel de la fonction if (level <= DEBUG_LEVEL) { while (chaine[length]!=0) { //traitement des argument %... if (chaine[length] == %) { length++;//on passe au caractere suivant switch (chaine[length]) { case d: printf("%d", va_arg(args, long)); break; case i: printf("%i", va_arg(args, long)); break; case x: printf("%x", va_arg(args, unsigned long)); break; case X: printf("%X", va_arg(args, unsigned long)); break; case s: printf("%s", va_arg(args, char *)); break; } } else { //on a une chaine classique donc on laffiche printf("%c", chaine[length]); } length++; } //on termine correctement lutilisation dune structure va_list va_end(args); printf("\n");//on va a la ligne! return 0; } }

Une utilisation classique dune telle fonction, on associera ` lachage dinfora mations de debuggage prcises un niveau level lev tandis que les achages e e e de base auront un param`tre level faible. Ensuite, cest lutilisateur qui en e 68

faisant varier la valeur de DEBUG_LEVEL modie la quantit et la prcision des e e informations aches ` lexcution. Exemple : e a e
/** * Insertion dune valeur val dans un tableau tab de taille n * contenant n-1 premiers elements tries */ void insertion(int tab[], int n, int val) { int i=n; trace(1, "Debut de insertion - param n=%i, val=%i",n,val); trace(2, " Debut decalage des elements"); while( i!=0 && val < tab[i-1]) { trace(3, "Decalage de tab[%i] en position %i",i-1,i); tab[i] = tab[i-1]; i--; } trace(2, " Fin decalage des elements"); trace(2, " Insertion de la valeur %i en position %i",val,i) tab[i] = val; trace(1, "Fin de insertion"); }

A lappel de cette fonction, on aura le comportement suivant : si DEBUG_LEVEL = O : pas dachage ; si DEBUG_LEVEL = 1 : lentre et la sortie de la fonction sera visible par un e message ach. Cela permet de savoir rapidement en cas de bug quelle fonce tion pose probl`me ; e si DEBUG_LEVEL = 2, on aura en plus lachage des tapes dans lalgorithme e utilis pour la fonction ; e si DEBUG_LEVEL > 2, on aura tous les dtails possibles. e

5.8

Pointeurs sur une fonction

Il est parfois utile de passer une fonction comme param`tre dune autre fonce tion. Cette procdure permet en particulier dutiliser une mme fonction pour e e dirents usages. Pour cela, on utilise un mcanisme de pointeur. e e Un pointeur sur une fonction correspond ` ladresse du dbut du code de la a e fonction. Un pointeur p_fonction sur une fonction ayant pour prototype type fonction(type_1 arg_1,...,type_n arg_n); se dclare par : e type (*p_fonction)(type_1,...,type_n); Ainsi, considrons une fonction operateur_binaire prenant pour param`tres e e deux entiers a et b, ainsi quune fonction f de type int qui prend elle-mme e deux entiers en param`tres. On suppose que operateur_binaire renvoit lentier e f(a,b). Le prototype de cette fonction sera : int operateur_binaire(int a, int b, int (*f)(int, int)) Pour appeler la fonction operateur_binaire, on utilisera comme troisi`me e param`tre eectif lidenticateur de la fonction utilise. Par exemple, si somme e e est la fonction 69

int somme(int a, int b) { return a+b; } on appelle la fonction operateur_binaire pour la fonction somme par lexpression : operateur_binaire(a,b,somme) A noter que la notation &somme nest pas utilise comme param`tre eectif. e e Pour appeler la fonction passe en param`tre dans le corps de la fonction e e operateur_binaire, on crit (*f)(a, b). Ainsi, cette fonction pourrait scrire : e e int operateur_binaire(int a, int b, int (*f)(int, int)) { return((*f)(a,b)); } Les pointeurs sur les fonctions sont notamment utiliss dans la fonction de tri e des lments dun tableau qsort et dans la recherche dun lment dans un ee ee tableau bsearch. Ces deux fonctions sont dnies dans la librairie standard e <stdlib.h>. Le prototype de la fonction de tri5 (algorithme quicksort) est ainsi : void qsort(void *tab, size_t nb_elements, size_t taille_elements, int(*compar)(const void *, const void *));

Cette gymnastique sur les pointeurs de fonction nest pas forcment triviale e mais reste utile dans certains cas, en particulier dans la dnition de fonction e gnrique comme la fonction qsort voque prcdemment. e e e e e e

Elle permet de trier les nb_elements premiers lments du tableau tab. Le param`tre ee e taille_elements donne la taille des lments du tableau. Le type size_t utilis ici est un ee e type prdni dans <stddef.h>. Il correspond au type du rsultat de lvaluation de sizeof. e e e e La fonction qsort est paramtre par la fonction de comparaison compar de prototype : e e int compar(void *a, void *b) ; Les deux param`tres a et b de la fonction compar sont des pointeurs gnriques de type void *. e e e Ils correspondent ` des adresses dobjets dont le type nest pas dtermin. Cette fonction de a e e comparaison retourne un entier qui vaut 0 si les deux objets points par a et b sont gaux ; e e une valeur strictement ngative (resp. positive) si lobjet point par a est strictement e e infrieur (resp. suprieur) ` celui point par b. e e a e

70

Chapitre 6

Gestion des chiers


Comme beaucoup de langages, le C ore la possibilit de lire et dcrire des e e donnes dans un chier. e Pour des raisons decacit, les acc`s ` un chier se font par lintermdiaire e e a e dune mmoire-tampon (on parle de buer), ce qui permet de rduire le nombre e e dacc`s aux priphriques (disque...). e e e Pour pouvoir manipuler un chier, un programme a besoin dun certain nombre dinformations : ladresse de lendroit de la mmoire-tampon o` se trouve le e u chier, la position de la tte de lecture, le mode dacc`s au chier (lecture ou e e criture)... Ces informations sont rassembles dans une structure, dont le type, e e FILE *, est dni dans <stdio.h>. Un objet de type FILE * est appel ot de e e donnes (stream en anglais). e Avant de lire ou dcrire dans un chier, on notie son acc`s par la commande e e fopen. Cette fonction prend comme argument le nom du chier, dni avec e le syst`me dexploitation le mode douverture du chier et initialise un ot de e donnes, qui sera ensuite utilis lors de lcriture ou de la lecture. Apr`s les e e e e traitements, on annule la liaison entre le chier et le ot de donnes grce ` la e a a fonction fclose.

6.1
6.1.1

Ouverture et fermeture de chiers


Ouverture de chiers : la fonction fopen

Lorsquon dsire accder ` un chier, il est ncessaire avant tout acc`s douvrir e e a e e le chier ` laide de la fonction fopen. a Cette fonction, de type FILE * ouvre un chier et lui associe un ot de donnes. e Sa syntaxe est : fopen("nom-de-fichier","mode") La valeur retourne par fopen est e soit un ot de donnes ; e soit NULL si lexcution de cette fonction ne se droule pas normalement. e e Je vous conseille donc de toujours tester si la valeur renvoye par la fonction e fopen est gale ` NULL an de dtecter les erreurs douverture de chier (lecture e a e dun chier inexistant...). 71

Le premier argument de fopen fournit donc le nom du chier. Le second argument, mode, est une cha de caract`res qui spcie le mode dacc`s au chier. ne e e e Les spcicateurs de mode dacc`s di`rent suivant le type de chier considr. e e e ee On distingue les chiers textes, pour lesquels les caract`res de contrle (retour ` la ligne e o a ...) seront interprts en tant que tels lors de la lecture et de lcriture ; ee e les chiers binaires, pour lesquels les caract`res de contrle se sont pas intere o prts. ee Les dirents modes dacc`s sont lists dans le tableau 6.1. e e e "r" "w" "a" "rb" "wb" "ab" "r+" "w+" "a+" "r+b" "w+b" "a+b" ouverture ouverture ouverture ouverture ouverture ouverture ouverture ouverture ouverture ouverture ouverture ouverture dun dun dun dun dun dun dun dun dun dun dun dun chier chier chier chier chier chier chier chier chier chier chier chier texte en lecture texte en criture e texte en criture ` la n e a binaire en lecture binaire en criture e binaire en criture ` la n e a texte en lecture/criture e texte en lecture/criture e texte en lecture/criture ` la n e a binaire en lecture/criture e binaire en lecture/criture e binaire en lecture/criture ` la n e a

Tab. 6.1 Les modes dacc`s aux chiers dans la fonction fopen e

Conditions particuli`res et cas derreur e Si le mode contient la lettre r, le chier doit exister, sinon cest une erreur. Si le mode contient la lettre w, le chier peut ne pas exister. Dans ce cas, il sera cr, et si le chier existait dj`, son ancien contenu est perdu. ee ea Si le mode contient la lettre a, le chier peut ne pas exister. Comme pour le cas prcdent, si le chier nexiste pas, il est cr ; si le chier existe dj`, son e e ee ea ancien contenu est conserv. e Si un chier est ouvert en mode criture ` la n, toutes les critures se font ` e a e a lendroit qui est tait la n du chier lors de lexcution de lordre dcriture. e e e Cela signie que si plusieurs processus partagent le mme FILE*, rsultat de e e louverture dun chier en criture ` la n, leurs critures ne scraseront pas e a e e mutuellement. Les modes de communication standard Quand un programme est lanc par le syst`me, celui-ci ouvre trois chiers core e respondant aux trois modes de communication standard : standard input, standard output et standard error. Il y a trois constantes prdnies dans stdio.h e e qui correspondent aux trois chiers : stdin (standard input) : ot dentre (par dfaut, le clavier) ; e e 72

stdout (standard output) : ot de sortie (par dfaut, lcran) ; e e stderr (standard error) : ot dachage des messages derreur (par dfaut, e lcran). e Il est fortement conseill dacher systmatiquement les messages derreur sur e e stderr an que ces messages apparaissent ` lcran mme lorsque la sortie a e e standard est redirige. e Utilisation typique de fopen
#include <stdio.h> FILE *fp; ... if ((fp = fopen("donnees.txt","r")) == NULL) { fprintf(stderr,"Impossible douvrir le fichier donnes en lecture\n"); e exit(1); }

6.1.2

Fermeture de chiers : la fonction fclose

Elle permet de fermer le ot qui a t associ ` un chier par la fonction fopen. ee ea Sa syntaxe est : fclose(flot) o` flot est le ot de type FILE* retourn par la fonction fopen correspondante. u e La fonction fclose retourne 0 si lopration sest droule normalement et EOF e e e si il y a eu une erreur.

6.2
6.2.1

Les entres-sorties formates e e


La fonction dcriture en chier fprintf e

La fonction fprintf, analogue ` printf (voir 2.5.4 page 29), permet dcrire a e des donnes dans un ot. Sa syntaxe est : e fprintf(flot,"cha^ne de contr^le", expression1 , . . . , expressionn ); o o` flot est le ot de donnes retourn par la fonction fopen. Les spcications u e e e de format utilises pour la fonction fprintf sont les mmes que pour printf e e puisque : printf(...) fprintf(stdout,...)

6.2.2

La fonction de saisie en chier fscanf

La fonction fscanf, analogue ` scanf (voir 2.5.5 page 31), permet de lire des a donnes dans un chier. Sa syntaxe est semblable ` celle de scanf : e a fscanf(flot,"cha^ne de contr^le", arg1 , . . . , argn ); o o` flot est le ot de donnes retourn par fopen. Les spcications de format u e e e sont ici les mmes que celles de la fonction scanf. e 73

6.3
6.3.1

Impression et lecture de caract`res dans un e chier


Lecture et criture par caract`re : fgetc et fputc e e

Similaires aux fonctions getchar et putchar (voir 2.5.1 et 2.5.2), les fonctions fgetc et fputc permettent respectivement de lire et dcrire un caract`re dans e e un chier. La fonction fgetc retourne le caract`re lu dans le chier et la constante EOF e lorsquelle dtecte la n du chier. Son prototype est : e int fgetc(FILE* flot); o` flot est le ot de type FILE* retourn par la fonction fopen. u e La fonction fputc crit un caract`re dans le ot de donnes : e e e int fputc(int caractere, FILE *flot) Elle retourne lentier correspondant au caract`re lu (ou la constante EOF en cas e derreur).

6.3.2

Lecture et criture optimises par caract`re : getc et putc e e e

Il existe galement deux versions optimises des fonctions fgetc et fputc qui e e sont implmentes par des macros. Il sagit respectivement de getc et putc. e e Leur syntaxe est similaire ` celle de fgetc et fputc : a int getc(FILE* flot); int putc(int caractere, FILE *flot) Ainsi, le programme suivant lit le contenu du chier texte entree.txt, et le recopie caract`re par caract`re dans le chier sortie.txt : e e
#include <stdio.h> #include <stdlib.h> #define ENTREE "entree.txt" #define SORTIE "sortie.txt" int main(void) { FILE *f_in, *f_out; int c; // Ouverture du fichier ENTREE en lecture if ((f_in = fopen(ENTREE,"r")) == NULL) { fprintf(stderr, "\nErreur: Impossible de lire %s\n",ENTREE); return(EXIT_FAILURE); } // Ouverture du fichier SORTIE en ecriture if ((f_out = fopen(SORTIE,"w")) == NULL) { fprintf(stderr, "\nErreur: Impossible decrire dans %s\n",SORTIE); return(EXIT_FAILURE); }

74

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

On notera lutilisation des constantes EXIT_SUCCESS (valant 0) et EXIT_FAILURE (valant 1), dnies dans la librairie <stdlib.h> et qui sont les param`tres prie e vilgis de la fonction exit (voir 9.19). e e

6.3.3

Relecture dun caract`re e

Il est possible de replacer un caract`re dans un ot au moyen de la fonction e ungetc : int ungetc(int carac, FILE *f); Cette fonction place le caract`re carac (converti en unsigned char) dans le e ot f. En particulier, si carac est gal au dernier caract`re lu dans le ot, elle e e annule le dplacement provoqu par la lecture prcdente. Toutefois, ungetc e e e e peut tre utilise avec nimporte quel caract`re (sauf EOF). Lexemple suivant e e e permet dillustrer le comportement de ungetc :
#include <stdio.h> #include <stdlib.h> #define ENTREE "entree.txt" int main(void) { FILE *f_in; int c; if ((f_in = fopen(ENTREE,"r")) == NULL) { fprintf(stderr, "\nErreur: Impossible de lire le fichier %s\n",ENTREE); return(EXIT_FAILURE); } while ((c = fgetc(f_in)) != EOF) { if (c == 0) ungetc(.,f_in); putchar(c); } fclose(f_in); return(EXIT_SUCCESS); }

Sur le chier entree.txt dont le contenu est 097023, ce programme ache ` a lcran 0.970.23. e 75

6.3.4

Les entres-sorties binaires : fread et fwrite e

Les fonctions dentres-sorties binaires permettent de transfrer des donnes e e e dans un chier sans transcodage. En ce sens, elles sont plus portables car elles crivent ce quon leur dit. Elles sont ainsi plus ecaces que les fonctions dentree e sortie standard. Elles sont notamment utiles pour manipuler des donnes de grande taille ou e ayant un type compos. Leurs prototypes sont : e size_t fread(void *pointeur, size_t taille, size_t nbre, FILE *f); size_t fwrite(void *pointeur, size_t taille, size_t nbre, FILE *f); o` pointeur est ladresse du dbut des donnes ` transfrer, taille la taille des u e e a e objets ` transfrer et nbre leur nombre. Rappelons que le type size_t, dni a e e dans <stddef.h>, correspond au type du rsultat de lvaluation de sizeof e e (voir 2.1.7 et 2.4.4). La fonction fread lit les donnes sur le ot f et la fonction fwrite les crit. e e Ces deux fonctions retournent le nombre de donnes transfres. e ee Par exemple, le programme suivant crit un tableau dentiers (contenant les 50 e premiers entiers) avec fwrite dans le chier sortie.txt, puis lit ce chier avec fread et imprime les lments du tableau. ee
#include <stdio.h> #include <stdlib.h> #define NB 50 #define F_SORTIE "sortie.txt" int main(void) { FILE *f_in, *f_out; int *tab1, *tab2; int i; // allocation memoire des tableaux tab1 = (int*)malloc(NB * sizeof(int)); tab2 = (int*)malloc(NB * sizeof(int)); for (i = 0 ; i < NB; i++) tab1[i] = i; /* ecriture du tableau dans F_SORTIE */ if ((f_out = fopen(F_SORTIE, "w")) == NULL) { fprintf(stderr, "\nImpossible decrire dans %s\n",F_SORTIE); return(EXIT_FAILURE); } fwrite(tab1, NB * sizeof(int), 1, f_out); fclose(f_out); /* lecture dans F_SORTIE */ if ((f_in = fopen(F_SORTIE, "r")) == NULL) { fprintf(stderr, "\nImpossible de lire dans %s\n",F_SORTIE); return(EXIT_FAILURE); } fread(tab2, NB * sizeof(int), 1, f_in);

76

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

Les lments du tableau sont bien achs ` lcran. Par contre, on constate que ee e a e le contenu du chier sortie nest pas encod. e

6.3.5

Positionnement dans un chier : fseek, rewind et ftell

Les direntes fonctions dentres-sorties permettent daccder ` un chier en e e e a mode squentiel : les donnes du chier sont lues ou crites les unes ` la suite e e e a des autres. Il est galement possible daccder ` un chier en mode direct, cest-`-dire que e e a a lon peut se positionner ` nimporte quel endroit du chier. La fonction fseek a permet de se positionner ` un endroit prcis et a pour prototype : a e int fseek(FILE *flot, long deplacement, int origine); La variable deplacement dtermine la nouvelle position dans le chier. Il sagit e dun dplacement relatif par rapport ` origine, compt en nombre doctets. e a e La variable origine peut prendre trois valeurs : 1. SEEK_SET (gale ` 0) : dbut du chier ; e a e 2. SEEK_CUR (gale ` 1) : position courante ; e a 3. SEEK_END (gale ` 2) : n du chier. e a La fonction int rewind(FILE *flot); permet de se positionner au dbut du chier. Elle est quivalente ` fseek(flot, e e a 0, SEEK_SET) ; La fonction long ftell(FILE *flot); retourne la position courante dans le chier (en nombre doctets depuis lorigine). Lexemple suivant illustre lutilisation des fonctions prcdentes : e e
#include <stdio.h> #include <stdlib.h> #define NB 50 #define F_SORTIE "sortie.txt" int main(void) { FILE *f_in, *f_out; int *tab; int i; // Initialisation du tableau

77

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

Lexcution de ce programme ache ` lcran : e a e position position position position 200 160 0 24

i = 40 i = 0 i = 6

On constate en particulier que lemploi de la fonction fread provoque un dplae cement correspondant ` la taille de lobjet lu ` partir de la position courante. a a

78

Chapitre 7

Les directives du prprocesseur e


Le prprocesseur est un programme excut lors de la premi`re phase de la e e e e compilation. Il eectue des modications textuelles sur le chier source ` para tir de directives. Les direntes directives au prprocesseur, introduites par le e e caract`re #, ont pour but : e lincorporation de chiers source (#include), la dnition de constantes symboliques et de macros (#define), e la compilation conditionnelle (#if, #ifdef,...). Lutilisation de ces directives a dj` t introduite au 1.4 page 7. Il sagit ici eaee de dtailler lensemble des directives du prprocesseur. e e

7.1

La directive #include

Elle permet dincorporer dans le chier source le texte gurant dans un autre chier. Ce dernier peut tre un chier en-tte de la librairie standard (stdio.h, e e math.h,...) ou nimporte quel autre chier. La directive #include poss`de deux e syntaxes voisines : #include <nom-de-fichier> recherche le chier mentionn dans un ou plusieurs rpertoires syst`mes dnis e e e e par limplmentation (typiquement /usr/include/) : e #include "nom-de-fichier" recherche le chier dans le rpertoire courant (celui o` se trouve le chier e u source). On peut spcier dautres rpertoires ` laide de loption -I du compie e a lateur (voir chapitre 8). La premi`re syntaxe est gnralement utilise pour les chiers en-tte de la e e e e e librairie standard, tandis que la seconde est plutt destine aux chiers crs o e ee par lutilisateur.

7.2

La directive #define

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

7.2.1

Dnition de constantes symboliques e

Bien que cela a dj` t vu prcdemment, rappelons que lorsque le prproceseaee e e e seur lit une ligne du type : #define nom reste-de-la-ligne il remplace dans toute la suite du source toute nouvelle occurrence de nom par reste-de-la-ligne. Il ny a aucune contrainte quand ` ce qui peut se trouver a dans reste-de-la-ligne : on pourra ainsi crire e #define BEGIN { #define END } Lutilit principale des macros sans param`tre est de donner un nom parlant ` e e a une constante. Les avantages ` toujours donner un nom aux constantes sont les a suivants : 1. un nom bien choisi permet dexpliciter la smantique de la constante. Ex : e #define NB_COLONNES 100. 2. la constante chire se trouve ` un seul endroit, ce qui facilite la modie a cation du programme quand on veut changer la valeur de la constante (cas de la taille dun tableau, par exemple). 3. on peut expliciter facilement les relations entre constantes. Ex : #define NB_LIGNES 24 #define NB_COLONNES 80 #define TAILLE_MATRICE NB_LIGNES * NB_COLONNES Dnition de constantes symboliques ` linvocation du compilateur e a Certains compilateurs permettent de dnir des constantes symboliques ` line a vocation du compilateur. Il est alors possible dcrire un programme utilisant e une macro qui nest nulle part dnie dans le source. e Ceci est tr`s pratique pour que certaines constantes critiques dun programme e aient une valeur qui soit attribue ` lextrieur du programme (comme lors e a e dune une phase de conguration par exemple). Ci-dessous, un exemple pour gcc : la compilation du chier prog.c en dnise sant la constante symbolique NB_LIGNES de valeur 14 : gcc -c -DNB_LIGNES=24 prog.c Constantes symboliques prdnies e e Il y a un certain nombre de macros prdnies par le prprocesseur, rcapitules e e e e e dans le tableau 7.1.

7.2.2

Les macros avec param`tres e

Une macro avec param`tres se dnit de la mani`re suivante : e e e #define nom(liste-de-param`tres) corps-de-la-macro e 80

Nom __LINE__ __FILE__ __DATE__ __TIME__ __STDC__

Valeur de la macro numro de la ligne courante du programme source e nom du chier source en cours de compilation la date de la compilation lheure de la compilation 1 si le compilateur est ISO, 0 sinon Tab. 7.1 Les constantes symboliques prdnies e e

Type entier cha ne cha ne cha ne entier

o` liste-de-param`tres est une liste didenticateurs spars par des virgules. u e e e Par exemple, avec la directive #define MAX(a,b) (a > b ? a : b) le processeur remplacera dans la suite du code toutes les occurences du type MAX(x,y) o` x et y sont des symboles quelconques par (x > y ? x : y) u Une macro a donc une syntaxe similaire ` celle dune fonction, mais son emploi a permet en gnral dobtenir de meilleures performances en temps dexcution. e e e La distinction entre une dnition de constante symbolique et celle dune macro e avec param`tres se fait sur le caract`re qui suit immdiatement le nom de e e e la macro : si ce caract`re est une parenth`se ouvrante, cest une macro avec e e param`tres, sinon cest une constante symbolique. Il ne faut donc jamais mettre e despace entre le nom de la macro et la parenth`se ouvrante. Lerreur classique e tant dcrire par erreur : e e #define CARRE (a) a * a la cha de caract`res CARRE(2) sera alors remplace par : (a) a * a (2) ne e e Il faut toujours garder ` lesprit que le prprocesseur neectue que des rema e placements de cha nes de caract`res. En particulier, il est conseill de toujours e e mettre entre parenth`ses le corps de la macro et les param`tres formels qui y e e sont utiliss. Par exemple, si lon crit sans parenth`ses : e e e #define CARRE(a) a * a le prprocesseur remplacera CARRE(a + b) par a + b * a + b et non par e (a + b) * (a + b). De mme, !CARRE(x) sera remplac par ! x * x et non e e par !(x * x). Enn, il faut tre attentif aux ventuels eets de bord que peut entra e e ner lusage de macros. Par exemple, CARRE(x++) aura pour expansion (x++) * (x++). Loprateur dincrmentation sera donc appliqu deux fois au lieu dune. e e e En conclusion, les macros avec param`tres sont ` utiliser avec prcaution et en e a e cas de doutes, il faut mieux sen passer.

7.3

La compilation conditionnelle

La compilation conditionnelle a pour but dincorporer ou dexclure des parties du code source dans le texte qui sera gnr par le prprocesseur, le choix tant e ee e e bas sur un test excut ` la compilation. Elle permet dadapter le programme e e ea au matriel ou ` lenvironnement sur lequel il sexcute, ou dintroduire dans le e a e programme des instructions de dbuggage. e 81

Les directives de compilation conditionnelle se rpartissent en deux catgories, e e suivant le type de la condition invoque qui est teste : e e la valeur dune expression lexistence ou linexistence de symboles

7.3.1

Condition lie ` la valeur dune expression e a

Sa syntaxe la plus gnrale est : e e #if condition-1 partie-du-programme-1 #elif condition-2 partie-du-programme-2 ... #elif condition-n partie-du-programme-n #else partie-du-programme-else #endif Le nombre de #elif est quelconque et le #else est facultatif. Chaque condition-i doit tre une expression constante. e Lors de la compilation, une seule partie-du-programme-i sera compile : celle e qui correspond ` la premi`re condition-i non nulle, ou bien la partie-du-programme-else a e si toutes les conditions sont nulles. Par exemple, on peut crire : e
#define PROCESSEUR ALPHA ... #if PROCESSEUR == ALPHA taille_long = 64; #elif PROCESSEUR == PC taille_long = 32; #endif

7.3.2

Condition lie ` lexistence dun symbole e a

Sa syntaxe est la suivante : #ifdef symbole partie-du-programme-1 #else condition-2 partie-du-programme-2 #endif Si symbole est dni au moment o` lon rencontre la directive #ifdef, alors e u partie-du-programme-1 sera compile et partie-du-programme-2 sera ignoe re. Dans le cas contraire, cest partie-du-programme-2 qui sera compile. La e e directive #else est comme prcdemment facultative. Ce type de directive est e e utile pour rajouter des instructions destines au dbuggage du programme : e e 82

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

Il sut alors de supprimer la directive #define DEBUG pour que les instructions lies au debuggage ne soient pas compiles1 . e e De faon similaire, on peut tester la non-existence dun symbole ` laide de la c a directive #ifndef : #ifndef symbole partie-du-programme-1 #else condition-2 partie-du-programme-2 #endif On rencontre beaucoup cette directive dans le cadre de la compilation modulaire (voir chapitre 8) puisquelle permet de sassurer que les dnitions dinstructions e dans un chier den-tte ne seront eectues quune seule fois. e e

7.3.3

Loprateur defined e

Loprateur defined est un oprateur spcial : il ne peut tre utilis que dans e e e e e le contexte dune commande #if ou #elif. Il peut tre utilis sous lune des e e deux formes suivantes : defined nom defined ( nom ) Il dlivre la valeur 1 si nom est une macro dnie, et la valeur 0 sinon. Lintrt e e ee de cet oprateur est de permettre dcrire des tests portant sur la dnition de e e e plusieurs macros, alors que #ifdef ne peut en tester quune : #if defined(SOLARIS) || defined(SYSV)

7.3.4

La commande #error

La commande #error a la syntaxe suivante : #error chaine La rencontre de cette commande provoquera lmission dun message derreur e comprenant la chaine. Cette commande a pour utilit de capturer ` la compie a lation des conditions qui font que le programme ne peut pas sexcuter sur cette e plate-forme. Voici un exemple o` on teste que la taille des entiers est susante : u
#include <limits.h> #if INT_MAX < 1000000 #error "Entiers trop petits sur cette machine" #endif
1 A noter aussi que comme on la vu prcedemment, on peut remplacer cette derni`re e e directive par loption de compilation -DDEBUG, qui permet de dnir ce symbole. e

83

7.3.5

La commande #pragma

La directive #pragma est lie ` une implmentation spcique et permet de de a e e e nir, pour un compilateur donn, une directive de prtraitement. Sa syntaxe e e est la suivante : #pragma commande

84

Chapitre 8

La programmation modulaire
D`s que lon crit un programme de taille importante ou destin ` tre utilis e e eae e et maintenu par dautres personnes, il est indispensable de se xer un certain nombre de r`gles dcriture. En particulier, il est ncessaire de fractionner le e e e programme en plusieurs chiers sources, que lon compile sparemment. e Ces r`gles dcriture ont pour objectifs de rendre un programme lisible, pore e table, rutilisable mais surtout facile ` maintenir et ` modier. e a a Lide est alors de regrouper dans un mme chier les instructions implmentant e e e des fonctionnalits similaires (par exemple, tab_management.c contiendra une e biblioth`que de fonctions grant les tableaux, file_management.c fournira une e e biblioth`que de fonctions grant les chiers dans le contexte du projet eectu e e e et main.c contiendra la dnition de la fonction main). La programmation e modulaire consiste alors ` oprer les manipulations ncessaires permettant de a e e lier ces chiers.

8.1

Principes lmentaires ee

Trois principes essentiels doivent guider lcriture dun programme C. Ces prine cipes sappliquent en fait dans le cas gnral du gnie logiciel. e e e 1. Labstraction des constantes littrales e Lutilisation explicite de constantes littrales dans le corps dune fonce tion rend les modications et la maintenance diciles. Des instructions comme :
fopen("mon_fichier", "r"); perimetre = 2 * 3.14 * rayon;

sont ` proscrire (il faudrait dnir des constantes fournissant le nom du a e chier ou la valeur de ). Sauf cas tr`s particuliers, les constantes doivent e tre dnies comme des constantes symboliques au moyen de la directive e e #define. 2. La factorisation du code Le but est dviter les duplications de code. La prsence dune mme pore e e tion de code ` plusieurs endroits du programme est un obstacle ` dvena a e 85

tuelles modications. Les fonctions doivent donc tre systmatiquement e e utilises pour viter la duplication de code. Il ne faut pas craindre de e e dnir une multitude de fonctions de petite taille. e 3. La fragmentation du code Pour des raisons de lisibilit, il est pertinent de dcouper un programme e e en plusieurs chiers. En plus, cette r`gle permet de rutiliser facilement e e une partie du code pour dautres applications. On spare alors le programme en modules, chaque module implmentant e e des fonctions sur un th`me similaire et qui se traduiront physiquement e par deux chiers : (a) un chier en-tte (on dit aussi de header) ayant lextension .h et e contenant le prototype des fonctions principales implmentes dans e e ce module. (b) un chier source ayant lextension .c contenant non seulement le corps des fonctions dclares dans le chier en-tte mais galement e e e e celui des fonctions intermdiaires ventuellement utilises. Ce chier e e e inclut videmment le chier en-tte par le biais de la directive #include. e e Une possibilit est de placer une partie du code dans un chier en-tte e e (on dit aussi de header) ayant lextension .h que lon inclut dans le chier contenant le programme principal ` laide de la directive #include. a Lexemple suivant illustre ce principe sur un programme qui saisit deux entiers au clavier et ache leur produit. Sur cet exemple, il a t choisi de ee dnir un module arithmtique qui fournit la fonction eectuant le proe e duit1 . Ce module est donc implment dans les chiers arithmtique.h e e e et arithmtique.c. Le programme est donc compos de trois chiers : e e les 2 chiers du module et le chier principale contenant la fonction main appel ici main.c : e
/*********************************************/ /* fichier: main.c */ /* saisit 2 entiers et affiche leur produit */ /*********************************************/ #include <stdlib.h> #include <stdio.h> #include "arithmetique.h" // Le module darithmetique int main(void) { int a, b, c; scanf("%d",&a); scanf("%d",&b); c = produit(a,b); // appel de la fonction dfinie dans arithmetique.h e printf("\nle produit vaut %d\n",c); return EXIT_SUCCESS; } /********************************************************/
1

Il sagit videmment dun exemple tr`s simpliste ! e e

86

/* fichier: arithmetique.h */ /* G`re les oprations arithmtiques sur les entiers e e e */ /* (Ici, seul le produit est implment e e */ /********************************************************/ int produit(int a, int b); /*****************************************************/ /* fichier: arithmetique.c */ /* Corps des fonctions dfinies dans arithmetique.h */ e /*****************************************************/ #include "arithmetique.h" int produit(int a, int b) { return(a * b); }

Remarquer que cest exactement la procdure utilise pour les fonctions de e e la librairie standard : les chiers en-tte .h de la librairie standard sont e constitus de dclarations de fonctions et de dnitions de constantes e e e symboliques (la seule information importante pour un utilisateur) et le corps des fonctions en lui-mme est spar. e e e

8.2

Eviter les erreurs dinclusions multiples

Une derni`re r`gle, tr`s importante, consiste ` viter les erreurs de double e e e a e dnition de fonctions qui se traduise ` ldition de lien par le message suivant e a e (avec gcc) :
toto.o:toto.c multiple definition of ... toto.o: first defined here

Cette erreur se produit en gnral lors de linclusion multiple dun mme chier e e e en-tte. e Pour viter cela, la mthode consiste ` dnir une constante ` la premi`re ine e a e a e clusion du chier en-tte et dignorer le contenu de celui-ci si la constante a dj` e ea t dnie (donc si on a dj` fait une inclusion). Pour cela, on utilise la direcee e ea tive #ifndef (voir 7.3.2). Il est recommand dappeler la constante __TOTO_H e pour le chier toto.h En appliquant cette r`gle, le chier arithmetique.h de e lexemple prcdent devient : e e
/********************************************************/ /* fichier: arithmetique.h */ /* G`re les oprations arithmtiques sur les entiers e e e */ /* (Ici, seul le produit est implment e e */ /********************************************************/ #ifndef __ARITHMETIQUE_H #define __ARITHMETIQUE_H int produit(int a, int b); #endif

Je vous recommande de toujours appliquer cette derni`re r`gle d`s e e e lors que vous crez un chier de header ! e 87

8.3

La compilation spare e e

Ce nest pas le tout de bien fragmenter son code en plusieurs chiers, encore faut-il les compiler pour obtenir un executable. La mthode consiste ` gnrer un chier objet par module (option -c de gcc) : e a e e
gcc -O3 -Wall -I. -c module_1.c gcc -O3 -Wall -I. -c module_2.c . . . gcc -O3 -Wall -I. -c module_n.c

Ces commandes gn`rent n chiers objets module_i.o. e e Loption -I de gcc permet dajouter un rpertoire en premi`re position de la e e liste des rpertoires o` sont cherchs les chiers en-tte. (ici, le rpertoire coue u e e e rant, ./ : loption -I est donc en fait facultative dans ce cas). Cette option est utile lorsque, pour des raisons de lisibilit dans larchitecture des chiers e sources, un rpertoire Include est cr pour contenir tous les chiers en-tte e ee e du programme. La compilation avec gcc comportera alors loption -IInclude. Une passe ddition de lien entre ces chiers objets en ensuite ncessaire pour e e gnrer lexcutable nal toto.exe : e e e
gcc -o toto.exe module_1.o module_2.o ... module_n.o

8.4

Rsum des r`gles de programmation modulaire e e e

1. Dcouper le programme en modules implmentant des fonctions similaires. e e 2. Chaque module se traduit en 2 chiers sources : un chier en-tte module.h qui dnit son interface, cest ` dire le e e a prototype des fonctions du module qui sont exportes. Plus prcisment, e e e ce chier se compose : des dclarations des fonctions dinterface (celles qui sont exportes et e e donc utilises dans dautres chiers sources) e dventuelles dnitions de constantes symboliques et de macros. e e dventuelles directives au prprocesseur (inclusion dautres chiers, e e compilation conditionnelle). Ce chier doit respecter galement les conventions dcritures proposes e e e au 8.2 pour viter les erreurs de dnitions multiples. e e un chier module.c contenant le corps des fonctions implmentes dans e e ce module. Ce chier se compose : de variables globales qui ne sont utilises que dans le chier module.c ; e du corps des fonctions dinterface dont la dclaration se trouve dans e module.h ; dventuelles fonctions locales ` module.c. e a 3. Le chier module.h est inclus dans le chier module.c (via la directive #include) et dans tous les autres chiers qui font appel aux fonctions exportes par ce module. e 4. Chaque module est compil pour gnrer lexcutable nal (voir 8.3). e e e e 88

Ces r`gles sajoutent aux r`gles gnrales dcriture de programmes C abordes e e e e e e au 1.6 page 9.

8.5

Loutils make

Losrquun programme est fragment en plusieurs chiers sources compils se e e paremment, la procdure de compilation peut tr`s vite devenir longue et fastie e dieuse. Il est alors extr`mement pratique de lautomatiser ` laide de lutilitaire make e a dUnix. Une bonne utilisation de make permet de rduire le temps de compilae tion et galement de garantir que celle-ci est eectue correctement. e e

8.5.1

Principe de base

Lide principale de make est deectuer uniquement les tapes de compilation e e ncessaires ` la cration dun excutable. Par exemple, si un seul chier source e a e e a t modi dans un programme compos de plusieurs chiers, il sut de ee e e recompiler ce chier et deectuer ldition de liens. Les autres chiers sources e nont pas besoin dtre recompils. e e La commande make recherche par dfaut dans le rpertoire courant un chier e e makefile ou Makefile. Ce chier spcie les dpendances entre les dirents e e e chiers sources, objets et excutables. e Voyons tout de suite les principales options de cette commande (les plus utiles ; pour plus de dtails : man make) e make -f <nom_fichier> : utiliser le chier <nom_fichier> ` la place du a traditionnel chier Makefile. make -C <path> : excuter le chier Makefile situ dans le rpertoire <path>. e e e

8.5.2

Cration dun Makefile e

Un chier Makefile est compos dune liste de r`gles de dpendances entre e e e chiers, de la forme : cible: liste_de_dpendances e <TAB> commandes_a_effectuer La premi`re ligne spcie un chier cible (gnr par commandes_a_effectuer) e e e ee et la liste des chiers dont il dpend (spars par des espaces) : il sagit donc e e e de la liste des ciers requis pour la gnration de cible. e e Les lignes suivantes, qui doivent commencer par une tabulation (le caract`re e TAB), indiquent les commandes ` excuter pour gnrer le chier cible. Ces a e e e commandes ne seront excutes que si le chier cible nexcute pas ou si lun e e e des chiers de dpendance est plus rcent que le chier cible. e e De faon gnrale, lcriture dun chier Makefile se rapproche largement de c e e e lcriture dun script shell : e On peut placer des commentaires, qui commencent par # : #Ceci est un commentaire Les commentaires stendent sur une seule ligne (quivalent du // en C99). e e 89

On peut declarer des variables. Une variable se dclare de la faon suivante : SRC = machin.c e c Ensuite, le contenu de cette variable (machin.c) est obtenu par lappel $(SRC) Ainsi, considrons lexemple du programme eectuant le produit de deux entiers e prsent au ??. On souhaite gnrer lexcutable toto. e e e e e Pour une compilation ` la main et en suivant les recommendations du 1.1.4, a il faudrait excuter les commandes : e gcc -g3 -Wall -c produit.c gcc -g3 -Wall -c main.c gcc -g3 -Wall -o toto main.o produit.o On laura compris : cela peut devenir tr`s vite long et lassant ! e Un premier chier Makefile grant cette compilation pourrait tre : e e
## Premier exemple de Makefile # il faut generer lexecutable toto all: toto # Le fichier produit.o dpend de produit.c et de produit.h e produit.o: produit.c produit.h gcc -g3 -Wall -c produit.c # Le fichier main.o dpend de main.c et de produit.h e main.o: main.c produit.h gcc -g3 -Wall -c main.c # Generation de lexcutable toto qui dpend des fichiers objets e e toto: main.o produit.o gcc -g3 -Wall -o toto main.o produit.o

Moyennant lcriture de ce chier de conguration, il sura ensuite dexcuter e e la commande make pour lancer la compilation du programme toto !

8.5.3

Utilisation de macros et de variables

On suppose maintenant quon souhaite en plus que les chiers de header soient placs dans le rpertoire include/ et les chiers objets dans le rpertoire obj/. e e e Les chiers sources (.c) sont quand ` eux dans le rpertoire courant et on a e souhaite quil en soit de mme pour lexcutable nal (toto). e e Larborescence gnral des chiers et le graphe des dpendances entre les chiers e e e est expos dans la gure 8.1. e Avec une telle arborescence, on peut modier le Makefile prcdent pour grer e e e la compilation du programme toto de la faon suivante : c
## Deuxieme exemple de Makefile - gestion dune arborescence all: toto obj/produit.o: produit.c include/produit.h gcc -g3 -Wall -Iinclude/ -c produit.c -o obj/produit.o obj/main.o: main.c include/produit.h gcc -g3 -Wall -Iinclude/ -c main.c -o obj/main.o toto: obj/main.o obj/produit.o

90

ARBORESCENCE projet/ include/


produit.h

GRAPHE DES DEPENDANCES

toto

Excutable

obj/
main.o produit.o

.o main.o

.o produit.o

Fichiers Objets

.c
main.c produit.c toto Makefile

.h produit.h

.c produit.c

main.c

Fichiers Sources

Fig. 8.1 Arborescence et graphe de dpendances entre les chiers e


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

On voit quun certain nombre dinformations sont redondantes. En particulier, si on change le nom dun rpertoire, il faut le changer ` plusieurs endroit dans e a le Makefile dou lide de centraliser ce type dinformations. On utilise pour e cela des variables :
## Troisieme exemple de Makefile - gestion dune arborescence # Utilisation de variables EXE = toto OBJ_DIR = obj H_DIR = include OPT = -g3 -Wall all: $(EXE) $(OBJ_DIR)/produit.o: produit.c $(H_DIR)/produit.h gcc $(OPT) -Iinclude/ -c produit.c -o $(OBJ_DIR)/produit.o $(OBJ_DIR)/main.o: main.c $(H_DIR)/produit.h gcc $(OPT) -Iinclude/ -c main.c -o $(OBJ_DIR)/main.o $(EXE): $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o gcc $(OPT) -o $(EXE) $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o

Mais la encore, on continue ` dupliquer un certain nombre dinformations. Pour a cela, il existe un certain nombre de variables prdnies, dont les les plus utiles e e sont rsumes dans le tableau 8.1. e e A partir de ces variables, on obtient une version beaucoup plus simple du Makele prcdent : e e
## Quatrieme exemple de Makefile - gestion dune arborescence # Utilisation des variables predefinies EXE = toto OBJ_DIR = obj H_DIR = include

91

$@ $* $< $?

Le chier cible (par exemple : obj/main.o) Le chier cible sans suxe (ex : obj/main) Le premier chier de la liste des dpendances (par exemple : main.c) e Lensemble des chiers de dpendances e Tab. 8.1 Les principales variables prdnies dans un Makefile e e

OPT = -Wall -g3 all: $(EXE) $(OBJ_DIR)/produit.o: produit.c $(H_DIR)/produit.h gcc $(OPT) -I$(H_DIR)/ -c $< -o $@ $(OBJ_DIR)/main.o: main.c $(H_DIR)/produit.h gcc $(OPT) -I$(H_DIR)/ -c $< -o $@ $(EXE): $(OBJ_DIR)/main.o $(OBJ_DIR)/produit.o gcc $(OPT) -o $(EXE) $?

A noter que pour dterminer facilement les dpendances entre les dirents e e e chiers, on peut utiliser loption -MM de gcc. Par exemple, % gcc -MM -Iinclude/ *.c main.o: main.c include/produit.h produit.o: produit.c include/produit.h On rajoute habituellement dans un chier Makefile une cible appele clean e permettant de dtruire tous les chiers objets et une cible distclean qui supe prime galement les excutables gnrs lors de la compilation. e e e ee clean: rm -f $(OBJ_DIR)/*.o distclean: clean rm -f $(EXE) La commande make clean permet donc de nettoyer le rpertoire courant. Noe tons que lon utilise ici la commande rm avec loption -f qui vite lapparition e dun message derreur si le chier ` dtruire nexiste pas. a e Il y aurait encore beaucoup de choses ` dire sur les Makefile mais cela dpasse a e largement le cadre de ce polycopi. La meilleur documentation ` ce sujet se e a trouve ` lURL http://www.gnu.org/software/make/manual/make.html. a Pour conclure, on notera que tous les exemples de Makele proposs doivent e tre modis d`s lors quun nouveau chier est ajout au projet. Un exemple e e e e de Makele gnrique (dans le sens o` il na pas besoin dtre modi lors de e e u e e lajout de nouveau chiers) est fourni an annexe B page 112. Pour rutiliser ce e Makele dans nimporte quel projet, il sut dadapter les variables $(INCL), $(DIR_OBJ) et $(EXE). Pour plus dinformations, il sut de taper make help. 92

Chapitre 9

La biblioth`que standard e
Ce chapitre est un aide-mmoire qui donne la liste exhaustive de toutes les e fonctions de la biblioth`que standard, accompagnes la plupart du temps de e e leurs prototypes. Les chiers den-tte de la librairie ANSI sont les suivants (ceux correspone dants ` de nouvelles fonctionnalits introduites dans la norme ANSI C99 sont a e indiques par un astrisque entre parenth`ses (*). e e e assert.h complex.h(*) ctype.h errno.h fenv.h(*) float.h inttypes.h(*) iso646.h(*) limits.h locale.h math.h setjmp.h signal.h stdarg.h stdbool.h(*) stddef.h stdint.h(*) stdio.h stdlib.h string.h tgmath.h(*) time.h wchar.h(*) wctype.h(*)

De mani`re gnrale, pour obtenir le maximum dinformations sur une come e e mande, on utilisera la commande man.

9.1

Diagnostics derreurs <assert.h>

Cette biblioth`que ne fournit quune seule fontion, assert, qui permet de mettre e des assertions dans le source du programme. Son prototype est le suivant : void assert(int expression); ` A lexcution, si le param`tre de assert svalue ` faux, le programme est e e e a stopp sur terminaison anormale. Exemple : e
#include <assert.h> #include <stdio.h> #define TAILLE 512 int calcul(int tab[], int i) { assert(i >= 0 && i < TAILLE); return 2 * tab[i] + 5; }

93

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

Lexcution de ce programme renverra : e DEBUG: tableau[0] = 180613 a.out: toto.c:7: calcul: Assertion i >= 0 && i < 512 failed. Abandon

9.2

Gestion des nombres complexes <complex.h>

Cette biblioth`que permet de grer les nombres complexes, de la forme e e z = a + i b, o` a, b R. On rappelle que est la partie relle de z, b sa partie u a e imaginaire et i le nombre imaginaire i = 1 (i2 = 1). man complex pour des exemples de code utilisant ces nombres complexes.

9.3

Classication de caract`res et changements de e casse <ctype.h>

Toutes ces fonctions permettent de tester une proprit sur le caract`re pass ee e e en param`tre : e
Fonction isalnum isalpha isblank iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit Prototype int isalnum(int c) int isalpha(int c) int isblank(int c) int iscntrl(int c) int isdigit(int c) int isgraph(int c) int islower(int c) int isprint(int c) int ispunct(int c) int isspace(int c) int isupper(int c) int isxdigit(int c) Teste si le param`tre est e une lettre ou un chire une lettre un espace ou une tabulation un caract`re de commande e un chire dcimal e un caract`re imprimable ou le blanc e une lettre minuscule un caract`re imprimable (pas le blanc) e un signe de ponctuation un caract`re despace blanc e une lettre majuscule un chire hexadcimal e

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


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

94

9.4

Valeur du dernier signal derreur <errno.h>

Cette biblioth`que ne dnit quune variable globale, e e extern int errno; dont la valeur est un numro derreur aect lors dappels syst`mes (et par e e e certaines fonctions de librairie). Cette valeur na de sens que si lappel syst`me e sest mal pass et retourne une erreur (la valeur -1). Dans ce cas, la variable e errno indique quel type derreur sest produite. man errno fournit la liste des valeurs derreurs valides.

9.5

Gestion dun environnement ` virgule ottante a <fenv.h>

Le standard C99 a introduit via <fenv.h> la notion denvironnement a vir` gule ottante an de permettre une reprsentation plus dtaille des conditions e e e derreurs dans larithmtique en virgule ottante. e Deux variables syst`mes sont dnies dans cet environnement : e e 1. les ags de statut, utiliss dans la gestion des exceptions en virgule ote tante. 2. les modes de contrle qui dterminent les dirents comportements de o e e larithmtique en virgule ottante (la mthode darrondi utilise par exemple). e e e

9.5.1

Gestion des exceptions

Toute erreur (exception) en virgule ottante est caractrise par une constante e e symbolique : FE_DIVBYZERO : division par 0 ; FE_INEXACT : le rsultat de lopration nest pas exact ; e e FE_INVALID : rsultat indni ; e e FE_OVERFLOW : dpassement de capacit ; e e FE_UNDERFLOW : sous-dpassement de capacit. e e La constante FE_ALL_EXCEPT est un OU bit-`-bit de toutes les constantes pra e cdentes. e Les fonctions suivantes sont utilises pour grer ces exceptions. Toutes (` lexe e a ception de fetestexcept) retournent 0 en cas de succ`s. Largument excepts e indique lune des constantes prcdentes. e e
Fonction feclearexcept fegetexceptflag feraiseexcept fesetexceptflag fetestexcept Prototype int feclearexcept(int excepts) int fegetexceptflag(fexcept_t *flagp, int excepts) int feraiseexcept(int excepts) int fesetexceptflag(fexcept_t *flagp, int excepts) int fetestexcept(int excepts) Action Eace les exceptions excepts Sauvegarde le statut dexception spci dans *flagp e e Dclenche lexception expects e Sauvegarde le statut de lexception expects dans *flagp renvoie les bits correspondants aux exceptions courantes

95

9.5.2

Gestion des arrondis

fenv.h supporte quatre mthodes darrondis, caractrises par les constantes e e e symboliques suivantes : FE_DOWNWARD : arrondi vers la valeur infrieure la plus proche ; e FE_TONEAREST : arrondi vers la valeur la plus proche ; FE_TOWARDZERO : partie enti`re ; e FE_UPWARD : arrondi vers la valeur suprieure la plus proche. e Les fonctions int fegetround() et int fesetround( int rounding_mode) permettent de lire et de changer la mthode darrondi courante. e

9.5.3

Gestion des environnements en virgule ottante.

Lenvironnement de travail en virgule ottante peut tre manipul via les fonce e tions suivantes sous forme dun seul objet opaque de type fenv_t. Lenvironnement par dfaut est reprsent par la constante FE_DFL_ENV. e e e
Fonction fegetenv feholdexcept fesetenv feupdateenv Prototype int fegetenv(fenv_t *envp) int feholdexcept(fenv_t *envp) int fesetenv(fenv_t *envp) int feupdateenv(fenv_t *envp) Action Sauve lenvironnement courant dans *envp idem, et eace tous les ags dexceptions et continue en mode sans interruption recharge lenvironnement ` *envp a idem et replace les exceptions dj` prsentes ea e dans *envp

9.6

Intervalle et prcision des nombres ottants <float.h> e

float.h dnit des constantes symboliques qui caractrisent les types rels, en e e e particulier :
FLT_DIG DBL_DIG LDBL_DIG FLT_EPSILON DBL_EPSILON LDBL_EPSILON FLT_MAX DBL_MAX LDBL_MAX FLT_MIN DBL_MIN LDBL_MIN nombre de chires signicatifs dun float nombre de chires signicatifs dun double nombre de chires signicatifs dun long double le plus petit nombre tel que 1.0 + x != 1.0 le plus petit nombre tel que 1.0 + x != 1.0 le plus petit nombre tel que 1.0 + x != 1.0 le plus grand nombre du type float le plus grand nombre du type double le plus grand nombre du type long double le plus petit nombre du type float le plus petit nombre du type double le plus petit nombre du type long double

9.7

Dnitions de types entiers de taille xe <inte e types.h>

La biblioth`que <inttypes.h> dnit au moins les types suivants : e e 96

int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t intptr_t uintptr_t

type entier sign sur 8 bits e type entier sign sur 16 bits e type entier sign sur 32 bits e type entier sign sur 64 bits e type entier non sign sur 8 bits e type entier non sign sur 16 bits e type entier non sign sur 32 bits e type entier non sign sur 64 bits e entier sign pouvant stocker un pointeur (une adresse) e entier non sign pouvant stocker un pointeur (une adresse) e

En pratique, ces types sont dnis dans stdint.h (voir 9.17). e

9.8

Alias doprateurs logiques et binaires <iso646.h> e

<iso646.h> dnit des alias (synonymes) aux oprateurs binaires et logiques : e e and bitand compl not_eq or_eq xor_eq && & ~ != |= ^= and_eq bitor not or xor &= | ! || ^

9.9

Intervalle de valeur des types entiers <limits.h>

Le chier <limits.h> dnit les constantes symboliques qui prcisent les valeurs e e maximales et minimales qui peuvent tre reprsentes par un type entier donn. e e e e
Type char signed char short int long long long Minimum CHAR_MIN SCHAR_MIN SHRT_MIN INT_MIN LONG_MIN LLONG_MIN Maximum CHAR_MAX SCHAR_MAX SHRT_MAX INT_MAX LONG_MAX LLONG_MAX Maximum (types non-signs) e UCHAR_MAX USHRT_MAX UINT_MAX ULONG_MAX ULLONG_MAX

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

9.10

Gestion de lenvironnement local <locale.h>

Il y a deux fonctions permettant de grer les conventions nationales concernant e lcriture du point dcimal dans les nombres, le signe reprsentant lunit moe e e e ntaire etc... Ces fonctions sont setlocale et localeconv (faire un man sur ces e fonctions pour plus de dtails). e 97

9.11

Les fonctions mathmatiques de <math.h> e

Pour utiliser les fonctions de cette librairie, il faut en plus de linclusion de la librairie par la directive #include <math.h> ajouter loption -lm a ldition de e lien. Ainsi, la compilation de prog.c utilisant la librairie <math.h> se fera par la commande : gcc -g3 -Wall -lm prog.c -o prog Le rsultat et les param`tres de toutes ces fonctions sont de type double. Si e e les param`tres eectifs sont de type float, ils seront convertis en double par le e compilateur.

9.11.1

Fonctions trigonomtriques et hyperboliques e


Fonction acos asin atan cosh tanh Smantique e arc cosinus arc sinus arc tangente cosinus hyperbolique tangente hyperbolique Fonction cos sin tan sinh atan2 Smantique e cosinus sinus tangente sinus hyperbolique arc tangente

9.11.2

Fonctions exponentielles et logarithmiques


Fonction exp frexp ldexp log log10 modf Smantique e exponentielle tant donn x, trouve n et p tels que x = n * 2p e e multiplie un nombre par une puissance enti`re de 2 e logarithme logarithme dcimal e calcule partie enti`re et dcimale dun nombre e e

9.11.3

Fonctions diverses
Fonction ceil fabs floor fmod pow sqrt Smantique e entier le plus proche par les valeurs suprieures e valeur absolue entier le plus proche par les valeurs infrieures e reste de division puissance racine carre e

9.12

Branchements non locaux <setjmp.h>

Linstruction goto prsente au 2.2.6 ne permet de raliser des branchements e e e quau sein dune mme procdure. Pour raliser des branchements ` lextrieur e e e a e dune procdure, il faut utiliser setjmp et longjmp. e Prototype de ces fonctions : int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); Plus prcisment, les fonctions setjmp et longjmp permettent de grer les ere e e reurs et les interruptions rencontres dans des routines bas-niveau. setjmp saue 98

vegarde le contexte de pile et denvironnement dans env an de lutiliser ulte rieurement avec longjmp. Pour plus de dtails, utiliser le man sur ces fonctions. e

9.13

Manipulation des signaux <signal.h>

Lorsquun v`nement exceptionnel se produit, le syst`me dexploitation peut e e e envoyer un signal aux processus. Ceci peut arriver en cas derreur grave (une erreur dadressage par exemple). Le tableau suivant fournit la liste de quelques constantes symboliques dnies dans <signal.h> et caractrisant ces erreurs : e e
Signal SIGABRT SIGFPE SIGILL SIGINT SIGSEGV SIGTERM Signication Fin anormale du programme (ex : utilisation de la fonction abort()) Exception ` la virgule ottante. a Instruction illgale rencontre dans le code machine. e e Interruption par la touche Ctrl-C. Violation de Segmentation : acc`s illgal ` la mmoire. e e a e Terminer : requte pour terminer le programme. e

Deux fonctions permettent dinteragir avec le mcanisme des signaux : e


Fonct. raise signal Prototype int raise(int sig) typedef void (*sigh_t)(int) ; sigh_t signal(int signum, sigh_t handler) ; Action Envoie le signal sig au processus excutant. e prcise la rponse du programme au signal e e signum. handler est un pointeur vers la fonction eectuant le traitement de ce signal.

Comme toujours, le man reste la meilleur source dinformations sur ce sujet.

9.14

Nombre variable de param`tres <stdarg.h> e

Si on dsire programmer une fonction avec un nombre variable de param`tres e e (comme printf), on dispose des macros va_start, va_arg et va_end. Lutilisation de ces macros et du type associ va_list est abord en dtail dans e e e le 5.7 page 67.

9.15

Dnition du type boolen <stdbool.h> e e

La librairie <stdbool.h>, ajoute avec la norme ANSI C99, introduit la notion e de boolen et dnit le type boolen bool et les valeurs associes, true et false. e e e e

9.16

Dnitions standards <stddef.h> e

Cette librairie introduit un ensemble de types prdnis tr`s utiles qui sont e e e lists dans le tableau suivant : e
Type ptrdiff_t wchar_t size_t Description Dirence entre deux pointeurs ( int en gnral) e e e Employ pour les caract`res tendus (voir 9.23) e e e Taille dun objet en nombres doctets ( unsigned int en gnral) e e

99

<stddef.h> dnit galement le pointeur NULL, utilis lors de linitialisation des e e e pointeurs (cf chapitre 3) ainsi que la fonction : size_t offsetof(type, member-designator) qui permet de conna le dcalage (en nombre doctets) entre un champ dune tre e structure (member-designator) et le dbut de cette structure (type). Ainsi, e si on suppose dnie la structure toto ayant un champ next (voir 4.3.5), le e dcalage du champ data est donn par lexpression : e e offsetof (struct toto, data)

9.17

Dnitions de types entiers <stdint.h> e

La libraisie <stdint.h> est un sous ensemble de la librairie <inttypes.h> (voir 9.7) et en constitue donc une version allge, adapte aux environnements eme e e barqus qui peuvent ne pas supporter les fonctions dentres/sorties formattes. e e e

9.18
9.18.1

Entres-sorties <stdio.h> e
Manipulation de chiers
Prototype FILE *fopen(char *path, char *mode) int fclose(FILE *fp) int fflush(FILE *stream) FILE *tmpfile (void) Action (voir chapitre 6) ouverture dun chier Fermeture dun chier criture des buers en mmoire dans le chier e e cration dun chier temporaire e

Fonction fopen fclose fflush tmpfile

9.18.2
Fonction fprintf fscanf printf scanf sprintf sscanf

Entres et sorties formates e e


Prototype int fprintf(FILE *stream, char *format, ...) int fscanf(FILE *stream, char *format, ...) int printf(char *format, ...) int scanf(char *format, ...) int sprintf(char *s, char *format, ...) int sscanf(char *s, char *format, ...) Action (voir 2.5/chapitre 6) criture sur un chier e lecture depuis un chier criture sur la sortie standard e lecture depuis lentre standard e criture dans la cha de caract`res s e ne e lecture depuis la cha de caract`res s ne e

9.18.3
Fonction fgetc fputc getc putc getchar putchar fgets fputs gets puts

Impression et lecture de caract`res e


Prototype int fgetc(FILE *stream) int fputc(int c, FILE *stream) int getc(FILE *stream) int putc(int c, FILE *stream) int getchar(void) int putchar(int c) char *fgets(char *s, FILE *stream) int *fputs(char *s, FILE *stream) char *gets(char *s) int *puts(char *s) Action lecture dun caract`re depuis un chier e criture dun caract`re sur un chier e e quivalent de fgetc mais optimise e e quivalent de fputc mais optimise e e lecture dun caract`re depuis stdin e criture dun caract`re sur stdout e e lecture dune cha de caract`res depuis un chier ne e criture dune cha de caract`res sur un chier e ne e lecture dune cha de caract`res sur stdin ne e criture dune cha de caract`res sur stdout e ne e

100

9.19
9.19.1

Utilitaires divers <stdlib.h>


Allocation dynamique

Ces fonctions sont dcrites dans le chapitre 3 et au 3.5. e


Fonct. calloc malloc realloc free Prototype void *calloc(size_t n, size_t size) void *malloc(size_t size) void *realloc(void *ptr, size_t size) void free(void *ptr) Action allocation dynamique de n*size octects et initialisation ` zro. a e allocation dynamique modie la taille dune zone pralablee ment alloue par calloc ou malloc e lib`re une zone mmoire e e

9.19.2

Conversion de cha nes de caract`res en nombres e

Les fonctions suivantes permettent de convertir une cha de caract`res en un ne e nombre.


Fonct. atof atoi atol Prototype double atof(char *chaine) int atoi(char *chaine) long atol(char *chaine) Action convertit chaine en un double convertit chaine en un int convertit chaine en un long int

Il semblerait que quil faille prfrer maintenant les fonctions strtol, strtoll ee et strtoq.

9.19.3

Gnration de nombres pseudo-alatoires e e e

La fonction rand fournit un nombre entier pseudo-alatoire dans lintervalle e [0,RAND_MAX]. RAND_MAX est une constante prdnie au moins gale ` 215 1. e e e a Lala fournit par la fonction rand nest toutefois pas de tr`s bonne qualit. e e e La valeur retourne par rand dpend de linitialisation (germe ou seed en ane e glais) du gnrateur. Cette derni`re est gale ` 1 par dfaut mais elle peut tre e e e e a e e modie ` laide de la fonction srand. e a
Fonct. rand srand Prototype int rand(void) void srand(unsigned int seed) Action fournit un nombre pseudo-alatoire e modie la valeur du germe du gne e rateur pseudo-alatoire e

On utilise souvent la valeur de lhorloge interne de lordinateur (obtenue ` laide a de la librairie <time.h>, voir 9.22) pour fournir un bon1 germe ` srand. a Exemple :
#include <stdlib.h> #include <time.h> //see also man 3 rand

/* initialize random generator */ void init_rand(){ srand(time(NULL)); }


1

Pas susamment bon cependant pour les applications cryptographiques

101

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

9.19.4

Arithmtique sur les entiers e

Fonct. abs labs div ldiv

Prototype int abs(int n) long labs(long n) div_t div(int a, int b) ldiv_t ldiv(long a, long b)

Action valeur absolue dun entier idem mais pour un long int quotient et reste de la division euclidienne de a par b. idem pour les long int

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

9.19.5

Recherche et tri

Les fonctions qsort (resp. bsearch) permettent de trier un tableau (resp. rechercher un lment dans un tableau dj` tri). Pour leurs syntaxes : voir 5.8. ee ea e

9.19.6

Communication avec lenvironnement

Fonct. abort exit system

Prototype void abort(void) void exit(int etat) int system(char *s)

Action terminaison anormale du programme terminaison du programme ; rend le contrle au syst`me o e en lui fournissant la valeur etat (0 = n normal). excution de la commande syst`me dnie par s. e e e

102

9.20
Fonct. strcpy strncpy strcat strncat strcmp strncmp strchr strrchr strstr strlen

Manipulation de cha nes de caract`res <string.h> e


Prototype char *strcpy(char *ch1,char *ch2) char *strcpy(char *ch1,char *ch2, int n) char *strcat(char *ch1,char *ch2) char *strncat(char *ch1,char *ch2, int n) int strcmp(char *ch1,char *ch2) int strcmp(char *ch1,char *ch2, int n) char *strchr(char *ch,char c) char *strchr(char *chaine,char c) char *strchr(char *ch1,char *ch2) int strlen(char *chaine) Action copie ch2 dans ch1 ; retourne ch1. idem mais ne copie que n caract`res. e copie ch2 ` la n de ch1 ; retourne ch1. a idem mais seuls n caract`res sont copis e e compare ch1 et ch2 pour lordre lexicographique ; idem mais seuls n caract`res sont compars. e e retourne un pointeur sur la premi`re occue rence de c dans ch et NULL si c ch. / retourne un pointeur sur la derni`re occue rence de c dans ch et NULL si c ch / retourne un pointeur sur la premi`re occue rence de ch2 dans ch1 et NULL si ch2 ch1 / retourne la longueur de chaine.

La librairie <string.h> fournit un ensemble de fonctions permettent de grer e les cha nes de caract`res pour : e copier : memcpy, memmove, strcpy, strncpy ; concatner : strcat, strncat ; e comparer : memcmp, strcmp, strcoll, strncmp ; transformer : strxfrm ; rechercher : memchr, strchr, strcspn, strpbrk, strrchr, strspn, strstr, strtok ; initialiser : memset ; mesurer : strlen ; obtenir un message derreur ` partir du numro de lerreur : strerror. a e Concernant strcmp, on notera que le rsultat de cette fonction est e une valeur ngative si ch1 est infrieure ` ch2 (pour lordre lexicographique) ; e e a 0 si ch1 et ch2 sont identiques ; une valeur positive sinon.

9.21

Macros gnriques pour les fonctions mathmae e e tiques <tgmath.h>

Cette librairie, introduite avec la norme ANSI C99, est un sur-ensemble des librairies <math.h> et de <complex.h> et dnit pour chacune des fonctions e communes ` ces deux librairie une version similaire dnie sous forme de macro a e pour un type gnrique. La liste de ces fonctions est donne dans le tableau e e e suivant : 103

<math.h> Fonction acos() asin() atan() acosh() asinh() atanh() cos() sin() tan() cosh() sinh() tanh() exp() log() pow() sqrt() fabs()

<complex.h> Function cacos() casin() catan() cacosh() casinh() catanh() ccos() csin() ctan() ccosh() csinh() ctanh() cexp() clog() cpow() csqrt() cabs()

Type-gnrique e e Macro acos() asin() atan() acosh() asinh() atanh() cos() sin() tan() cosh() sinh() tanh() exp() log() pow() sqrt() fabs()

Pour un complment dinformations : e


http://www.opengroup.org/onlinepubs/009695399/basedefs/tgmath.h.html

9.22

Date et heure <time.h>

Plusieurs fonctions permettent dobtenir la date et lheure. Le temps est reprsent par des objets de type time_t ou clock_t, lesquels correspondent e e gnralement ` des int ou a des long int. Les fonctions de manipulation de e e a ` la date et de lheure sont : clock, difftime, mktime, time, asctime, ctime, gmtime, localtime, strftime. Les plus utiles sont listes dans le tableau suivant : e
Fonct. time difftime ctime clock Prototype time_t time(time_t *tp) double difftime(time_t t1, time_t t2) char *ctime(time_t *tp) clock_t clock(void) Action retourne dans *tp le nombre de secondes coules e e depuis le 1er janvier 1970, 0 heures G.M.T. retourne la dirence t1 - t2 en secondes. e convertit *tp en une cha de caract`res explicine e tant la date et lheure (format prdtermin). e e e renvoit le temps CPU (ms) depuis le dernier clock.

9.23

Manipulation de caract`res tendus <wchar.h> e e et <wctype.h>

Le type char est cod sur 8 bits et le restera parce quil dsigne la plus petite e e unit de donnes adressable. e e Dans le cadre du design dapplications multilingues, on devra donc grer des e caract`res et des cha e nes au format UNICODE. 104

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

105

Annexe A

Etude de quelques exemples


A.1 Gestion des arguments de la ligne de commande

Le code source suivant traite lexemple prsent au 4.2.6 page 50. e e Commande de compilation : gcc -O3 -Wall commande.c -o commande
/** * @file command.c * @author Sebastien Varrette <Sebastien.Varrette@imag.fr> * @date Tue Oct 4 2005 * * @brief see command.h traitement des arguments de la ligne de commande utilisant la librairie <getopt.h> Exemple trait: programme command au format dappel e suivant (les lments entre crochets sont optionnels): e e ./command [-i arg_i] [-f arg_f] [-s arg_s] [-k arg_k] [-h] [-V] arg_file Les options de la ligne de commande sont donc: -i: permet de spcifier la valeur de arg_i, de type int e Valeur par dfaut: 14 e -f: permet de spcifier la valeur de arg_f, de type float e Valeur par dfaut: 1.5 e -s: permet de spcifier la valeur de arg_s, une cha^ne de caract`res e e possdant au plus MAX_SIZE caract`res e e Valeur par dfaut: "On est super content!" e -h: affiche laide (auteur, but du programme et usage) et sort du programme. -V: affiche la version du programme et sort arg_file est un param`tre obligatoire du programme qui spcifie e e la valeur de la cha^ne de caract`res arg_file. e */ /********************************************************************************/ #include <stdio.h> /* for printf */ #include <stdlib.h> /* for exit */ #include <assert.h> /* for assert */ #include <string.h> #include <getopt.h> //#include "toto.h"

106

#define VERSION 0.1 #define MAX_SIZE 256

// source version // maximal size of strings, including \0

void printHelp(char * command); // print help message void printError(char * error_message, char * command); // print error message void printVersion(char * command); // print version /** * Entry point where the program begins its execution. * @param argc number of arguments of the command line * @param argv multi-array containing the command line arguments * (argv[0] is the name of the program, argv[argc]=NULL) * @return status of the execution (0: correct execution) */ int main(int argc, char * argv[]){ // Command line management extern char *optarg; // specific to getopt extern int optind; //, opterr, optopt; // id. char c; // to deal with options char opt_i[MAX_SIZE] = ""; char opt_f[MAX_SIZE] = ""; long arg_i = 14; double arg_f = 1.5; char arg_s[MAX_SIZE] = "On est super content!"; char arg_file[MAX_SIZE] = "";

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

107

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

// required parameter: arg_file assert(strlen(argv[optind]) < MAX_SIZE); strncpy(arg_file,argv[optind],MAX_SIZE); //Print values printf("arg_i = %ld\n",arg_i); printf("arg_f = %f\n",arg_f); printf("Valeur de arg_s: %s\n",arg_s); printf("Valeur de arg_file: %s\n",arg_file); return EXIT_SUCCESS; }

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

108

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

109

A.2

Exemple de liste cha ee n

Le code source suivant traite lexemple prsent au 4.3.5 page 53. e e Commande de compilation : gcc -O3 -Wall toto.c -o toto
/********************************************************* Fichier: toto.c Auteur: Sebastien Varrette <Sebastien.Varrette@imag.fr> Un exemple de gestion des listes chaines e *********************************************************/ #include <stddef.h> #include <stdio.h> #include <stdlib.h> /* Dfinition de la structure toto*/ e struct toto { int data; struct toto *next; }; //type liste synonyme du type pointeur vers une struct toto typedef struct toto *liste; /************************************** * Ins`re un lment en t^te de liste * e e e e **************************************/ liste insere(int element, liste Q) { liste L; L = (liste)malloc(sizeof(struct toto)); // allocation de la zone // mmoire necssaire e e L->data = element; L->next = Q; return L; } /*************************************** * Ins`re un lment en queue de liste * e e e ***************************************/ liste insereInTail(int element, liste Q) { liste L, tmp=Q; L = (liste)malloc(sizeof(struct toto)); // allocation de la zone // mmoire necssaire e e L->data = element; L->next = NULL; if (Q == NULL) return L; // maintenant tmp=Q est forcement non vide => tmp->next existe while (tmp->next != NULL) tmp = tmp->next; // deplacement jusquau // dernier elt de la liste tmp->next = L; return Q; } /*************************************** * Supprime llment en t^te de liste * e e e ***************************************/ liste supprime_tete(liste L) { liste suivant = L; if (L != NULL) { // pour etre s^r que L->next existe u suivant= L->next;

110

free(L); //libration de lespace allou pour une cellule e e } return suivant; } /***************************** * Supprime toute la liste L * *****************************/ liste supprime(liste L) { while (L != NULL) L = supprime_tete(L); //suppression de la t^te de liste e return L; } /************************************ * Affiche le contenu de la liste L * ************************************/ void print_liste(liste L) { liste tmp = L; while (tmp != NULL) { //on aurait pu crire while (tmp) e printf("%d \t",tmp->data); tmp = tmp->next; } printf("NULL\n"); } /********************************************************/ int main() { liste L; L = insere(14,insere(2,insere(3,insere(10,NULL)))); L = insereInTail(15,(insereInTail(14,L))); printf("Impression de la liste:\nL = \t"); print_liste(L); printf("Suppression de lelement en tete:\nL = \t"); L = supprime_tete(L); print_liste(L); printf("Suppression de la liste:\nL = \t"); L = supprime(L); print_liste(L); return 0; }

111

Annexe B

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

112

# Object files OBJ = $(patsubst %.$(C_EXT),%.o,$(SRC:.$(CPP_EXT)=.o)) ABSOBJ = $(OBJ:%.o=$(OBJ_DIR)/%.o) # Backup files generated by text editors BACKUP_FILE = $(shell find . -name "*~") # Doxygen stuff DOC_DIR = Doc DOXYGEN = $(shell which doxygen) DOXYGEN_CONF = .doxygen.conf YES_ATTRIBUTES := JAVADOC_AUTOBRIEF EXTRACT_ALL EXTRACT_PRIVATE EXTRACT_STATIC \ SOURCE_BROWSER GENERATE_MAN # Compilator configuration # Detect if you have a C or a C++ project through file extension ifneq ($(filter %.c,$(SRC)),) CXX = gcc YES_ATTRIBUTES := $(YES_ATTRIBUTES) OPTIMIZE_OUTPUT_FOR_C else CXX = g++ SPECIAL_CPP_OPTION = -Wno-deprecated endif CXXFLAGS = -g3 -O3 -Wall $(SPECIAL_CPP_OPTION) -I$(INCLUDE_DIR) -c ADD_OPT = #-lntl -lgmp -lm # Optionnal option for the linker # Specifies the list of directories that make should search VPATH = $(INCLUDE_DIR):$(OBJ_DIR) # dependance file used for make rules MAKEDEP_FILE = .Makefile.dep ############################### Now starting rules ################################ # Required rule : whats to be done each time all : $(MAKEDEP_FILE) $(EXE) TAGS # Generate TAGS for emacs TAGS : $(ALL_SRC) etags $< cp TAGS $(INCLUDE_DIR)/ # Clean Options clean : @echo "Remove backup files generated by emacs and vi" rm -f $(BACKUP_FILE) # Clean everything (including object files, binaries and documentation) realclean : clean @echo "Remove object files" rm -f $(ABSOBJ) @echo "Remove generated executable" rm -f $(EXE) @if [ ! -z "$(DOC_DIR)" -a ! -z "$(DOXYGEN)" ]; then \ echo "Remove documentation (make doc to regenerate it)"; \ rm -rf $(DOC_DIR)/*; \ fi # Force re-compilation, even if not required force : touch $(ALL_SRC) $(ABSOBJ) @$(MAKE) # Generate the dependance file $(MAKEDEP_FILE) : $(ALL_SRC) $(CXX) $(SPECIAL_CPP_OPTION) -MM -I$(INCLUDE_DIR) $(SRC) > $@ include $(MAKEDEP_FILE) # Generic description for compilation of object files %.o : %.$(C_EXT) $(CXX) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@ %.o : %.$(CPP_EXT)

113

$(CXX) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@ # Generation of the final binary (see $(EXE)) $(EXE) : $(OBJ) $(ALL_SRC) $(CXX) -g -o $@ $(ABSOBJ) $(ADD_OPT) @$(MAKE) help # Help rule help : @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo # Test values test : @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo @echo print help message +-----------------------------------------------------------------------------+ | Available Commands | +----------------+------------------------------------------------------------+ | make | Compile files, binary is generated in the current directory| | make force | Force the complete re-compilation, even if not required | | make clean | Remove cache backup files generated by emacs and vi | | make realclean | Remove all generated files (including .o and binary) | | make doc | Generate documentation using doxygen (see www.doxygen.org) | | make help | Print help message | +----------------+------------------------------------------------------------+ of variables - for debug purpose "INCLUDE_DIR = "OBJ_DIR = "EXE = "SRC = "SRC_H = "ALL_SRC = "OBJ = "BACKUP_FILE = "CXX = "CXXFLAGS = "ADD_OPT = "DOC_DIR = "DOXYGEN = "DOXYGEN_CONF = "YES_ATTRIBUTES "MAKEDEP_FILE = $(INCLUDE_DIR)" $(OBJ_DIR)" $(EXE)" $(SRC)" $(SRC_H)" $(ALL_SRC)" $(OBJ)" $(BACKUP_FILE)" $(CXX)" $(CXXFLAGS)" $(ADD_OPT)" $(DOC_DIR)" $(DOXYGEN)" $(DOXYGEN_CONF)" = $(YES_ATTRIBUTES)" $(MAKEDEP_FILE)"

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

114

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

\ \ \ \

115

Annexe C

Le btisier e
Cette annexe est une collection des btises quil faut faire au moins une fois e dans sa vie pour tre vaccin. Lide est reprise de [Cas98] e e e La caractristique de beaucoup de ces erreurs est de ne pas provoquer de mese sage derreur du compilateur, rendant ainsi leur dtection dicile. La dirence e e entre le texte correct et le texte erron est souvent seulement dun seul carace t`re. La dcouverte de telles erreurs ne peut donc se faire que par un examen e e tr`s attentif du source du programe. e

C.1
C.1.1

Erreur avec les oprateurs e


Erreur sur une comparaison
: : : : Comparer a et b if (a == b) if (a = b) une aectation de b ` a, le rsultat tant la a e e valeur aecte. e

Ce que voulait le programmeur Ce quil aurait d crire ue Ce quil a crit e Ce quil a obtenu

Pourquoi tant de ha ? Laectation est un oprateur et non pas une ne e instruction.

C.1.2

Erreur sur laectation


: : : : Aecter b ` a a a = b a == b La comparaison de a ` b. a

Cest linverse de lerreur prcdente, mais qui reste beaucoup moins frquente. e e e
Ce que voulait le programmeur Ce quil aurait d crire ue Ce quil a crit e Ce quil a obtenu

C.2

Erreurs avec les macros

Le mcanisme des macros est ralis par le prprocesseur qui ralise un traitee e e e e ment en amont du compilateur proprement dit. Ce concept est abord en dtail e e dans le chapitre 7. Comme le traitement des macros est un pur traitement textuel, sans aucun contexte, cest un nid ` erreurs. a 116

C.2.1

Un #define nest pas une dclaration e


la dnition de la constante MAX initialise a 10 e e #define MAX 10 #define MAX 10 ;

Ce que voulait le programmeur : Ce quil aurait d crire : ue Ce quil a crit : e

Cette erreur peut provoquer ou non une erreur de compilation ` lutilisation de a la macro : Lutilisation x = MAX ; aura pour traduction x = 10 ; ;, ce qui est possible : il y a une instruction nulle derri`re linstruction x = 10 ; e Lutilisation int t[MAX] ; aura pour expansion int t[10 ;] ; ce qui gn`rera e e un message derreur.

C.2.2

Un #define nest pas une initialisation


la dnition de la constante MAX initialise a 10 e e #define MAX 10 #define MAX = 10 ;

Ce que voulait le programmeur : Ce quil aurait d crire : ue Ce quil a crit : e

Cette erreur sera gnralement dtecte ` la compilation, malheureusement le e e e e a message derreur sera mis sur lutilisation de la macro, et non pas l` o` rside e a u e lerreur, ` savoir la dnition de la macro. a e

C.2.3

Erreur sur macro avec param`tres e

La distinction entre macro avec param`tres et macro sans param`tre se fait e e sur la prsence dune parenth`se ouvrante juste apr`s le nom de la macro, e e e sans aucun blanc entre les deux. Ceci peut amener des rsultats surprenant ; e comparer les deux exemples suivants :
Dnition de la macro e #define add(a,b) (a + b) #define add (a,b) (a + b) param`tres e a et b aucun traduction (a + b) (a,b) (a + b)

C.2.4

Erreur avec les eets de bord

Le corps dune macro peut comporter plusieurs occurrences dun param`tre. Si e a ` lutilisation de la macro on ralise un eet de bord sur le param`tre eectif, e e cet eet de bord sera ralis plusieurs fois. Exemple : e e #define CARRE(a) ((a) * (a)) lutilisation de CARRE(x++) aura comme traduction ((x++) * (x++)) et lope rateur ++ sera appliqu deux fois. e

C.3

Erreurs avec linstruction if

Linstruction if ne comporte ni mot-cl introducteur de la partie then, ni tere minateur (pas de fi dans le style des if then else fi). Ceci peut provoquer les erreurs suivantes : 117

Ce que voulait le programmeur : Ce quil aurait d crire : ue Ce quil a crit : e

tester si a > b if ( a > b) a = b; if ( a > b); a = b;

Le probl`me vient aussi du fait de lexistence de linstruction nulle : e


Ce que voulait le programmeur : Ce quil aurait d crire : ue tester si a > b if ( a > b) { if ( x > y) x = y; } else ... if ( a > b) if ( x > y) x = y; else ...

Ce quil a crit : e

On rappelle quun else est raccroch au premier if. e

C.4

Erreurs avec les commentaires

Il y a deux erreurs classiques avec les commentaires : 1. le programmeur oublie la squence fermante /*. Le compilateur mange e donc tout le texte jusqu` la squence fermante du prochain commentaire. a e 2. On veut enlever (en le mettant en commentaire) un gros bloc dinstructions sans prendre garde au fait quil comporte des commentaires. Les commentaires ne pouvant tre imbriqus, ca naura pas leet escompt e e e par le programmeur. La mthode classique pour enlever (tout en le laissant e dans le source) un ensemble dinstructions est dutiliser le prprocesseur : e #ifdef NOTDEFINED ... #endif

C.5

Erreurs avec les priorits des oprateurs e e

Les priorits des oprateurs sont parfois surprenantes. Les cas les plus gnants e e e sont les suivants : La priorit des oprateurs bit ` bit est infrieure ` celle des oprateurs de e e a e a e comparaison.
Le programmeur a crit e x & 0xff == 0xac il dsirait e (x & 0xff) == 0xac il a obtenu x & (0xff == 0xac)

La priorit des oprateurs de dcalage est infrieure ` celle des oprateurs e e e e a e arithmtiques. e
Le programmeur a crit e x << 4 + 0xf il dsirait e (x << 4) + 0xf il a obtenu x << (4 + 0xf)

118

La priorit de loprateur daectation est infrieure ` celle des oprateurs e e e a e de comparaison. Dans la squence ci-dessous, tr`s souvent utilise, toutes les e e e parenth`ses sont ncessaire : e e while ((c = getchar()) != EOF) { ... }

C.6
C.6.1

Erreur avec linstruction switch


Oubli du break

Sans linstruction break, le programme continuera ` lalternative suivante ce a qui peut poser probl`me. (voir 2.2.5). e

C.6.2

Erreur sur le default

Lalternative ` excuter par dfaut est introduite par letiquette default. Si a e e une faute de frappe est commise sur cette tiquette, lalternative par dfaut ne e e sera plus reconnue : ltiquette sera prise pour une tiquette dinstruction sur e e laquelle ne sera fait aucun goto (voir 2.2.6). switch(a) { case 1 : a = b; defult : return(1); }

/*

erreur non dtecte e e

*/

Version diabolique de cette erreur : si la lettre l de default est remplace par e le chire 1, avec les fontes utilises pour imprimer les sources, qui verra la e dirence entre l et 1 ? e

C.7

Erreur sur les tableaux multidimensionnels

La rfrence ` un tableau t ` deux dimensions scrit t[i][j] et non pas t[i,j] ee a a e comme dans dautres langages de programmation. Malheureusement, si on utilise par erreur la notation t[i,j] et selon le contexte dutilisation, elle pourra tre accepte par le compilateur. En eet, dans cette e e expression la virgule est loprateur qui dlivre comme rsultat loprande droite e e e e apr`s avoir valu loprande gauche. Comme lvaluation de loprande gauche e e e e e e ne ralise ici aucun eet de bord, cette valuation est inutile , donc t[i,j] est e e quivalent ` t[j] qui est ladresse du sous-tableau correspondant ` lindex j. e a a

C.8

Erreur avec la compilation spare e e

Une erreur classique est davoir un tableau dni dans une unit de compilation : e e int tab[10] ; et dutiliser comme dclaration de rfrence dans une autre unit de compilae ee e tion : 119

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

120

C.9

Liens utiles

En plus des ouvrages cits dans la bibliographie, voici quelques sites web qui e mont servis pour raliser ce polycopi et/ou qui mont parus intressants : e e e [Cas98] : http://www-clips.imag.fr/commun/bernard.cassagne/Introduction_ ANSI_C.html http://fr.wikibooks.org/wiki/Programmation_C [Can03] : http://www-rocq.inria.fr/codes/Anne.Canteaut/COURS_C/ [Fab02] : http://www.ltam.lu/Tutoriel_Ansi_C/ (un tr`s bon tutorial en e ligne).

121

Bibliographie
[Can03] Anne Canteaut. Programmation en Langage C. INRIA projet CODES, 2003. http://www-rocq.inria.fr/codes/Anne. Canteaut/COURS_C/. [Cas98] Bernard Cassagne. Introduction au Langage C. Laboratoire CLIPS UJF/CNRS, 1997-1998. http://www-clips.imag.fr/commun/\\ bernard.cassagne/Introduction\_ANSI\_C.html. F. Faber. Introduction ` la programmation en ANSI-C. Technical a report, Tutorial, Ao t 2002. http://www.ltam.lu/Tutoriel_Ansi_ u C/. Matthieu Herrb. Guide superu de programmation en langage C. Technical report, CNRS/LAAS, Dec. 2001. http://www.laas.fr/ ~matthieu/cours/c-superflu/index. B.W. Kernighan and D.M. Ritchie. The C Programming Language. Prentice-Hall, Englewood Clis, New Jersey, USA, 1988. 2nd edition. Jean-Franois Pillou. Introduction au Langage C. Technical rec port, Tutorial, Encyclopdie Informatique Libre, 2004. http://www. e commentcamarche.net/c/cintro.php3.

[Fab02]

[Her01]

[KR88] [Pil04]

[PKP03] Peter Prinz and Ulla Kirch-Prinz. C Pocket Reference. OReilly & Associates, 2003. [Ste90] David Stevenson. IEEE Std 754-1985 IEEE Standard for Binary Floating-Point Arithmetic. Technical report, IEEE Standards Association, 1990.

122

You might also like