Vous êtes sur la page 1sur 133

Programmation en langage C

Anne CANTEAUT
INRIA - projet CODES B.P. 105 78153 Le Chesnay Cedex Anne.Canteaut@inria.fr http://www-rocq.inria.fr/codes/Anne.Canteaut/COURS C

Table des mati` eres

Table des mati` eres


1 Les 1.1 1.2 1.3 bases de la programmation en C Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La compilation . . . . . . . . . . . . . . . . . . . . . . . . . . Les composants el ementaires du C . . . . . . . . . . . . . . . 1.3.1 Les identicateurs . . . . . . . . . . . . . . . . . . . . 1.3.2 Les mots-clefs . . . . . . . . . . . . . . . . . . . . . . . 1.3.3 Les commentaires . . . . . . . . . . . . . . . . . . . . Structure dun programme C . . . . . . . . . . . . . . . . . . Les types pr ed enis . . . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Le type caract` ere . . . . . . . . . . . . . . . . . . . . . 1.5.2 Les types entiers . . . . . . . . . . . . . . . . . . . . . 1.5.3 Les types ottants . . . . . . . . . . . . . . . . . . . . Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.1 Les constantes enti` eres . . . . . . . . . . . . . . . . . . 1.6.2 Les constantes r eelles . . . . . . . . . . . . . . . . . . 1.6.3 Les constantes caract` eres . . . . . . . . . . . . . . . . 1.6.4 Les constantes cha nes de caract` eres . . . . . . . . . . Les op erateurs . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.1 Laectation . . . . . . . . . . . . . . . . . . . . . . . 1.7.2 Les op erateurs arithm etiques . . . . . . . . . . . . . . 1.7.3 Les op erateurs relationnels . . . . . . . . . . . . . . . 1.7.4 Les op erateurs logiques bool eens . . . . . . . . . . . . 1.7.5 Les op erateurs logiques bit ` a bit . . . . . . . . . . . . 1.7.6 Les op erateurs daectation compos ee . . . . . . . . . 1.7.7 Les op erateurs dincr ementation et de d ecr ementation 1.7.8 Lop erateur virgule . . . . . . . . . . . . . . . . . . . . 1.7.9 Lop erateur conditionnel ternaire . . . . . . . . . . . . 1.7.10 Lop erateur de conversion de type . . . . . . . . . . . 1.7.11 Lop erateur adresse . . . . . . . . . . . . . . . . . . . . 1.7.12 R` egles de priorit e des op erateurs . . . . . . . . . . . . Les instructions de branchement conditionnel . . . . . . . . . 1.8.1 Branchement conditionnel if---else . . . . . . . . . 1.8.2 Branchement multiple switch . . . . . . . . . . . . . . Les boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.9.1 Boucle while . . . . . . . . . . . . . . . . . . . . . . . 1.9.2 Boucle do---while . . . . . . . . . . . . . . . . . . . . 9 9 9 11 11 12 12 12 14 14 16 17 17 18 18 19 19 19 19 20 21 21 22 22 23 23 23 24 24 24 25 25 25 26 26 26

1.4 1.5

1.6

1.7

1.8

1.9

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

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

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

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

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

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

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

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

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

4 1.9.3 Boucle for . . . . . . . . . . . . . . . . . . 1.10 Les instructions de branchement non conditionnel . 1.10.1 Branchement non conditionnel break . . . 1.10.2 Branchement non conditionnel continue . 1.10.3 Branchement non conditionnel goto . . . . 1.11 Les fonctions dentr ees-sorties classiques . . . . . . 1.11.1 La fonction d ecriture printf . . . . . . . . 1.11.2 La fonction de saisie scanf . . . . . . . . . 1.11.3 Impression et lecture de caract` eres . . . . . 1.12 Les conventions d ecriture dun programme C . . . 2 Les 2.1 2.2 2.3 2.4 2.5 2.6 3 Les 3.1 3.2 3.3 3.4 3.5 types compos es Les tableaux . . . . . . . . . . . . . . . . . Les structures . . . . . . . . . . . . . . . . . Les champs de bits . . . . . . . . . . . . . . Les unions . . . . . . . . . . . . . . . . . . . Les enum erations . . . . . . . . . . . . . . . D enition de types compos es avec typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Table des mati` eres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 28 28 28 29 29 29 31 32 33 35 35 37 39 39 40 41 43 43 44 46 47 50 50 52 53 54 54 56 59 59 60 60 61 62 63 64 66 67 69 74

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

pointeurs Adresse et valeur dun objet . . . . . . . . . . . . . . Notion de pointeur . . . . . . . . . . . . . . . . . . . Arithm etique des pointeurs . . . . . . . . . . . . . . Allocation dynamique . . . . . . . . . . . . . . . . . Pointeurs et tableaux . . . . . . . . . . . . . . . . . . 3.5.1 Pointeurs et tableaux ` a une dimension . . . . 3.5.2 Pointeurs et tableaux ` a plusieurs dimensions 3.5.3 Pointeurs et cha nes de caract` eres . . . . . . 3.6 Pointeurs et structures . . . . . . . . . . . . . . . . . 3.6.1 Pointeur sur une structure . . . . . . . . . . . 3.6.2 Structures auto-r ef erenc ees . . . . . . . . . . fonctions D enition dune fonction . . . . . . . . . . . . . . Appel dune fonction . . . . . . . . . . . . . . . . D eclaration dune fonction . . . . . . . . . . . . . Dur ee de vie des variables . . . . . . . . . . . . . 4.4.1 Variables globales . . . . . . . . . . . . . 4.4.2 Variables locales . . . . . . . . . . . . . . Transmission des param` etres dune fonction . . . Les qualicateurs de type const et volatile . . La fonction main . . . . . . . . . . . . . . . . . . Pointeur sur une fonction . . . . . . . . . . . . . Fonctions avec un nombre variable de param` etres

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

4 Les 4.1 4.2 4.3 4.4

4.5 4.6 4.7 4.8 4.9

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

Table des mati` eres 5 Les directives au pr eprocesseur 5.1 La directive #include . . . . . . . . . . . . . . . 5.2 La directive #define . . . . . . . . . . . . . . . . 5.2.1 D enition de constantes symboliques . . . 5.2.2 D enition de macros . . . . . . . . . . . . 5.3 La compilation conditionnelle . . . . . . . . . . . 5.3.1 Condition li ee ` a la valeur dune expression 5.3.2 Condition li ee ` a lexistence dun symbole 6 La gestion des chiers 6.1 Ouverture et fermeture dun chier . . 6.1.1 La fonction fopen . . . . . . . 6.1.2 La fonction fclose . . . . . . . 6.2 Les entr ees-sorties format ees . . . . . . 6.2.1 La fonction d ecriture fprintf 6.2.2 La fonction de saisie fscanf . . 6.3 Impression et lecture de caract` eres . . 6.4 Relecture dun caract` ere . . . . . . . . 6.5 Les entr ees-sorties binaires . . . . . . . 6.6 Positionnement dans un chier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5 77 77 77 78 78 79 79 80 81 81 81 82 83 83 83 83 84 85 86 89 89 90 91 93 93 93 94 96 97 99 99 99 99 100 101 102 103 104 104 104 104 104 105 105

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7 La programmation modulaire 7.1 Principes el ementaires . . . . . . . . . . 7.2 La compilation s epar ee . . . . . . . . . . 7.2.1 Fichier en-t ete dun chier source 7.2.2 Variables partag ees . . . . . . . . 7.3 Lutilitaire make . . . . . . . . . . . . . 7.3.1 Principe de base . . . . . . . . . 7.3.2 Cr eation dun Makefile . . . . . 7.3.3 Macros et abbr eviations . . . . . 7.3.4 R` egles g en erales de compilation .

A La librairie standard A.1 Entr ees-sorties <stdio.h> . . . . . . . . . . . . . . . . A.1.1 Manipulation de chiers . . . . . . . . . . . . . . A.1.2 Entr ees et sorties format ees . . . . . . . . . . . . A.1.3 Impression et lecture de caract` eres . . . . . . . . A.2 Manipulation de caract` eres <ctype.h> . . . . . . . . . A.3 Manipulation de cha nes de caract` eres <string.h> . . A.4 Fonctions math ematiques <math.h> . . . . . . . . . . . A.5 Utilitaires divers <stdlib.h> . . . . . . . . . . . . . . A.5.1 Allocation dynamique . . . . . . . . . . . . . . . A.5.2 Conversion de cha nes de caract` eres en nombres . A.5.3 G en eration de nombres pseudo-al eatoires . . . . A.5.4 Arithm etique sur les entiers . . . . . . . . . . . . A.5.5 Recherche et tri . . . . . . . . . . . . . . . . . . . A.5.6 Communication avec lenvironnement . . . . . .

Table des mati` eres A.6 Date et heure <time.h> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 107 107 108 108 109 111 113 113 113 114 116 116 117 119 120 123 123 124 124 127 128

B Le d ebogueur GDB B.1 D emarrer gdb . . . . . . . . . . . . . . . . . . . . . . . . . . . B.2 Quitter gdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.3 Ex ecuter un programme sous gdb . . . . . . . . . . . . . . . . B.4 Terminaison anormale du programme . . . . . . . . . . . . . B.5 Acher les donn ees . . . . . . . . . . . . . . . . . . . . . . . . B.6 Appeler des fonctions . . . . . . . . . . . . . . . . . . . . . . B.7 Modier des variables . . . . . . . . . . . . . . . . . . . . . . B.8 Se d eplacer dans la pile des appels . . . . . . . . . . . . . . . B.9 Poser des points darr et . . . . . . . . . . . . . . . . . . . . . B.10 G erer les points darr et . . . . . . . . . . . . . . . . . . . . . . B.11 Les points darr et conditionnels . . . . . . . . . . . . . . . . . B.12 Ex ecuter un programme pas ` a pas . . . . . . . . . . . . . . . B.13 Acher la valeur dune expression ` a chaque point darr et . . B.14 Ex ecuter automatiquement des commandes aux points darr et B.15 Les raccourcis des noms de commande . . . . . . . . . . . . . B.16 Utiliser lhistorique des commandes . . . . . . . . . . . . . . . B.17 Interface avec le shell . . . . . . . . . . . . . . . . . . . . . . . B.18 R esum e des principales commandes . . . . . . . . . . . . . . . Bibliographie Index

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

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

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

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

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

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

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

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

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

Liste des tableaux

Liste des tableaux


1.1 1.2 1.3 1.4 1.5 1.6 Codes ASCII des caract` eres imprimables . . . Les types entiers . . . . . . . . . . . . . . . . Les types ottants . . . . . . . . . . . . . . . R` egles de priorit e des op erateurs . . . . . . . Formats dimpression pour la fonction printf Formats de saisie pour la fonction scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 16 17 24 30 32

Liste des tableaux

Chapitre 1

Les bases de la programmation en C


1.1 Historique

Le C a et e con cu en 1972 par Dennis Richie et Ken Thompson, chercheurs aux Bell Labs, an de d evelopper un syst` eme dexploitation UNIX sur un DEC PDP-11. En 1978, Brian Kernighan et Dennis Richie publient la d enition classique du C dans le livre The C Programming language [6]. Le C devenant de plus en plus populaire dans les ann ees 80, plusieurs groupes mirent sur le march e des compilateurs comportant des extensions particuli` eres. En 1983, lANSI (American National Standards Institute) d ecida de normaliser le langage ; ce travail sacheva en 1989 par la d enition de la norme ANSI C. Celle-ci fut reprise telle quelle par lISO (International Standards Organization) en 1990. Cest ce standard, ANSI C, qui est d ecrit dans le pr esent document.

1.2

La compilation

Le C est un langage compil e (par opposition aux langages interpr et es). Cela signie quun programme C est d ecrit par un chier texte, appel e chier source. Ce chier n etant evidemment pas ex ecutable par le microprocesseur, il faut le traduire en langage machine. Cette op eration est eectu ee par un programme appel e compilateur. La compilation se d ecompose en fait en 4 phases successives : 1. Le traitement par le pr eprocesseur : le chier source est analys e par le pr eprocesseur qui eectue des transformations purement textuelles (remplacement de cha nes de caract` eres, inclusion dautres chiers source . . . ). 2. La compilation : la compilation proprement dite traduit le chier g en er e par le pr eprocesseur en assembleur, cest-` a-dire en une suite dinstructions du microprocesseur qui utilisent des mn emoniques rendant la lecture possible. 3. Lassemblage : cette op eration transforme le code assembleur en un chier binaire, cest-` a-dire en instructions directement compr ehensibles par le processeur. G en eralement, la compilation et lassemblage se font dans la foul ee, sauf si lon sp ecie explicitement que lon veut le code assembleur. Le chier produit par lassemblage est appel e chier objet.

10

Chapitre 1. Les bases de la programmation en C 4. L edition de liens : un programme est souvent s epar e en plusieurs chiers source, pour des raisons de clart e mais aussi parce quil fait g en eralement appel ` a des librairies de fonctions standard d ej` a ecrites. Une fois chaque code source assembl e, il faut donc lier entre eux les di erents chiers objets. L edition de liens produit alors un chier dit ex ecutable.

Les di erents types de chiers utilis es lors de la compilation sont distingu es par leur suxe. Les chiers source sont sux es par .c, les chiers pr etrait es par le pr eprocesseur par .i, les chiers assembleur par .s, et les chiers objet par .o. Les chiers objets correspondant aux librairies pr e-compil ees ont pour suxe .a. Le compilateur C sous UNIX sappelle cc. On utilisera de pr ef erence le compilateur gcc du projet GNU. Ce compilateur est livr e gratuitement avec sa documentation et ses sources. Par d efaut, gcc active toutes les etapes de la compilation. On le lance par la commande gcc [options] fichier.c [-llibrairies] Par d efaut, le chier ex ecutable sappelle a.out. Le nom de lex ecutable peut etre modi e a laide de loption -o. ` Les eventuelles librairies sont d eclar ees par la cha ne -llibrairie. Dans ce cas, le syst` eme recherche le chier liblibrairie.a dans le r epertoire contenant les librairies pr e-compil ees (g en eralement /usr/lib/). Par exemple, pour lier le programme avec la librairie math ematique, on sp ecie -lm. Le chier objet correspondant est libm.a. Lorsque les librairies pr e-compil ees ne se trouvent pas dans le r epertoire usuel, on sp ecie leur chemin dacc` es par loption -L. Les options les plus importantes du compilateur gcc sont les suivantes : -c : supprime l edition de liens ; produit un chier objet. -E : nactive que le pr eprocesseur (le r esultat est envoy e sur la sortie standard). -g : produit des informations symboliques n ecessaires au d ebogueur. -Inom-de-r epertoire : sp ecie le r epertoire dans lequel doivent etre recherch es les chiers en-t etes ` a inclure (en plus du r epertoire courant). -Lnom-de-r epertoire : sp ecie le r epertoire dans lequel doivent etre recherch ees les librairies pr ecompil ees (en plus du r epertoire usuel). -o nom-de-fichier : sp ecie le nom du chier produit. Par d efaut, le ex ecutable chier sappelle a.out. -O, -O1, -O2, -O3 : options doptimisations. Sans ces options, le but du compilateur est de minimiser le co ut de la compilation. En rajoutant lune de ces options, le compilateur tente de r eduire la taille du code ex ecutable et le temps dex ecution. Les options correspondent ` a di erents niveaux doptimisation : -O1 (similaire ` a -O) correspond ` a une faible optimisation, -O3 ` a loptimisation maximale. -S : nactive que le pr eprocesseur et le compilateur ; produit un chier assembleur. -v : imprime la liste des commandes ex ecut ees par les di erentes etapes de la compilation.

A. Canteaut - Programmation en langage C -W : imprime des messages davertissement (warning) suppl ementaires. -Wall : imprime tous les messages davertissement. Pour plus de d etails sur gcc, on peut consulter le chapitre 4 de [8].

11

1.3

Les composants el ementaires du C

Un programme en langage C est constitu e des six groupes de composants el ementaires suivants : les identicateurs, les mots-clefs, les constantes, les cha nes de caract` eres, les op erateurs, les signes de ponctuation. On peut ajouter ` a ces six groupes les commentaires, qui sont enlev es par le pr eprocesseur.

1.3.1

Les identicateurs

Le r ole dun identicateur est de donner un nom ` a une entit e du programme. Plus pr ecis ement, un identicateur peut d esigner : un nom de variable ou de fonction, un type d eni par typedef, struct, union ou enum, une etiquette. Un identicateur est une suite de caract` eres parmi : les lettres (minuscules ou majuscules, mais non accentu ees), les chires, le blanc soulign e ( ). Le premier caract` ere dun identicateur ne peut pas etre un chire. Par exemple, var1, tab 23 ou deb sont des identicateurs valides ; par contre, 1i et i:j ne le sont pas. Il est cependant d econseill e dutiliser comme premier caract` ere dun identicateur car il est souvent employ e pour d enir les variables globales de lenvironnement C. Les majuscules et minuscules sont di erenci ees. Le compilateur peut tronquer les identicateurs au-del` a dune certaine longueur. Cette limite d epend des impl ementations, mais elle est toujours sup erieure ` a 31 caract` eres. (Le standard dit que les identicateurs externes, cest-` a-dire ceux qui sont export es ` a l edition de lien, peuvent etre tronqu es ` a 6 caract` eres, mais tous les compilateurs modernes distinguent au moins 31 caract` eres).

12

Chapitre 1. Les bases de la programmation en C

1.3.2

Les mots-clefs

Un certain nombre de mots, appel es mots-clefs, sont r eserv es pour le langage lui-m eme et ne peuvent pas etre utilis es comme identicateurs. LANSI C compte 32 mots clefs : auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

que lon peut ranger en cat egories les sp ecicateurs de stockage auto register static extern typedef

les sp ecicateurs de type char union double enum float unsigned void int long short signed struct

les qualicateurs de type const volatile

les instructions de contr ole break switch divers return sizeof case continue while default do else for goto if

1.3.3

Les commentaires

Un commentaire d ebute par /* et se termine par */. Par exemple, /* Ceci est un commentaire */

On ne peut pas imbriquer des commentaires. Quand on met en commentaire un morceau de programme, il faut donc veiller ` a ce que celui-ci ne contienne pas de commentaire.

1.4

Structure dun programme C

Une expression est une suite de composants el ementaires syntaxiquement correcte, par exemple x = 0 ou bien (i >= 0) && (i < 10) && (p[i] != 0)

A. Canteaut - Programmation en langage C

13

Une instruction est une expression suivie dun point-virgule. Le point-virgule signie en quelque sorte evaluer cette expression. Plusieurs instructions peuvent etre rassembl ees par des accolades { et } pour former une instruction compos ee ou bloc qui est syntaxiquement equivalent ` a une instruction. Par exemple, if (x != 0) { z = y / x; t = y % x; } Une instruction compos ee dun sp ecicateur de type et dune liste didenticateurs s epar es par une virgule est une d eclaration. Par exemple, int a; int b = 1, c; double x = 2.38e4; char message[80]; En C, toute variable doit faire lobjet dune d eclaration avant d etre utilis ee. Un programme C se pr esente de la fa con suivante : [ directives au pr eprocesseur] [ d eclarations de variables externes] [ fonctions secondaires] main() { d eclarations de variables internes instructions } La fonction principale main peut avoir des param` etres formels. On supposera dans un premier temps que la fonction main na pas de valeur de retour. Ceci est tol er e par le compilateur mais produit un message davertissement quand on utilise loption -Wall de gcc (cf. page 67). Les fonctions secondaires peuvent etre plac ees indi eremment avant ou apr` es la fonction principale. Une fonction secondaire peut se d ecrire de la mani` ere suivante : type ma_fonction ( { d eclarations de variables internes instructions } Cette fonction retournera un objet dont le type sera type (` a laide dune instruction comme return objet;). Les arguments de la fonction ob eissent ` a une syntaxe voisine de celle des d eclarations : on met en argument de la fonction une suite dexpressions type objet s epar ees arguments )

14

Chapitre 1. Les bases de la programmation en C

par des virgules. Par exemple, la fonction secondaire suivante calcule le produit de deux entiers : int produit(int a, int b) { int resultat; resultat = a * b; return(resultat); }

1.5

Les types pr ed enis

Le C est un langage typ e. Cela signie en particulier que toute variable, constante ou fonction est dun type pr ecis. Le type dun objet d enit la fa con dont il est repr esent e en m emoire. La m emoire de lordinateur se d ecompose en une suite continue doctets. Chaque octet de la m emoire est caract eris e par son adresse, qui est un entier. Deux octets contigus en m emoire ont des adresses qui di` erent dune unit e. Quand une variable est d enie, il lui est attribu e une adresse. Cette variable correspondra ` a une zone m emoire dont la longueur (le nombre doctets) est x ee par le type. La taille m emoire correspondant aux di erents types d epend des compilateurs ; toutefois, la norme ANSI sp ecie un certain nombre de contraintes. Les types de base en C concernent les caract` eres, les entiers et les ottants (nombres r eels). Ils sont d esign es par les mots-clefs suivants : char int float double short long unsigned

1.5.1

Le type caract` ere

Le mot-clef char d esigne un objet de type caract` ere. Un char peut contenir nimporte quel el ement du jeu de caract` eres de la machine utilis ee. La plupart du temps, un objet de type char est cod e sur un octet ; cest lobjet le plus el ementaire en C. Le jeu de caract` eres utilis e correspond g en eralement au codage ASCII (sur 7 bits). La plupart des machines utilisent d esormais le jeu de caract` eres ISO-8859 (sur 8 bits), dont les 128 premiers caract` eres correspondent aux caract` eres ASCII. Les 128 derniers caract` eres (cod es sur 8 bits) sont utilis es pour les caract` eres propres aux di erentes langues. La version ISO-8859-1 (aussi appel ee ISOLATIN-1) est utilis ee pour les langues dEurope occidentale. Ainsi, le caract` ere de code 232 est le ` e, le caract` ere 233 correspond au e. . . Pour plus de d etails sur lhistorique du codage des caract` eres pour les di erentes langues ainsi que sur la norme UNICODE (sur 16 bits, qui permet de coder les caract` eres pour toutes les langues) et sur la norme ISO/IEC-10646 (sur 32 bits, ce qui permet dajouter les caract` eres anciens), consulter larticle de J. Andr e et M. Goossens [1].

A. Canteaut - Programmation en langage C d ec. 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 oct. 40 41 42 43 44 45 46 47 50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 67 70 71 72 73 74 75 76 77 hex. 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f d ec. 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 oct. 100 101 102 103 104 105 106 107 110 111 112 113 114 115 116 117 120 121 122 123 124 125 126 127 130 131 132 133 134 135 136 137 hex. 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f d ec. 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 oct. 140 141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160 161 162 163 164 165 166 167 170 171 172 173 174 175 176 177 hex. 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f

15

! # $ % & ( ) * + , . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?

@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _

a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ DEL

Tab. 1.1 Codes ASCII des caract` eres imprimables Une des particularit es du type char en C est quil peut etre assimil e ` a un entier : tout objet de type char peut etre utilis e dans une expression qui utilise des objets de type entier. Par exemple, si c est de type char, lexpression c + 1 est valide. Elle d esigne le caract` ere suivant dans le code ASCII. La table de la page 15 donne le code ASCII (en d ecimal, en octal et en hexad ecimal) des caract` eres imprimables. Ainsi, le programme suivant imprime le caract` ere B. main() { char c = A;

16 printf("%c", c + 1); }

Chapitre 1. Les bases de la programmation en C

Suivant les impl ementations, le type char est sign e ou non. En cas de doute, il vaut mieux pr eciser unsigned char ou signed char. Notons que tous les caract` eres imprimables sont positifs.

1.5.2

Les types entiers

Le mot-clef d esignant le type entier est int. Un objet de type int est repr esent e par un mot naturel de la machine utilis ee, 32 bits pour un DEC alpha ou un PC Intel. Le type int peut etre pr ec ed e dun attribut de pr ecision (short ou long) et/ou dun attribut de repr esentation (unsigned). Un objet de type short int a au moins la taille dun char et au plus la taille dun int. En g en eral, un short int est cod e sur 16 bits. Un objet de type long int a au moins la taille dun int (64 bits sur un DEC alpha, 32 bits sur un PC Intel). DEC Alpha 8 bits 16 bits 32 bits 64 bits n.i. PC Intel (Linux) 8 bits 16 bits 32 bits 32 bits 64 bits

char short int long long long

caract` ere entier court entier entier long entier long (non ANSI)

Tab. 1.2 Les types entiers Le bit de poids fort dun entier est son signe. Un entier positif est donc repr esent e en m emoire par la suite de 32 bits dont le bit de poids fort vaut 0 et les 31 autres bits correspondent ` a la d ecomposition de lentier en base 2. Par exemple, pour des objets de type char (8 bits), lentier positif 12 sera repr esent e en m emoire par 00001100. Un entier n egatif est, lui, repr esent e par une suite de 32 bits dont le bit de poids fort vaut 1 et les 31 autres bits correspondent ` a la valeur absolue de lentier repr esent ee suivant la technique dite du compl ement a 2. Cela signie que lon exprime la valeur absolue de lentier sous forme binaire, que lon ` prend le compl ementaire bit-` a-bit de cette valeur et que lon ajoute 1 au r esultat. Ainsi, pour des objets de type signed char (8 bits), -1 sera repr esent e par 11111111, -2 par 11111110, -12 par par 11110100. Un int peut donc repr esenter un entier entre 231 et (231 1). Lattribut unsigned sp ecie que lentier na pas de signe. Un unsigned int peut donc repr esenter un 32 entier entre 0 et (2 1). Sur un DEC alpha, on utilisera donc un des types suivants en fonction de la taille des donn ees ` a stocker : signed char unsigned char short int unsigned short int int unsigned int long int (DEC alpha) unsigned long int (DEC alpha) [27 ; 27 [ [0; 28 [ [215 ; 215 [ [0; 216 [ [231 ; 231 [ [0; 232 [ [263 ; 263 [ [0; 264 [

A. Canteaut - Programmation en langage C

17

Plus g en eralement, les valeurs maximales et minimales des di erents types entiers sont d enies dans la librairie standard limits.h. Le mot-clef sizeof a pour syntaxe sizeof(expression) o` u expression est un type ou un objet. Le r esultat est un entier egal au nombre doctets n ecessaires pour stocker le type ou lobjet. Par exemple unsigned short x; taille = sizeof(unsigned short); taille = sizeof(x); Dans les deux cas, taille vaudra 4. Pour obtenir des programmes portables, on seorcera de ne jamais pr esumer de la taille dun objet de type entier. On utilisera toujours une des constantes de limits.h ou le r esultat obtenu en appliquant lop erateur sizeof.

1.5.3

Les types ottants

Les types float, double et long double servent ` a repr esenter des nombres en virgule ottante. Ils correspondent aux di erentes pr ecisions possibles. DEC Alpha 32 bits 64 bits 64 bits PC Intel 32 bits 64 bits 128 bits

float double long double

ottant ottant double pr ecision ottant quadruple pr ecision

Tab. 1.3 Les types ottants Les ottants sont g en eralement stock es en m emoire sous la repr esentation de la virgule ottante normalis ee. On ecrit le nombre sous la forme signe 0, mantisse B exposant . En g en eral, B = 2. Le digit de poids fort de la mantisse nest jamais nul. Un ottant est donc repr esent e par une suite de bits dont le bit de poids fort correspond au signe du nombre. Le champ du milieu correspond ` a la repr esentation binaire de lexposant alors que les bits de poids faible servent ` a repr esenter la mantisse.

1.6

Les constantes

Une constante est une valeur qui appara t litt eralement dans le code source dun programme, le type de la constante etant d etermin e par la fa con dont la constante est ecrite. Les constantes peuvent etre de 4 types : entier, ottant (nombre r eel), caract` ere, enum eration. Ces constantes vont etre utilis ees, par exemple, pour initialiser une variable.

18

Chapitre 1. Les bases de la programmation en C

1.6.1

Les constantes enti` eres

Une constante enti` ere peut etre repr esent ee de 3 mani` eres di erentes suivant la base dans laquelle elle est ecrite : d ecimale : par exemple, 0 et 2437348 sont des constantes enti` eres d ecimales. octale : la repr esentation octale dun entier correspond ` a sa d ecomposition en base 8. Les constantes octales doivent commencer par un z ero. Par exemple, les repr esentations octales des entiers 0 et 255 sont respectivement 00 et 0377. hexad ecimale : la repr esentation hexad ecimale dun entier correspond ` a sa d ecomposition en base 16. Les lettres de a ` a f sont utilis ees pour repr esenter les nombres de 10 ` a 15. Les constantes hexad ecimales doivent commencer par 0x ou 0X. Par exemple, les repr esentations hexad ecimales de 14 et 255 sont respectivement 0xe et 0xff. Par d efaut, une constante d ecimale est repr esent ee avec le format interne le plus court permettant de la repr esenter parmi les formats des types int, long int et unsigned long int tandis quune constante octale ou hexad ecimale est repr esent ee avec le format interne le plus court permettant encore de la repr esenter parmi les formats des types int, unsigned int, long int et unsigned long int. On peut cependant sp ecier explicitement le format dune constante enti` ere en la suxant par u ou U pour indiquer quelle est non sign ee, ou en la suxant par l ou L pour indiquer quelle est de type long. Par exemple : constante 1234 02322 0x4D2 123456789L 1234U 123456789UL type int int /* octal */ int /* hexad ecimal */ long unsigned int unsigned long int

1.6.2

Les constantes r eelles

Les constantes r eelles sont repr esent ees par la notation classique par mantisse et exposant. Lexposant est introduit par la lettre e ou E ; il sagit dun nombre d ecimal eventuellement sign e. Par d efaut, une constante r eelle est repr esent ee avec le format du type double. On peut cependant inuer sur la repr esentation interne de la constante en lui ajoutant un des suxes f (indi eremment F) ou l (indi eremment L). Les suxes f et F forcent la repr esentation de la constante sous forme dun float, les suxes l et L forcent la repr esentation sous forme dun long double. Par exemple : constante 12.34 12.3e-4 12.34F 12.34L type double double float long double

A. Canteaut - Programmation en langage C

19

1.6.3

Les constantes caract` eres

Pour d esigner un caract` ere imprimable, il sut de le mettre entre apostrophes (par ex. A ou $). Les seuls caract` eres imprimables quon ne peut pas repr esenter de cette fa con sont lantislash et lapostrophe, qui sont respectivement d esign es par \\ et \. Le point dinterrogation et les guillemets peuvent aussi etre d esign es par les notations \? et \". Les caract` eres non imprimables peuvent etre d esign es par \code-octal o` u code-octal est le code en octal du caract` ere. On peut aussi ecrire \xcode-hexa o` u code-hexa est le code en hexad ecimal du caract` ere (cf. page 15). Par exemple, \33 et \x1b d esignent le caract` ere escape. Toutefois, les caract` eres non-imprimables les plus fr equents disposent aussi dune notation plus simple : \n \t \v \b nouvelle ligne tabulation horizontale tabulation verticale retour arri` ere \r \f \a retour chariot saut de page signal dalerte

1.6.4

Les constantes cha nes de caract` eres

Une cha ne de caract` eres est une suite de caract` eres entour es par des guillemets. Par exemple, "Ceci est une cha^ ne de caract` eres" Une cha ne de caract` eres peut contenir des caract` eres non imprimables, d esign es par les repr esentations vues pr ec edemment. Par exemple, "ligne 1 \n ligne 2" A lint erieur dune cha ne de caract` eres, le caract` ere " doit etre d esign e par \". Enn, le caract` ere \ suivi dun passage ` a la ligne est ignor e. Cela permet de faire tenir de longues cha nes de caract` eres sur plusieurs lignes. Par exemple, "ceci est une longue longue cha^ ne de caract` eres" longue longue longue longue longue longue \

1.7
1.7.1

Les op erateurs
Laectation

En C, laectation est un op erateur ` a part enti` ere. Elle est symbolis ee par le signe =. Sa syntaxe est la suivante : variable = expression Le terme de gauche de laectation peut etre une variable simple, un el ement de tableau mais pas une constante. Cette expression a pour eet d evaluer expression et daecter la valeur obtenue ` a variable. De plus, cette expression poss` ede une valeur, qui est celle expression. Ainsi, lexpression i = 5 vaut 5.

20

Chapitre 1. Les bases de la programmation en C

Laectation eectue une conversion de type implicite : la valeur de lexpression (terme de droite) est convertie dans le type du terme de gauche. Par exemple, le programme suivant main() { int i, j = 2; float x = 2.5; i = j + x; x = x + i; printf("\n %f \n",x); } imprime pour x la valeur 6.5 (et non 7), car dans linstruction i = j + x;, lexpression j + xa et e convertie en entier.

1.7.2

Les op erateurs arithm etiques

Les op erateurs arithm etiques classiques sont lop erateur unaire - (changement de signe) ainsi que les op erateurs binaires + addition - soustraction * multiplication / division % reste de la division (modulo) Ces op erateurs agissent de la fa con attendue sur les entiers comme sur les ottants. Leurs seules sp ecicit es sont les suivantes : Contrairement ` a dautres langages, le C ne dispose que de la notation / pour d esigner ` a la fois la division enti` ere et la division entre ottants. Si les deux op erandes sont de type entier, lop erateur / produira une division enti` ere (quotient de la division). Par contre, il d elivrera une valeur ottante d` es que lun des op erandes est un ottant. Par exemple, float x; x = 3 / 2; aecte ` a x la valeur 1. Par contre x = 3 / 2.; aecte ` a x la valeur 1.5. Lop erateur % ne sapplique qu` a des op erandes de type entier. Si lun des deux op erandes est n egatif, le signe du reste d epend de limpl ementation, mais il est en g en eral le m eme que celui du dividende. Notons enn quil ny a pas en C dop erateur eectuant l el evation ` a la puissance. De fa con g en erale, il faut utiliser la fonction pow(x,y) de la librairie math.h pour calculer xy .

A. Canteaut - Programmation en langage C

21

1.7.3

Les op erateurs relationnels

> strictement sup erieur >= sup erieur ou egal < strictement inf erieur <= inf erieur ou egal == egal != di erent Leur syntaxe est expression-1 op expression-2 Les deux expressions sont evalu ees puis compar ees. La valeur rendue est de type int (il ny a pas de type bool een en C); elle vaut 1 si la condition est vraie, et 0 sinon. Attention ` a ne pas confondre lop erateur de test d egalit e == avec lop erateur daection =. Ainsi, le programme main() { int a = 0; int b = 1; if (a = b) printf("\n a et b sont egaux \n"); else printf("\n a et b sont differents \n"); } imprime ` a l ecran a et b sont egaux !

1.7.4

Les op erateurs logiques bool eens

&& et logique || ou logique ! n egation logique Comme pour les op erateurs de comparaison, la valeur retourn ee par ces op erateurs est un int qui vaut 1 si la condition est vraie et 0 sinon. Dans une expression de type expression-1 op-1 expression-2 op-2 ...expression-n l evaluation se fait de gauche ` a droite et sarr ete d` es que le r esultat nal est d etermin e. Par exemple dans int i; int p[10]; if ((i >= 0) && (i <= 9) && !(p[i] == 0)) ... la derni` ere clause ne sera pas evalu ee si i nest pas entre 0 et 9.

22

Chapitre 1. Les bases de la programmation en C

1.7.5

Les op erateurs logiques bit ` a bit

Les six op erateurs suivants permettent de manipuler des entiers au niveau du bit. Ils sappliquent aux entiers de toute longueur (short, int ou long), sign es ou non. & ^ << et ou exclusif d ecalage ` a gauche | ~ >> ou inclusif compl ement ` a1 d ecalage ` a droite

En pratique, les op erateurs &, | et ~ consistent ` a appliquer bit ` a bit les op erations suivantes & 0 1 0 0 0 1 0 1 | 0 1 0 0 1 1 1 1 ^ 0 1 0 0 1 1 1 0

Lop erateur unaire ~ change la valeur de chaque bit dun entier. Le d ecalage ` a droite et ` a gauche eectuent respectivement une multiplication et une division par une puissance de 2. Notons que ces d ecalages ne sont pas des d ecalages circulaires (ce qui d epasse dispara t). Consid erons par exemple les entiers a=77 et b=23 de type unsigned char (i.e. 8 bits). En base 2 il s ecrivent respectivement 01001101 et 00010111. valeur binaire d ecimale expression a 01001101 77 00010111 23 b 00000101 5 a & b a | b 01011111 95 a ^ b 01011010 90 ~a 10110010 178 b << 2 01011100 92 multiplication par 4 b << 5 11100000 112 ce qui d epasse dispara t 00001011 11 division enti` ere par 2 b >> 1

1.7.6

Les op erateurs daectation compos ee

Les op erateurs daectation compos ee sont += -= *= /= %= &= ^= |= <<= >>=

Pour tout op erateur op, lexpression expression-1 op= expression-2 est equivalente ` a expression-1 = expression-1 op expression-2 Toutefois, avec laection compos ee, expression-1 nest evalu ee quune seule fois.

A. Canteaut - Programmation en langage C

23

1.7.7

Les op erateurs dincr ementation et de d ecr ementation

Les op erateurs dincr ementation ++ et de d ecr ementation -- sutilisent aussi bien en suxe (i++) quen pr exe (++i). Dans les deux cas la variable i sera incr ement ee, toutefois dans la notation suxe la valeur retourn ee sera lancienne valeur de i alors que dans la notation pr exe se sera la nouvelle. Par exemple, int a = 3, b, c; b = ++a; /* a et b valent 4 */ c = b++; /* c vaut 4 et b vaut 5 */

1.7.8

Lop erateur virgule


expression-1, expression-2, ... , expression-n

Une expression peut etre constitu ee dune suite dexpressions s epar ees par des virgules : Cette expression est alors evalu ee de gauche ` a droite. Sa valeur sera la valeur de lexpression de droite. Par exemple, le programme main() { int a, b; b = ((a = 3), (a + 2)); printf("\n b = %d \n",b); } imprime b = 5. La virgule s eparant les arguments dune fonction ou les d eclarations de variables nest pas lop erateur virgule. En particulier l evaluation de gauche ` a droite nest pas garantie. Par exemple linstruction compos ee { int a=1; printf("\%d \%d",++a,a); } (compil ee avec gcc) produira la sortie 2 1 sur un PC Intel/Linux et la sortie 2 2 sur un DEC Alpha/OSF1.

1.7.9

Lop erateur conditionnel ternaire


condition ? expression-1 : expression-2

Lop erateur conditionnel ? est un op erateur ternaire. Sa syntaxe est la suivante : Cette expression est egale ` a expression-1 si condition est satisfaite, et ` a expression-2 sinon. Par exemple, lexpression x >= 0 ? x : -x correspond ` a la valeur absolue dun nombre. De m eme linstruction m = ((a > b) ? a : b); aecte ` a m le maximum de a et de b.

24

Chapitre 1. Les bases de la programmation en C

1.7.10

Lop erateur de conversion de type

Lop erateur de conversion de type, appel e cast, permet de modier explicitement le type dun objet. On ecrit (type) objet Par exemple, main() { int i = 3, j = 2; printf("%f \n",(float)i/j); } retourne la valeur 1.5.

1.7.11

Lop erateur adresse

Lop erateur dadresse & appliqu e` a une variable retourne ladresse-m emoire de cette variable. La syntaxe est &objet

1.7.12

R` egles de priorit e des op erateurs

Le tableau suivant classe les op erateurs par ordres de priorit e d ecroissants. Les op erateurs plac es sur une m eme ligne ont m eme priorit e. Si dans une expression gurent plusieurs op erateurs de m eme priorit e, lordre d evaluation est d enie par la ` eche de la seconde colonne du tableau. On pr eferera toutefois mettre des parenth` eses en cas de doute... op erateurs () [] -> ! ~ ++ * / % + -(binaire) << >> < <= > == != &(et bit-` a-bit) ^ | && || ?: = += -= ,

. --

-(unaire)

(type)

*(indirection)

&(adresse)

sizeof

>=

*=

/=

%=

&=

^=

|=

<<=

>>=

Tab. 1.4 R` egles de priorit e des op erateurs

A. Canteaut - Programmation en langage C

25

Par exemple, les op erateurs logiques bit-` a-bit sont moins prioritaires que les op erateurs relationnels. Cela implique que dans des tests sur les bits, il faut parenth eser les expressions. Par exemple, il faut ecrire if ((x ^ y) != 0)

1.8

Les instructions de branchement conditionnel

On appelle instruction de contr ole toute instruction qui permet de contr oler le fonctionnement dun programme. Parmi les instructions de contr ole, on distingue les instructions de branchement et les boucles. Les instructions de branchement permettent de d eterminer quelles instructions seront ex ecut ees et dans quel ordre.

1.8.1

Branchement conditionnel if---else

La forme la plus g en erale est celle-ci : if ( expression-1 ) instruction-1 else if ( expression-2 ) instruction-2 ... else if ( expression-n ) instruction-n else instruction- avec un nombre quelconque de else if ( ... ). Le dernier else est toujours facultatif. La forme la plus simple est if ( expression ) instruction Chaque instruction peut etre un bloc dinstructions.

1.8.2

Branchement multiple switch

Sa forme la plus g en erale est celle-ci : switch ( expression ) { case constante-1: liste dinstructions 1 break; case constante-2: liste dinstructions 2 break; ... case constante-n: liste dinstructions n

26 break; default: liste dinstructions break; }

Chapitre 1. Les bases de la programmation en C

Si la valeur de expression est egale ` a lune des constantes, la liste dinstructions correspondant est ex ecut ee. Sinon la liste dinstructions correspondant ` a default est ex ecut ee. Linstruction default est facultative.

1.9

Les boucles

Les boucles permettent de r ep eter une s erie dinstructions tant quune certaine condition nest pas v eri ee.

1.9.1

Boucle while

La syntaxe de while est la suivante : while ( expression ) instruction Tant que expression est v eri ee (i.e., non nulle), instruction est ex ecut ee. Si expression est nulle au d epart, instruction ne sera jamais ex ecut ee. instruction peut evidemment etre une instruction compos ee. Par exemple, le programme suivant imprime les entiers de 1 ` a 9. i = 1; while (i < 10) { printf("\n i = %d",i); i++; }

1.9.2

Boucle do---while

Il peut arriver que lon ne veuille eectuer le test de continuation quapr` es avoir ex ecut e linstruction. Dans ce cas, on utilise la boucle do---while. Sa syntaxe est do instruction while ( expression ); Ici, instruction sera ex ecut ee tant que expression est non nulle. Cela signie donc que instruction est toujours ex ecut ee au moins une fois. Par exemple, pour saisir au clavier un entier entre 1 et 10 : int a; do

A. Canteaut - Programmation en langage C { printf("\n Entrez un entier entre 1 et 10 : "); scanf("%d",&a); } while ((a <= 0) || (a > 10));

27

1.9.3

Boucle for

La syntaxe de for est : for ( expr 1 ; expr 2 ; expr 3) instruction Une version equivalente plus intuitive est : expr 1; while ( expr 2 ) { instruction expr 3; } Par exemple, pour imprimer tous les entiers de 0 ` a 9, on ecrit : for (i = 0; i < 10; i++) printf("\n i = %d",i); A la n de cette boucle, i vaudra 10. Les trois expressions utilis ees dans une boucle for peuvent etre constitu ees de plusieurs expressions s epar ees par des virgules. Cela permet par exemple de faire plusieurs initialisations ` a la fois. Par exemple, pour calculer la factorielle dun entier, on peut ecrire : int n, i, fact; for (i = 1, fact = 1; i <= n; i++) fact *= i; printf("%d ! = %d \n",n,fact); On peut egalement ins erer linstruction fact *= i; dans la boucle for ce qui donne : int n, i, fact; for (i = 1, fact = 1; i <= n; fact *= i, i++); printf("%d ! = %d \n",n,fact); On evitera toutefois ce type dacrobaties qui napportent rien et rendent le programme dicilement lisible.

28

Chapitre 1. Les bases de la programmation en C

1.10
1.10.1

Les instructions de branchement non conditionnel


Branchement non conditionnel break

On a vu le r ole de linstruction break; au sein dune instruction de branchement multiple switch. Linstruction break peut, plus g en eralement, etre employ ee ` a lint erieur de nimporte quelle boucle. Elle permet dinterrompre le d eroulement de la boucle, et passe ` a la premi` ere instruction qui suit la boucle. En cas de boucles imbriqu ees, break fait sortir de la boucle la plus interne. Par exemple, le programme suivant : main() { int i; for (i = 0; i < 5; i++) { printf("i = %d\n",i); if (i == 3) break; } printf("valeur de i a la sortie de la boucle = %d\n",i); } imprime ` a l ecran i = 0 i = 1 i = 2 i = 3 valeur de i a la sortie de la boucle = 3

1.10.2

Branchement non conditionnel continue

Linstruction continue permet de passer directement au tour de boucle suivant, sans ex ecuter les autres instructions de la boucle. Ainsi le programme main() { int i; for (i = 0; i < 5; i++) { if (i == 3) continue; printf("i = %d\n",i); } printf("valeur de i a la sortie de la boucle = %d\n",i); } imprime i = 0

A. Canteaut - Programmation en langage C i = 1 i = 2 i = 4 valeur de i a la sortie de la boucle = 5

29

1.10.3

Branchement non conditionnel goto

Linstruction goto permet deectuer un saut jusqu` a linstruction etiquette correspondant. Elle est ` a proscrire de tout programme C digne de ce nom.

1.11

Les fonctions dentr ees-sorties classiques

Il sagit des fonctions de la librairie standard stdio.h utilis ees avec les unit es classiques dentr ees-sorties, qui sont respectivement le clavier et l ecran. Sur certains compilateurs, lappel ` a la librairie stdio.h par la directive au pr eprocesseur #include <stdio.h> nest pas n ecessaire pour utiliser printf et scanf.

1.11.1

La fonction d ecriture printf

La fonction printf est une fonction dimpression format ee, ce qui signie que les donn ees sont converties selon le format particulier choisi. Sa syntaxe est printf("cha^ ne de contr^ ole ",expression-1, ..., expression-n); La cha^ ne de contr^ ole contient le texte ` a acher et les sp ecications de format correspondant ` a chaque expression de la liste. Les sp ecications de format ont pour but dannoncer le format des donn ees ` a visualiser. Elles sont introduites par le caract` ere %, suivi dun caract` ere d esignant le format dimpression. Les formats dimpression en C sont donn es ` a la table 1.5. En plus du caract` ere donnant le type des donn ees, on peut eventuellemnt pr eciser certains param` etres du format dimpression, qui sont sp eci es entre le % et le caract` ere de conversion dans lordre suivant : largeur minimale du champ dimpression : %10d sp ecie quau moins 10 caract` eres seront r eserv es pour imprimer lentier. Par d efaut, la donn ee sera cadr ee ` a droite du champ. Le signe - avant le format signie que la donn ee sera cadr ee ` a gauche du champ (%-10d). pr ecision : %.12f signie quun ottant sera imprim e avec 12 chires apr` es la virgule. De m eme %10.2f signie que lon r eserve 12 caract` eres (incluant le caract` ere .) pour imprimer le ottant et que 2 dentre eux sont destin es aux chires apr` es la virgule. Lorsque la pr ecision nest pas sp eci ee, elle correspond par d efaut ` a 6 chires apr` es la virgule. Pour une cha ne de caract` eres, la pr ecision correspond au nombre de caract` eres imprim es : %30.4s signie que lon r eserve un champ de 30 caract` eres pour imprimer la cha ne mais que seulement les 4 premiers caract` eres seront imprim es (suivis de 26 blancs).

30 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 int unsigned int unsigned long int unsigned int unsigned long int double long double double long double double long double unsigned char char*

Chapitre 1. Les bases de la programmation en C ecriture d ecimale sign ee d ecimale sign ee d ecimale non sign ee d ecimale non sign ee octale non sign ee octale non sign ee hexad ecimale non sign ee hexad ecimale non sign ee d ecimale virgule xe d ecimale virgule xe d ecimale notation exponentielle d ecimale notation exponentielle d ecimale, repr esentation la plus courte parmi %f et %e d ecimale, repr esentation la plus courte parmi %lf et %le caract` ere cha ne de caract` eres

Tab. 1.5 Formats dimpression pour la fonction printf Exemple : #include <stdio.h> main() { int i = 23674; int j = -23674; long int k = (1l << 32); double x = 1e-8 + 1000; char c = A; char *chaine = "chaine de caracteres"; printf("impression de i: \n"); printf("%d \t %u \t %o \t %x",i,i,i,i); printf("\nimpression de j: \n"); printf("%d \t %u \t %o \t %x",j,j,j,j); printf("\nimpression de k: \n"); printf("%d \t %o \t %x",k,k,k); printf("\n%ld \t %lu \t %lo \t %lx",k,k,k,k); printf("\nimpression de x: \n"); printf("%f \t %e \t %g",x,x,x); printf("\n%.2f \t %.2e",x,x); printf("\n%.20f \t %.20e",x,x); printf("\nimpression de c: \n"); printf("%c \t %d",c,c); printf("\nimpression de chaine: \n"); printf("%s \t %.10s",chaine,chaine);

A. Canteaut - Programmation en langage C printf("\n"); } Ce programme imprime ` a l ecran : impression de i: 23674 23674 56172 5c7a impression de j: -23674 4294943622 37777721606 ffffa386 impression de k: 0 0 0 4294967296 4294967296 40000000000 100000000 impression de x: 1000.000000 1.000000e+03 1000 1000.00 1.00e+03 1000.00000001000000000000 1.00000000001000000000e+03 impression de c: A 65 impression de chaine: chaine de caracteres chaine de

31

1.11.2

La fonction de saisie scanf

La fonction scanf permet de saisir des donn ees au clavier et de les stocker aux adresses sp eci ees par les arguments de la fonctions. scanf("cha^ ne de contr^ ole",argument-1,...,argument-n) La cha^ ne de contr^ ole indique le format dans lequel les donn ees lues sont converties. Elle ne contient pas dautres caract` eres (notamment pas de \n). Comme pour printf, les conversions de format sont sp eci ees par un caract` ere pr ec ed e du signe %. Les formats valides pour la fonction scanf di` erent l eg` erement de ceux de la fonction printf. Les donn ees ` a entrer au clavier doivent etre s epar ees par des blancs ou des <RETURN> sauf sil sagit de caract` eres. On peut toutefois xer le nombre de caract` eres de la donn ee ` a lire. Par exemple %3s pour une cha ne de 3 caract` eres, %10d pour un entier qui s etend sur 10 chires, signe inclus. Exemple : #include <stdio.h> main() { int i; printf("entrez un entier sous forme hexadecimale i = "); scanf("%x",&i); printf("i = %d\n",i); } Si on entre au clavier la valeur 1a, le programme ache i = 26.

32 format %d %hd %ld %u %hu %lu %o %ho %lo %x %hx %lx %f %lf %Lf %e %le %Le %g %lg %Lg %c %s type dobjet point e int short int long int unsigned int unsigned short int unsigned long int int short int long int int short int long int float double long double float double long double float double long double char char*

Chapitre 1. Les bases de la programmation en C repr esentation de la donn ee saisie d ecimale sign ee d ecimale sign ee d ecimale sign ee d ecimale non sign ee d ecimale non sign ee d ecimale non sign ee octale octale octale hexad ecimale hexad ecimale hexad ecimale ottante virgule xe ottante virgule xe ottante virgule xe ottante notation exponentielle ottante notation exponentielle ottante notation exponentielle ottante virgule xe ou notation exponentielle ottante virgule xe ou notation exponentielle ottante virgule xe ou notation exponentielle caract` ere cha ne de caract` eres

Tab. 1.6 Formats de saisie pour la fonction scanf

1.11.3

Impression et lecture de caract` eres

Les fonctions getchar et putchar permettent respectivement de lire et dimprimer des caract` eres. Il sagit de fonctions dentr ees-sorties non format ees. La fonction getchar retourne un int correspondant au caract` ere lu. Pour mettre le caract` ere lu dans une variable caractere, on ecrit caractere = getchar(); Lorsquelle d etecte la n de chier, elle retourne lentier EOF (End Of File), valeur d enie dans la librairie stdio.h. En g en eral, la constante EOF vaut -1. La fonction putchar ecrit caractere sur la sortie standard : putchar(caractere); Elle retourne un int correspondant ` a lentier lu ou ` a la constante EOF en cas derreur. Par exemple, le programme suivant lit un chier et le recopie caract` ere par caract` ere ` a l ecran. #include <stdio.h> main() {

A. Canteaut - Programmation en langage C char c; while ((c = getchar()) != EOF) putchar(c); }

33

Pour lex ecuter, il sut dutiliser lop erateur de redirection dUnix : programme-executable < nom-fichier Notons que lexpression (c = getchar()) dans le programme pr ec edent a pour valeur la valeur de lexpression getchar() qui est de type int. Le test (c = getchar()) != EOF compare donc bien deux objets de type int (sign es). Ce nest par contre pas le cas dans le programme suivant : #include <stdio.h> main() { char c; do { c = getchar(); if (c != EOF) putchar(c); } while (c != EOF); } Ici, le test c != EOF compare un objet de type char et la constante EOF qui vaut -1. Si le type char est non sign e par d efaut, cette condition est donc toujours v eri ee. Si le type char est sign e, alors le caract` ere de code 255, y , sera converti en lentier -1. La rencontre du caract` ere y sera donc interpr et ee comme une n de chier. Il est donc recommand e de d eclarer de type int (et non char) une variable destin ee ` a recevoir un caract` ere lu par getchar an de permettre la d etection de n de chier.

1.12

Les conventions d ecriture dun programme C

Il existe tr` es peu de contraintes dans l ecriture dun programme C. Toutefois ne prendre aucune pr ecaution aboutirait ` a des programmes illisibles. Aussi existe-t-il un certain nombre de conventions. On n ecrit quune seule instruction par ligne : le point virgule dune instruction ou dune d eclaration est toujours le dernier caract` ere de la ligne. Les instructions sont dispos ees de telle fa con que la structure modulaire du programme soit mise en evidence. En particulier, une accolade ouvrante marquant le d ebut dun bloc doit etre seule sur sa ligne ou plac ee ` a la n dune ligne. Une accolade fermante est toujours seule sur sa ligne. On laisse un blanc entre les mots-clefs if, while, do, switch et la parenth` ese ouvrante qui suit,

34 apr` es une virgule,

Chapitre 1. Les bases de la programmation en C

de part et dautre dun op erateur binaire. On ne met pas de blanc entre un op erateur unaire et son op erande, ni entre les deux caract` eres dun op erateur daectation compos ee. Les instructions doivent etre indent ees an que toutes les instructions dun m eme bloc soient align ees. Le mieux est dutiliser le mode C dEmacs.

35

Chapitre 2

Les types compos es


A partir des types pr ed enis du C (caract` eres, entiers, ottants), on peut cr eer de nouveaux types, appel es types compos es, qui permettent de repr esenter des ensembles de donn ees organis ees.

2.1

Les tableaux

Un tableau est un ensemble ni d el ements de m eme type, stock es en m emoire ` a des adresses contigu es. La d eclaration dun tableau ` a une dimension se fait de la fa con suivante : type nom-du-tableau[nombre- el ements]; o` u nombre- el ements est une expression constante enti` ere positive. Par exemple, la d eclaration int tab[10]; indique que tab est un tableau de 10 el ements de type int. Cette d eclaration alloue donc en m emoire pour lobjet tab un espace de 10 4 octets cons ecutifs. Pour plus de clart e, il est recommand e de donner un nom ` a la constante nombre- el ements par une directive au pr eprocesseur, par exemple #define nombre- el ements 10 On acc` ede ` a un el ement du tableau en lui appliquant lop erateur []. Les el ements dun tableau sont toujours num erot es de 0 ` a nombre- el ements -1. Le programme suivant imprime les el ements du tableau tab : #define N 10 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 ` a un pointeur vers le premier el ement du tableau. Ce pointeur est constant. Cela implique en particulier quaucune op eration globale nest autoris ee sur un tableau. Notamment, un tableau ne peut pas gurer ` a gauche dun op erateur daectation.

36

Chapitre 2. Les types compos es

Par exemple, on ne peut pas ecrire tab1 = tab2;. Il faut eectuer laectation pour chacun des el ements du tableau : #define N 10 main() { int tab1[N], tab2[N]; int i; ... for (i = 0; i < N; i++) tab1[i] = tab2[i]; } On peut initialiser un tableau lors de sa d eclaration par une liste de constantes de la fa con suivante : type nom-du-tableau[N] = {constante-1,constante-2,...,constante-N}; Par exemple, on peut ecrire #define N 4 int tab[N] = {1, 2, 3, 4}; main() { int i; for (i = 0; i < N; i++) printf("tab[%d] = %d\n",i,tab[i]); } Si le nombre de donn ees dans la liste dinitialisation est inf erieur ` a la dimension du tableau, seuls les premiers el ements seront initialis es. Les autres el ements seront mis ` a z ero si le tableau est une variable globale (ext erieure ` a toute fonction) ou une variable locale de classe de m emorisation static (cf. page 64). De la m eme mani` ere un tableau de caract` eres peut etre initialis e par une liste de caract` eres, mais aussi par une cha ne de caract` eres litt erale. Notons que le compilateur compl` ete toute cha ne de caract` eres avec un caract` ere nul \0. Il faut donc que le tableau ait au moins un el ement de plus que le nombre de caract` eres de la cha ne litt erale. #define N 8 char tab[N] = "exemple"; main() { int i; for (i = 0; i < N; i++) printf("tab[%d] = %c\n",i,tab[i]); } Lors dune initialisation, il est egalement possible de ne pas sp ecier le nombre d el ements du tableau. Par d efaut, il correspondra au nombre de constantes de la liste dinitialisation. Ainsi le programme suivant imprime le nombre de caract` eres du tableau tab, ici 8. char tab[] = "exemple";

A. Canteaut - Programmation en langage C

37

main() { int i; printf("Nombre de caracteres du tableau = %d\n",sizeof(tab)/sizeof(char)); } De mani` ere similaire, on peut d eclarer un tableau ` a plusieurs dimensions. Par exemple, pour un tableau ` a deux dimensions : type nom-du-tableau[nombre-lignes][nombre-colonnes] En fait, un tableau ` a deux dimensions est un tableau unidimensionnel dont chaque el ement est lui-m eme un tableau. On acc` ede ` a un el ement du tableau par lexpression tableau[i][j]. Pour initialiser un tableau ` a plusieurs dimensions ` a la compilation, on utilise une liste dont chaque el ement est une liste de constantes : #define M 2 #define N 3 int tab[M][N] = {{1, 2, 3}, {4, 5, 6}}; main() { int i, j; for (i = 0 ; i < M; i++) { for (j = 0; j < N; j++) printf("tab[%d][%d]=%d\n",i,j,tab[i][j]); } }

2.2

Les structures

Une structure est une suite nie dobjets de types di erents. Contrairement aux tableaux, les di erents el ements dune structure noccupent pas n ecessairement des zones contigu es en m emoire. Chaque el ement de la structure, appel e membre ou champ, est d esign e par un identicateur. On distingue la d eclaration dun mod` ele de structure de celle dun objet de type structure correspondant ` a un mod` ele donn e. La d eclaration dun mod` ele de structure dont lidenticateur est modele suit la syntaxe suivante : struct modele { type-1 membre-1; type-2 membre-2; ... type-n membre-n; };

38

Chapitre 2. Les types compos es

Pour d eclarer un objet de type structure correspondant au mod` ele pr ec edent, on utilise la syntaxe : struct modele objet; ou bien, si le mod` ele na pas et e d eclar e au pr ealable : struct modele { type-1 membre-1; type-2 membre-2; ... type-n membre-n; } objet; On acc` ede aux di erents membres dune structure gr ace ` a lop erateur membre de structure, not e .. Le i-` eme membre de objet est d esign e par lexpression objet.membre-i On peut eectuer sur le i-` eme membre de la structure toutes les op erations valides sur des donn ees de type type-i. Par exemple, le programme suivant d enit la structure complexe, compos ee de deux champs de type double ; il calcule la norme dun nombre complexe. #include <math.h> struct complexe { double reelle; double imaginaire; }; main() { struct complexe z; 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); } Les r` egles dinitialisation dune structure lors de sa d eclaration sont les m emes que pour les tableaux. On ecrit par exemple : struct complexe z = {2. , 2.}; En ANSI C, on peut appliquer lop erateur daectation aux structures (` a la di erence des tableaux). Dans le contexte pr ec edent, on peut ecrire : ... main() { struct complexe z1, z2;

A. Canteaut - Programmation en langage C ... z2 = z1; }

39

2.3

Les champs de bits

Il est possible en C de sp ecier la longueur des champs dune structure au bit pr` es si ce champ est de type entier (int ou unsigned int). Cela se fait en pr ecisant le nombre de bits du champ avant le ; qui suit sa d eclaration. Par exemple, la structure suivante struct registre { unsigned int actif : 1; unsigned int valeur : 31; }; poss` ede deux membres, actif qui est cod e sur un seul bit, et valeur qui est cod e sur 31 bits. Tout objet de type struct registre est donc cod e sur 32 bits. Toutefois, lordre dans lequel les champs sont plac es ` a lint erieur de ce mot de 32 bits d epend de limpl ementation. Le champ actif de la structure ne peut prendre que les valeurs 0 et 1. Aussi, si r est un objet de type struct registre, lop eration r.actif += 2; ne modie pas la valeur du champ. La taille dun champ de bits doit etre inf erieure au nombre de bits dun entier. Notons enn quun champ de bits na pas dadresse ; on ne peut donc pas lui appliquer lop erateur &.

2.4

Les unions

Une union d esigne un ensemble de variables de types di erents susceptibles doccuper alternativement une m eme zone m emoire. Une union permet donc de d enir un objet comme pouvant etre dun type au choix parmi un ensemble ni de types. Si les membres dune union sont de longueurs di erentes, la place r eserv ee en m emoire pour la repr esenter correspond ` a la taille du membre le plus grand. Les d eclarations et les op erations sur les objets de type union sont les m emes que celles sur les objets de type struct. Dans lexemple suivant, la variable hier de type union jour peut etre soit un entier, soit un caract` ere. union jour { char lettre; int numero; }; main() { union jour hier, demain; hier.lettre = J;

40 printf("hier = %c\n",hier.lettre); hier.numero = 4; demain.numero = (hier.numero + 2) % 7; printf("demain = %d\n",demain.numero); }

Chapitre 2. Les types compos es

Les unions peuvent etre utiles lorsquon a besoin de voir un objet sous des types di erents (mais en g en eral de m eme taille). Par exemple, le programme suivant permet de manipuler en m eme temps les deux champs de type unsigned int dune structure en les identiant ` a un objet de type unsigned long (en supposant que la taille dun entier long est deux fois celle dun int). struct coordonnees { unsigned int x; unsigned int y; }; union point { struct coordonnees coord; unsigned long mot; }; main() { union point p1, p2, p3; p1.coord.x = 0xf; p1.coord.y = 0x1; p2.coord.x = 0x8; p2.coord.y = 0x8; p3.mot = p1.mot ^ p2.mot; printf("p3.coord.x = %x \t p3.coord.y = %x\n", p3.coord.x, p3.coord.y); }

2.5

Les enum erations

Les enum erations permettent de d enir un type par la liste des valeurs quil peut prendre. Un objet de type enum eration est d eni par le mot-clef enum et un identicateur de mod` ele, suivis de la liste des valeurs que peut prendre cet objet : enum modele {constante-1, constante-2,...,constante-n}; En r ealit e, les objets de type enum sont repr esent es comme des int. Les valeurs possibles constante-1, constante-2,...,constante-n sont cod ees par des entiers de 0 ` a n-1. Par exemple, le type enum booleen d eni dans le programme suivant associe lentier 0 ` a la valeur faux et lentier 1 ` a la valeur vrai. main()

A. Canteaut - Programmation en langage C { enum booleen {faux, vrai}; enum booleen b; b = vrai; printf("b = %d\n",b); }

41

On peut modier le codage par d efaut des valeurs de la liste lors de la d eclaration du type enum er e, par exemple : enum booleen {faux = 12, vrai = 23};

2.6

D enition de types compos es avec typedef

Pour all eger l ecriture des programmes, on peut aecter un nouvel identicateur ` a un type compos e` a laide de typedef : typedef type synonyme; Par exemple, struct complexe { double reelle; double imaginaire; }; typedef struct complexe complexe; main() { complexe z; ... }

42

Chapitre 2. Les types compos es

43

Chapitre 3

Les pointeurs
Toute variable manipul ee dans un programme est stock ee quelque part en m emoire centrale. Cette m emoire est constitu ee doctets qui sont identi es de mani` ere univoque par un num ero quon appelle adresse. Pour retrouver une variable, il sut donc de conna tre ladresse de loctet o` u elle est stock ee (ou, sil sagit dune variable qui recouvre plusieurs octets contigus, ladresse du premier de ces octets). Pour des raisons evidentes de lisibilit e, on d esigne souvent les variables par des identicateurs, et non par leur adresse. Cest le compilateur qui fait alors le lien entre lidenticateur dune variable et son adresse en m emoire. Toutefois, il est parfois tr` es pratique de manipuler directement une variable par son adresse.

3.1

Adresse et valeur dun objet

On appelle Lvalue (left value) tout objet pouvant etre plac e ` a gauche dun op erateur daectation. Une Lvalue est caract eris ee par : son adresse, cest-` a-dire ladresse-m emoire ` a partir de laquelle lobjet est stock e; sa valeur, cest-` a-dire ce qui est stock e` a cette adresse. Dans lexemple, int i, j; i = 3; j = i; Si le compilateur a plac e la variable i ` a ladresse 4831836000 en m emoire, et la variable j ` a ladresse 4831836004, on a objet i j adresse 4831836000 4831836004 valeur 3 3

Deux variables di erentes ont des adresses di erentes. Laectation i = j; nop` ere que sur les valeurs des variables. Les variables i et j etant de type int, elles sont stock ees sur 4 octets. Ainsi la valeur de i est stock ee sur les octets dadresse 4831836000 ` a 4831836003. Ladresse dun objet etant un num ero doctet en m emoire, il sagit dun entier quelque soit le type de lobjet consid er e. Le format interne de cet entier (16 bits, 32 bits ou 64 bits)

44

Chapitre 3. Les pointeurs

d epend des architectures. Sur un DEC alpha, par exemple, une adresse a toujours le format dun entier long (64 bits). Lop erateur & permet dacc eder ` a ladresse dune variable. Toutefois &i nest pas une Lvalue mais une constante : on ne peut pas faire gurer &i ` a gauche dun op erateur daectation. Pour pouvoir manipuler des adresses, on doit donc recourir un nouveau type dobjets, les pointeurs.

3.2

Notion de pointeur

Un pointeur est un objet (Lvalue) dont la valeur est egale ` a ladresse dun autre objet. On d eclare un pointeur par linstruction : type *nom-du-pointeur; o` u type est le type de lobjet point e. Cette d eclaration d eclare un identicateur, nom-du-pointeur, associ e` a un objet dont la valeur est ladresse dun autre objet de type type. Lidenticateur nom-du-pointeur est donc en quelque sorte un identicateur dadresse. Comme pour nimporte quelle Lvalue, sa valeur est modiable. M eme si la valeur dun pointeur est toujours un entier ( eventuellement un entier long), le type dun pointeur d epend du type de lobjet vers lequel il pointe. Cette distinction est indispensable ` a linterpr etation de la valeur dun pointeur. En eet, pour un pointeur sur un objet de type char, la valeur donne ladresse de loctet o` u cet objet est stock e. Par contre, pour un pointeur sur un objet de type int, la valeur donne ladresse du premier des 4 octets o` u lobjet est stock e. Dans lexemple suivant, on d enit un pointeur p qui pointe vers un entier i : int i = 3; int *p; p = &i; On se trouve dans la conguration objet i p adresse 4831836000 4831836004 valeur 3 4831836000

Lop erateur unaire dindirection * permet dacc eder directement ` a la valeur de lobjet point e. Ainsi, si p est un pointeur vers un entier i, *p d esigne la valeur de i. Par exemple, le programme main() { int i = 3; int *p; p = &i; printf("*p = %d \n",*p); }

A. Canteaut - Programmation en langage C

45

imprime *p = 3. Dans ce programme, les objets i et *p sont identiques : ils ont m emes adresse et valeur. Nous sommes dans la conguration : objet i p *p adresse 4831836000 4831836004 4831836000 valeur 3 4831836000 3

Cela signie en particulier que toute modication de *p modie i. Ainsi, si lon ajoute linstruction *p = 0; ` a la n du programme pr ec edent, la valeur de i devient nulle. On peut donc dans un programme manipuler ` a la fois les objets p et *p. Ces deux manipulations sont tr` es di erentes. Comparons par exemple les deux programmes suivants : main() { int i = 3, j = 6; int *p1, *p2; p1 = &i; p2 = &j; *p1 = *p2; } et main() { int i = 3, j = 6; int *p1, *p2; p1 = &i; p2 = &j; p1 = p2; } Avant la derni` ere aectation de chacun de ces programmes, on est dans une conguration du type : objet i j p1 p2 adresse 4831836000 4831836004 4831835984 4831835992 valeur 3 6 4831836000 4831836004

Apr` es laectation *p1 = *p2; du premier programme, on a objet i j p1 p2 adresse 4831836000 4831836004 4831835984 4831835992 valeur 6 6 4831836000 4831836004

46

Chapitre 3. Les pointeurs

Par contre, laectation p1 = p2 du second programme, conduit ` a la situation : objet i j p1 p2 adresse 4831836000 4831836004 4831835984 4831835992 valeur 3 6 4831836004 4831836004

3.3

Arithm etique des pointeurs

La valeur dun pointeur etant un entier, on peut lui appliquer un certain nombre dop erateurs arithm etiques classiques. Les seules op erations arithm etiques valides sur les pointeurs sont : laddition dun entier ` a un pointeur. Le r esultat est un pointeur de m eme type que le pointeur de d epart ; la soustraction dun entier ` a un pointeur. Le r esultat est un pointeur de m eme type que le pointeur de d epart ; la di erence de deux pointeurs pointant tous deux vers des objets de m eme type. Le r esultat est un entier. Notons que la somme de deux pointeurs nest pas autoris ee. Si i est un entier et p est un pointeur sur un objet de type type, lexpression p + i d esigne un pointeur sur un objet de type type dont la valeur est egale ` a la valeur de p incr ement ee de i * sizeof(type). Il en va de m eme pour la soustraction dun entier ` a un pointeur, et pour les op erateurs dincr ementation et de d ecr ementation ++ et --. Par exemple, le programme main() { int i = 3; int *p1, *p2; p1 = &i; p2 = p1 + 1; printf("p1 = %ld \t p2 = %ld\n",p1,p2); } ache p1 = 4831835984 p2 = 4831835988. Par contre, le m eme programme avec des pointeurs sur des objets de type double : main() { double i = 3; double *p1, *p2; p1 = &i; p2 = p1 + 1; printf("p1 = %ld \t p2 = %ld\n",p1,p2); }

A. Canteaut - Programmation en langage C

47

ache p1 = 4831835984 p2 = 4831835992. Les op erateurs de comparaison sont egalement applicables aux pointeurs, ` a condition de comparer des pointeurs qui pointent vers des objets de m eme type. Lutilisation des op erations arithm etiques sur les pointeurs est particuli` erement utile pour parcourir des tableaux. Ainsi, le programme suivant imprime les el ements du tableau tab dans lordre croissant puis d ecroissant des indices. #define N 5 int tab[5] = {1, 2, 6, 0, 7}; main() { int *p; printf("\n ordre croissant:\n"); for (p = &tab[0]; p <= &tab[N-1]; p++) printf(" %d \n",*p); printf("\n ordre decroissant:\n"); for (p = &tab[N-1]; p >= &tab[0]; p--) printf(" %d \n",*p); } Si p et q sont deux pointeurs sur des objets de type type, lexpression p - q d esigne un entier dont la valeur est egale ` a (p - q)/sizeof(type) .

3.4

Allocation dynamique

Avant de manipuler un pointeur, et notamment de lui appliquer lop erateur dindirection *, il faut linitialiser. Sinon, par d efaut, la valeur du pointeur est egale ` a une constante symbolique not ee NULL d enie dans stdio.h. En g en eral, cette constante vaut 0. Le test p == NULL permet de savoir si le pointeur p pointe vers un objet. On peut initialiser un pointeur p par une aectation sur p. Par exemple, on peut aecter a p ladresse dune autre variable. Il est ` egalement possible daecter directement une valeur a *p. Mais pour cela, il faut dabord r ` eserver ` a *p un espace-m emoire de taille ad equate. Ladresse de cet espace-m emoire sera la valeur de p. Cette op eration consistant ` a r eserver un espace-m emoire pour stocker lobjet point e sappelle allocation dynamique. Elle se fait en C par la fonction malloc de la librairie standard stdlib.h. Sa syntaxe est malloc(nombre-octets) Cette fonction retourne un pointeur de type char * pointant vers un objet de taille nombreoctets octets. Pour initialiser des pointeurs vers des objets qui ne sont pas de type char, il faut convertir le type de la sortie de la fonction malloc ` a laide dun cast. Largument nombre-octets est souvent donn e ` a laide de la fonction sizeof() qui renvoie le nombre doctets utilis es pour stocker un objet. Ainsi, pour initialiser un pointeur vers un entier, on ecrit : #include <stdlib.h> int *p; p = (int*)malloc(sizeof(int));

48

Chapitre 3. Les pointeurs

On aurait pu ecrire egalement p = (int*)malloc(4); puisquun objet de type int est stock e sur 4 octets. Mais on pr ef erera la premi` ere ecriture qui a lavantage d etre portable. Le programme suivant #include <stdio.h> #include <stdlib.h> main() { int i = 3; int *p; printf("valeur de p avant initialisation = %ld\n",p); p = (int*)malloc(sizeof(int)); printf("valeur de p apres initialisation = %ld\n",p); *p = i; printf("valeur de *p = %d\n",*p); } d enit un pointeur p sur un objet *p de type int, et aecte ` a *p la valeur de la variable i. Il imprime ` a l ecran : valeur de p avant initialisation = 0 valeur de p apres initialisation = 5368711424 valeur de *p = 3 Avant lallocation dynamique, on se trouve dans la conguration objet i p adresse 4831836000 4831836004 valeur 3 0

A ce stade, *p na aucun sens. En particulier, toute manipulation de la variable *p g en ererait une violation m emoire, d etectable ` a lex ecution par le message derreur Segmentation fault. Lallocation dynamique a pour r esultat dattribuer une valeur ` a p et de r eserver ` a cette adresse un espace-m emoire compos e de 4 octets pour stocker la valeur de *p. On a alors objet i p *p adresse 4831836000 4831836004 5368711424 valeur 3 5368711424 ? (int)

*p est maintenant d enie mais sa valeur nest pas initialis ee. Cela signie que *p peut valoir nimporte quel entier (celui qui se trouvait pr ec edemment ` a cette adresse). Laectation *p = i; a enn pour r esultat daecter ` a *p la valeur de i. A la n du programme, on a donc objet i p *p adresse 4831836000 4831836004 5368711424 valeur 3 5368711424 3

A. Canteaut - Programmation en langage C Il est important de comparer le programme pr ec edent avec main() { int i = 3; int *p; p = &i; } qui correspond ` a la situation objet i p *p adresse 4831836000 4831836004 4831836000 valeur 3 4831836000 3

49

Dans ce dernier cas, les variables i et *p sont identiques (elles ont la m eme adresse) ce qui implique que toute modication de lune modie lautre. Ceci n etait pas vrai dans lexemple pr ec edent o` u *p et i avaient la m eme valeur mais des adresses di erentes. On remarquera que le dernier programme ne n ecessite pas dallocation dynamique puisque lespace-m emoire ` a ladresse &i est d ej` a r eserv e pour un entier. La fonction malloc permet egalement dallouer un espace pour plusieurs objets contigus en m emoire. On peut ecrire par exemple #include <stdio.h> #include <stdlib.h> main() { int i = 3; int j = 6; int *p; p = (int*)malloc(2 * sizeof(int)); *p = i; *(p + 1) = j; printf("p = %ld \t *p = %d \t p+1 = %ld \t *(p+1) = %d \n",p,*p,p+1,*(p+1)); } On a ainsi r eserv e, ` a ladresse donn ee par la valeur de p, 8 octets en m emoire, qui permettent de stocker 2 objets de type int. Le programme ache p = 5368711424 *p = 3 p+1 = 5368711428 *(p+1) = 6 . La fonction calloc de la librairie stdlib.h a le m eme r ole que la fonction malloc mais elle initialise en plus lobjet point e *p ` a z ero. Sa syntaxe est calloc(nb-objets,taille-objets) Ainsi, si p est de type int*, linstruction p = (int*)calloc(N,sizeof(int));

50 est strictement equivalente ` a p = (int*)malloc(N * sizeof(int)); for (i = 0; i < N; i++) *(p + i) = 0; Lemploi de calloc est simplement plus rapide.

Chapitre 3. Les pointeurs

Enn, lorsque lon na plus besoin de lespace-m emoire allou e dynamiquement (cest-` a-dire quand on nutilise plus le pointeur p), il faut lib erer cette place en m emoire. Ceci se fait ` a laide de linstruction free qui a pour syntaxe free(nom-du-pointeur); A toute instruction de type malloc ou calloc doit etre associ ee une instruction de type free.

3.5

Pointeurs et tableaux

Lusage des pointeurs en C est, en grande partie, orient e vers la manipulation des tableaux.

3.5.1

Pointeurs et tableaux ` a une dimension

Tout tableau en C est en fait un pointeur constant. Dans la d eclaration int tab[10]; tab est un pointeur constant (non modiable) dont la valeur est ladresse du premier el ement du tableau. Autrement dit, tab a pour valeur &tab[0]. On peut donc utiliser un pointeur initialis e` a tab pour parcourir les el ements du tableau. #define N 5 int tab[5] = {1, 2, 6, 0, 7}; main() { int i; int *p; p = tab; for (i = 0; i < N; i++) { printf(" %d \n",*p); p++; } } On acc` ede ` a l el ement dindice i du tableau tab gr ace ` a lop erateur dindexation [], par lexpression tab[i]. Cet op erateur dindexation peut en fait sappliquer ` a tout objet p de type pointeur. Il est li e` a lop erateur dindirection * par la formule p[i] = *(p + i)

A. Canteaut - Programmation en langage C

51

Pointeurs et tableaux se manipulent donc exactement de m eme mani` ere. Par exemple, le programme pr ec edent peut aussi s ecrire #define N 5 int tab[5] = {1, 2, 6, 0, 7}; main() { int i; int *p; p = tab; for (i = 0; i < N; i++) printf(" %d \n", p[i]); } Toutefois, la manipulation de tableaux, et non de pointeurs, poss` ede certains inconv enients d us au fait quun tableau est un pointeur constant. Ainsi on ne peut pas cr eer de tableaux dont la taille est une variable du programme, on ne peut pas cr eer de tableaux bidimensionnels dont les lignes nont pas toutes le m eme nombre d el ements. Ces op erations deviennent possibles d` es que lon manipule des pointeurs allou es dynamiquement. Ainsi, pour cr eer un tableau dentiers ` an el ements o` u n est une variable du programme, on ecrit #include <stdlib.h> main() { int n; int *tab; ... tab = (int*)malloc(n * sizeof(int)); ... free(tab); } Si on veut en plus que tous les el ements du tableau tab soient initialis es ` a z ero, on remplace lallocation dynamique avec malloc par tab = (int*)calloc(n, sizeof(int)); Les el ements de tab sont manipul es avec lop erateur dindexation [], exactement comme pour les tableaux. Les deux di erences principales entre un tableau et un pointeur sont un pointeur doit toujours etre initialis e, soit par une allocation dynamique, soit par aectation dune expression adresse, par exemple p = &i ; un tableau nest pas une Lvalue ; il ne peut donc pas gurer ` a gauche dun op erateur daectation. En particulier, un tableau ne supporte pas larithm etique (on ne peut pas ecrire tab++;).

52

Chapitre 3. Les pointeurs

3.5.2

Pointeurs et tableaux ` a plusieurs dimensions

Un tableau ` a deux dimensions est, par d enition, un tableau de tableaux. Il sagit donc en fait dun pointeur vers un pointeur. Consid erons le tableau ` a deux dimensions d eni par : int tab[M][N]; tab est un pointeur, qui pointe vers un objet lui-m eme de type pointeur dentier. tab a une valeur constante egale ` a ladresse du premier el ement du tableau, &tab[0][0]. De m eme tab[i], pour i entre 0 et M-1, est un pointeur constant vers un objet de type entier, qui est le premier el ement de la ligne dindice i. tab[i] a donc une valeur constante qui est egale ` a &tab[i][0]. Exactement comme pour les tableaux ` a une dimension, les pointeurs de pointeurs ont de nombreux avantages sur les tableaux multi-dimensionn es. On d eclare un pointeur qui pointe sur un objet de type type * (deux dimensions) de la m eme mani` ere quun pointeur, cest-` a-dire type **nom-du-pointeur; De m eme un pointeur qui pointe sur un objet de type type ** ( equivalent ` a un tableau ` a 3 dimensions) se d eclare par type ***nom-du-pointeur; Par exemple, pour cr eer avec un pointeur de pointeur une matrice ` a k lignes et n colonnes ` a coecients entiers, on ecrit : main() { int k, n; int **tab; tab = (int**)malloc(k * sizeof(int*)); for (i = 0; i < k; i++) tab[i] = (int*)malloc(n * sizeof(int)); .... for (i = 0; i < k; i++) free(tab[i]); free(tab); } La premi` ere allocation dynamique r eserve pour lobjet point e par tab lespace-m emoire correspondant ` a k pointeurs sur des entiers. Ces k pointeurs correspondent aux lignes de la matrice. Les allocations dynamiques suivantes r eservent pour chaque pointeur tab[i] lespace-m emoire n ecessaire pour stocker n entiers. Si on d esire en plus que tous les el ements du tableau soient initialis es ` a z ero, il sut de remplacer lallocation dynamique dans la boucle for par tab[i] = (int*)calloc(n, sizeof(int));

A. Canteaut - Programmation en langage C

53

Contrairement aux tableaux ` a deux dimensions, on peut choisir des tailles di erentes pour chacune des lignes tab[i]. Par exemple, si lon veut que tab[i] contienne exactement i+1 el ements, on ecrit for (i = 0; i < k; i++) tab[i] = (int*)malloc((i + 1) * sizeof(int));

3.5.3

Pointeurs et cha nes de caract` eres

On a vu pr ec edemment quune cha ne de caract` eres etait un tableau ` a une dimension dobjets de type char, se terminant par le caract` ere nul \0. On peut donc manipuler toute cha ne de caract` eres ` a laide dun pointeur sur un objet de type char. On peut faire subir ` a une cha ne d enie par char *chaine; des aectations comme chaine = "ceci est une chaine"; et toute op eration valide sur les pointeurs, comme linstruction chaine++;. Ainsi, le programme suivant imprime le nombre de caract` eres dune cha ne (sans compter le caract` ere nul). #include <stdio.h> main() { int i; char *chaine; chaine = "chaine de caracteres"; for (i = 0; *chaine != \0; i++) chaine++; printf("nombre de caracteres = %d\n",i); } La fonction donnant la longueur dune cha ne de caract` eres, d enie dans la librairie standard string.h, proc` ede de mani` ere identique. Il sagit de la fonction strlen dont la syntaxe est strlen(chaine); o` u chaine est un pointeur sur un objet de type char. Cette fonction renvoie un entier dont la valeur est egale ` a la longueur de la cha ne pass ee en argument (moins le caract` ere \0). Lutilisation de pointeurs de caract` ere et non de tableaux permet par exemple de cr eer une cha ne correspondant ` a la concat enation de deux cha nes de caract` eres : #include <stdio.h> #include <stdlib.h> #include <string.h> main() {

54 int i; char *chaine1, *chaine2, *res, *p;

Chapitre 3. Les pointeurs

chaine1 = "chaine "; chaine2 = "de caracteres"; res = (char*)malloc((strlen(chaine1) + strlen(chaine2)) * sizeof(char)); p = res; for (i = 0; i < strlen(chaine1); i++) *p++ = chaine1[i]; for (i = 0; i < strlen(chaine2); i++) *p++ = chaine2[i]; printf("%s\n",res); } On remarquera lutilisation dun pointeur interm ediaire p qui est indispensable d` es que lon fait des op erations de type incr ementation. En eet, si on avait incr ement e directement la valeur de res, on aurait evidemment perdu la r ef erence sur le premier caract` ere de la cha ne. Par exemple, #include <stdio.h> #include <stdlib.h> #include <string.h> main() { int i; char *chaine1, *chaine2, *res; chaine1 = "chaine "; chaine2 = "de caracteres"; res = (char*)malloc((strlen(chaine1) + strlen(chaine2)) * sizeof(char)); for (i = 0; i < strlen(chaine1); i++) *res++ = chaine1[i]; for (i = 0; i < strlen(chaine2); i++) *res++ = chaine2[i]; printf("\nnombre de caracteres de res = %d\n",strlen(res)); } imprime la valeur 0, puisque res a et e modi e au cours du programme et pointe maintenant sur le caract` ere nul.

3.6
3.6.1

Pointeurs et structures
Pointeur sur une structure

Contrairement aux tableaux, les objets de type structure en C sont des Lvalues. Ils poss` edent une adresse, correspondant ` a ladresse du premier el ement du premier membre de la structure. On peut donc manipuler des pointeurs sur des structures. Ainsi, le programme suivant cr ee, ` a laide dun pointeur, un tableau dobjets de type structure. #include <stdlib.h>

A. Canteaut - Programmation en langage C #include <stdio.h> struct eleve { char nom[20]; int date; }; typedef struct eleve *classe; main() { int n, i; classe tab; printf("nombre deleves de la classe = "); scanf("%d",&n); tab = (classe)malloc(n * sizeof(struct eleve)); for (i =0 ; i < n; i++) { printf("\n saisie de leleve numero %d\n",i); printf("nom de leleve = "); scanf("%s",&tab[i].nom); printf("\n date de naissance JJMMAA = "); scanf("%d",&tab[i].date); } printf("\n Entrez un numero "); scanf("%d",&i); printf("\n Eleve numero %d:",i); printf("\n nom = %s",tab[i].nom); printf("\n date de naissance = %d\n",tab[i].date); free(tab); }

55

Si p est un pointeur sur une structure, on peut acc eder ` a un membre de la structure point e par lexpression (*p).membre Lusage de parenth` eses est ici indispensable car lop erateur dindirection * ` a une priorit e plus elev ee que lop erateur de membre de structure. Cette notation peut etre simpli ee gr ace ` a lop erateur pointeur de membre de structure, not e ->. Lexpression pr ec edente est strictement equivalente ` a p->membre Ainsi, dans le programme pr ec edent, on peut remplacer tab[i].nom et tab[i].date respectivement par (tab + i)->nom et (tab + i)->date.

56

Chapitre 3. Les pointeurs

3.6.2

Structures auto-r ef erenc ees

On a souvent besoin en C de mod` eles de structure dont un des membres est un pointeur vers une structure de m eme mod` ele. Cette repr esentation permet en particulier de construire des listes cha n ees. En eet, il est possible de repr esenter une liste d el ements de m eme type par un tableau (ou un pointeur). Toutefois, cette repr esentation, dite contigu e, impose que la taille maximale de la liste soit connue a priori (on a besoin du nombre d el ements du tableau lors de lallocation dynamique). Pour r esoudre ce probl` eme, on utilise une repr esentation cha n ee : l el ement de base de la cha ne est une structure appel ee cellule qui contient la valeur dun el ement de la liste et un pointeur sur l el ement suivant. Le dernier el ement pointe sur la liste vide NULL. La liste est alors d enie comme un pointeur sur le premier el ement de la cha ne. liste L
- 1 - 2 - 3 - 4 - NULL

Pour repr esenter une liste dentiers sous forme cha n ee, on cr ee le mod` ele de structure cellule qui a deux champs : un champ valeur de type int, et un champ suivant de type pointeur sur une struct cellule. Une liste sera alors un objet de type pointeur sur une struct cellule. Gr ace au mot-clef typedef, on peut d enir le type liste, synonyme du type pointeur sur une struct cellule. struct cellule { int valeur; struct cellule *suivant; }; typedef struct cellule *liste; Un des avantages de la repr esentation cha n ee est quil est tr` es facile dins erer un el ement ` a un endroit quelconque de la liste. Ainsi, pour ins erer un el ement en t ete de liste, on utilise la fonction suivante : liste insere(int element, liste Q) { liste L; L = (liste)malloc(sizeof(struct cellule)); L->valeur = element; L->suivant = Q; return(L); } Le programme suivant cr ee une liste dentiers et limprime ` a l ecran : #include <stdlib.h> #include <stdio.h> struct cellule { int valeur;

A. Canteaut - Programmation en langage C struct cellule *suivant; }; typedef struct cellule *liste; liste insere(int element, liste Q) { liste L; L = (liste)malloc(sizeof(struct cellule)); L->valeur = element; L->suivant = Q; return(L); } main() { liste L, P; L = insere(1,insere(2,insere(3,insere(4,NULL)))); printf("\n impression de la liste:\n"); P = L; while (P != NULL) { printf("%d \t",P->valeur); P = P->suivant; } } On utilisera egalement une structure auto-r ef erenc ee pour cr eer un arbre binaire : struct noeud { int valeur; struct noeud *fils_gauche; struct noeud *fils_droit; }; typedef struct noeud *arbre;

57

58

Chapitre 3. Les pointeurs

59

Chapitre 4

Les fonctions
Comme dans la plupart des langages, on peut en C d ecouper un programme en plusieurs fonctions. Une seule de ces fonctions existe obligatoirement ; cest la fonction principale appel ee main. Cette fonction principale peut, eventuellement, appeler une ou plusieurs fonctions secondaires. De m eme, chaque fonction secondaire peut appeler dautres fonctions secondaires ou sappeler elle-m eme (dans ce dernier cas, on dit que la fonction est r ecursive).

4.1

D enition dune fonction

La d enition dune fonction est la donn ee du texte de son algorithme, quon appelle corps de la fonction. Elle est de la forme type { [ d eclarations de variables locales ] liste dinstructions } La premi` ere ligne de cette d enition est len-t ete de la fonction. Dans cet en-t ete, type d esigne le type de la fonction, cest-` a-dire le type de la valeur quelle retourne. Contrairement ` a dautres langages, il ny a pas en C de notion de proc edure ou de sous-programme. Une fonction qui ne renvoie pas de valeur est une fonction dont le type est sp eci e par le motclef void. Les arguments de la fonction sont appel es param` etres formels, par opposition aux param` etres eectifs qui sont les param` etres avec lesquels la fonction est eectivement appel ee. Les param` etres formels peuvent etre de nimporte quel type. Leurs identicateurs nont dimportance qu` a lint erieur de la fonction. Enn, si la fonction ne poss` ede pas de param` etres, on remplace la liste de param` etres formels par le mot-clef void. Le corps de la fonction d ebute eventuellement par des d eclarations de variables, qui sont locales ` a cette fonction. Il se termine par linstruction de retour ` a la fonction appelante, return, dont la syntaxe est return(expression); La valeur de expression est la valeur que retourne la fonction. Son type doit etre le m eme que celui qui a et e sp eci e dans len-t ete de la fonction. Si la fonction ne retourne pas de valeur (fonction de type void), sa d enition sach` eve par return; nom-fonction ( type-1 arg-1,..., type-n arg-n)

60

Chapitre 4. Les fonctions

Plusieurs instructions return peuvent appara tre dans une fonction. Le retour au programme appelant sera alors provoqu e par le premier return rencontr e lors de lex ecution. Voici quelques exemples de d enitions de fonctions : int produit (int a, int b) { return(a*b); } int puissance (int a, int n) { if (n == 0) return(1); return(a * puissance(a, n-1)); } void imprime_tab (int *tab, int nb_elements) { int i; for (i = 0; i < nb_elements; i++) printf("%d \t",tab[i]); printf("\n"); return; }

4.2

Appel dune fonction


nom-fonction(para-1,para-2,...,para-n)

Lappel dune fonction se fait par lexpression

Lordre et le type des param` etres eectifs de la fonction doivent concorder avec ceux donn es dans len-t ete de la fonction. Les param` etres eectifs peuvent etre des expressions. La virgule qui s epare deux param` etres eectifs est un simple signe de ponctuation ; il ne sagit pas de lop erateur virgule. Cela implique en particulier que lordre d evaluation des param` etres eectifs nest pas assur e et d epend du compilateur. Il est donc d econseill e, pour une fonction a plusieurs param` ` etres, de faire gurer des op erateurs dincr ementation ou de d ecr ementation (++ ou --) dans les expressions d enissant les param` etres eectifs (cf. Chapitre 1, page 23).

4.3

D eclaration dune fonction

Le C nautorise pas les fonctions imbriqu ees. La d enition dune fonction secondaire doit donc etre plac ee soit avant, soit apr` es la fonction principale main. Toutefois, il est indispensable que le compilateur connaisse la fonction au moment o` u celle-ci est appel ee. Si une fonction est d enie apr` es son premier appel (en particulier si sa d enition est plac ee apr` es la fonction main), elle doit imp erativement etre d eclar ee au pr ealable. Une fonction secondaire est d eclar ee par son prototype, qui donne le type de la fonction et celui de ses param` etres, sous la forme : type nom-fonction(type-1,...,type-n);

A. Canteaut - Programmation en langage C

61

Les fonctions secondaires peuvent etre d eclar ees indi eremment avant ou au d ebut de la fonction main. Par exemple, on ecrira int puissance (int, int ); int puissance (int a, int n) { if (n == 0) return(1); return(a * puissance(a, n-1)); } main() { int a = 2, b = 5; printf("%d\n", puissance(a,b)); } M eme si la d eclaration est parfois facultative (par exemple quand les fonctions sont d enies avant la fonction main et dans le bon ordre), elle seule permet au compilateur de v erier que le nombre et le type des param` etres utilis es dans la d enition concordent bien avec le protype. De plus, la pr esence dune d eclaration permet au compilateur de mettre en place d eventuelles conversions des param` etres eectifs, lorsque la fonction est appel ee avec des param` etres dont les types ne correspondent pas aux types indiqu es dans le prototype. Ainsi les chiers dextension .h de la librairie standard (chiers headers) contiennent notamment les prototypes des fonctions de la librairie standard. Par exemple, on trouve dans le chier math.h le prototype de la fonction pow ( el evation ` a la puissance) : extern double pow(double , double );

La directive au pr eprocesseur #include <math.h> permet au pr eprocesseur dinclure la d eclaration de la fonction pow dans le chier source. Ainsi, si cette fonction est appel ee avec des param` etres de type int, ces param` etres seront convertis en double lors de la compilation. Par contre, en labsence de directive au pr eprocesseur, le compilateur ne peut eectuer la conversion de type. Dans ce cas, lappel ` a la fonction pow avec des param` etres de type int peut produire un r esultat faux !

4.4

Dur ee de vie des variables

Les variables manipul ees dans un programme C ne sont pas toutes trait ees de la m eme mani` ere. En particulier, elles nont pas toutes la m eme dur ee de vie. On distingue deux cat egories de variables.

62

Chapitre 4. Les fonctions

Les variables permanentes (ou statiques) Une variable permanente occupe un emplacement en m emoire qui reste le m eme durant toute lex ecution du programme. Cet emplacement est allou e une fois pour toutes lors de la compilation. La partie de la m emoire contenant les variables permanentes est appel ee segment de donn ees. Par d efaut, les variables permanentes sont initialis ees ` a z ero par le compilateur. Elles sont caract eris ees par le mot-clef static. Les variables temporaires Les variables temporaires se voient allouer un emplacement en m emoire de fa con dynamique lors de lex ecution du programme. Elles ne sont pas initialis ees par d efaut. Leur emplacement en m emoire est lib er e par exemple ` a la n de lex ecution dune fonction secondaire. Par d efaut, les variables temporaires sont situ ees dans la partie de la m emoire appel ee segment de pile. Dans ce cas, la variable est dite automatique. Le sp ecicateur de type correspondant, auto, est rarement utilis e puisquil ne sapplique quaux variables temporaires qui sont automatiques par d efaut. Une variable temporaire peut egalement etre plac ee dans un registre de la machine. Un registre est une zone m emoire sur laquelle sont eectu ees les op erations machine. Il est donc beaucoup plus rapide dacc eder ` a un registre qu` a toute autre partie de la m emoire. On peut demander au compilateur de ranger une variable tr` es utilis ee dans un registre, ` a laide de lattribut de type register. Le nombre de registres etant limit e, cette requ ete ne sera satisfaite que sil reste des registres disponibles. Cette technique permettant dacc el erer les programmes a aujourdhui perdu tout son int er et. Gr ace aux performances des optimiseurs de code int egr es au compilateur (cf. options -O de gcc, page 10), il est maintenant plus ecace de compiler un programme avec une option doptimisation que de placer certaines variables dans des registres. La dur ee de vie des variables est li ee ` a leur port ee, cest-` a-dire ` a la portion du programme dans laquelle elles sont d enies.

4.4.1

Variables globales

On appelle variable globale une variable d eclar ee en dehors de toute fonction. Une variable globale est connue du compilateur dans toute la portion de code qui suit sa d eclaration. Les variables globales sont syst ematiquement permanentes. Dans le programme suivant, n est une variable globale : int n; void fonction(); void fonction() { n++; printf("appel numero %d\n",n); return; } main() { int i;

A. Canteaut - Programmation en langage C for (i = 0; i < 5; i++) fonction(); }

63

La variable n est initialis ee ` a z ero par le compilateur et il sagit dune variable permanente. En eet, le programme ache appel appel appel appel appel numero numero numero numero numero 1 2 3 4 5

4.4.2

Variables locales

On appelle variable locale une variable d eclar ee ` a lint erieur dune fonction (ou dun bloc dinstructions) du programme. Par d efaut, les variables locales sont temporaires. Quand une fonction est appel ee, elle place ses variables locales dans la pile. A la sortie de la fonction, les variables locales sont d epil ees et donc perdues. Les variables locales nont en particulier aucun lien avec des variables globales de m eme nom. Par exemple, le programme suivant int n = 10; void fonction(); void fonction() { int n = 0; n++; printf("appel numero %d\n",n); return; } main() { int i; for (i = 0; i < 5; i++) fonction(); } ache appel appel appel appel appel numero numero numero numero numero 1 1 1 1 1

Les variables locales ` a une fonction ont une dur ee de vie limit ee ` a une seule ex ecution de cette fonction. Leurs valeurs ne sont pas conserv ees dun appel au suivant.

64

Chapitre 4. Les fonctions

Il est toutefois possible de cr eer une variable locale de classe statique en faisant pr ec eder sa d eclaration du mot-clef static : static type nom-de-variable; Une telle variable reste locale ` a la fonction dans laquelle elle est d eclar ee, mais sa valeur est conserv ee dun appel au suivant. Elle est egalement initialis ee ` a z ero ` a la compilation. Par exemple, dans le programme suivant, n est une variable locale ` a la fonction secondaire fonction, mais de classe statique. int n = 10; void fonction(); void fonction() { static int n; n++; printf("appel numero %d\n",n); return; } main() { int i; for (i = 0; i < 5; i++) fonction(); } Ce programme ache appel appel appel appel appel numero numero numero numero numero 1 2 3 4 5

On voit que la variable locale n est de classe statique (elle est initialis ee ` a z ero, et sa valeur est conserv ee dun appel ` a lautre de la fonction). Par contre, il sagit bien dune variable locale, qui na aucun lien avec la variable globale du m eme nom.

4.5

Transmission des param` etres dune fonction

Les param` etres dune fonction sont trait es de la m eme mani` ere que les variables locales de classe automatique : lors de lappel de la fonction, les param` etres eectifs sont copi es dans le segment de pile. La fonction travaille alors uniquement sur cette copie. Cette copie dispara t lors du retour au programme appelant. Cela implique en particulier que, si la fonction modie la valeur dun de ses param` etres, seule la copie sera modi ee ; la variable du programme

A. Canteaut - Programmation en langage C

65

appelant, elle, ne sera pas modi ee. On dit que les param` etres dune fonction sont transmis par valeurs. Par exemple, le programme suivant void echange (int, int ); void echange (int a, int b) { int t; printf("debut fonction :\n a = %d \t b = %d\n",a,b); t = a; a = b; b = t; printf("fin fonction :\n a = %d \t b = %d\n",a,b); return; } main() { int a = 2, b = 5; printf("debut programme principal :\n a = %d \t b = %d\n",a,b); echange(a,b); printf("fin programme principal :\n a = %d \t b = %d\n",a,b); } imprime debut programme principal : a = 2 b = 5 debut fonction : a = 2 b = 5 fin fonction : a = 5 b = 2 fin programme principal : a = 2 b = 5 Pour quune fonction modie la valeur dun de ses arguments, il faut quelle ait pour param` etre ladresse de cet objet et non sa valeur. Par exemple, pour echanger les valeurs de deux variables, il faut ecrire : void echange (int *, int *); void echange (int *adr_a, int *adr_b) { int t; t = *adr_a; *adr_a = *adr_b; *adr_b = t; return;

66 }

Chapitre 4. Les fonctions

main() { int a = 2, b = 5; printf("debut programme principal :\n a = %d \t b = %d\n",a,b); echange(&a,&b); printf("fin programme principal :\n a = %d \t b = %d\n",a,b); } Rappelons quun tableau est un pointeur (sur le premier el ement du tableau). Lorsquun tableau est transmis comme param` etre ` a une fonction secondaire, ses el ements sont donc modi es par la fonction. Par exemple, le programme #include <stdlib.h> void init (int *, int ); void init (int *tab, int n) { int i; for (i = 0; i < n; i++) tab[i] = i; return; } main() { int i, n = 5; int *tab; tab = (int*)malloc(n * sizeof(int)); init(tab,n); } initialise les el ements du tableau tab.

4.6

Les qualicateurs de type const et volatile

Les qualicateurs de type const et volatile permettent de r eduire les possibilit es de modier une variable. const Une variable dont le type est quali e par const ne peut pas etre modi ee. Ce qualicateur est utilis e pour se prot eger dune erreur de programmation. On lemploie principalement pour qualier le type des param` etres dune fonction an d eviter de les modier involontairement.

A. Canteaut - Programmation en langage C

67

volatile Une variable dont le type est quali e par volatile ne peut pas etre impliqu ee dans les optimisations eectu ees par le compilateur. On utilise ce qualicateur pour les variables susceptibles d etre modi ees par une action ext erieure au programme. Les qualicateurs de type se placent juste avant le type de la variable, par exemple const char c; d esigne un caract` ere non modiable. Ils doivent toutefois etre utilis es avec pr ecaution avec les pointeurs. En eet, const char *p; d enit un pointeur sur un caract` ere constant, tandis que char * const p; d enit un pointeur constant sur un caract` ere.

4.7

La fonction main

La fonction principale main est une fonction comme les autres. Nous avons jusqu` a pr esent consid er e quelle etait de type void, ce qui est tol er e par le compilateur. Toutefois l ecriture main() provoque un message davertissement lorsquon utilise loption -Wall de gcc : % gcc -Wall prog.c prog.c:5: warning: return-type defaults to int prog.c: In function main: prog.c:11: warning: control reaches end of non-void function En fait, la fonction main est de type int. Elle doit retourner un entier dont la valeur est transmise ` a lenvironnement dex ecution. Cet entier indique si le programme sest ou non d eroul e sans erreur. La valeur de retour 0 correspond ` a une terminaison correcte, toute valeur de retour non nulle correspond ` a une terminaison sur une erreur. On peut utiliser comme egale ` a 0) et EXIT FAILURE valeur de retour les deux constantes symboliques EXIT SUCCESS ( ( egale ` a 1) d enies dans stdlib.h. Linstruction return(statut); dans la fonction main, o` u statut est un entier sp eciant le type de terminaison du programme, peut etre remplac ee par un appel ` a la fonction exit de la librairie standard (stdlib.h). La fonction exit, de prototype void exit(int statut); provoque une terminaison normale du programme en notiant un succ` es ou un echec selon la valeur de lentier statut. Lorsquelle est utilis ee sans arguments, la fonction main a donc pour prototype int main(void);

68

Chapitre 4. Les fonctions

On sattachera d esormais dans les programmes ` a respecter ce prototype et ` a sp ecier les valeurs de retour de main. La fonction main peut egalement poss eder des param` etres formels. En eet, un programme C peut recevoir une liste darguments au lancement de son ex ecution. La ligne de commande qui sert ` a lancer le programme est, dans ce cas, compos ee du nom du chier ex ecutable suivi par des param` etres. La fonction main re coit tous ces el ements de la part de linterpr eteur de commandes. En fait, la fonction main poss` ede deux param` etres formels, appel es par convention argc (argument count) et argv (argument vector). argc est une variable de type int dont la valeur est egale au nombre de mots composant la ligne de commande (y compris le nom de lex ecutable). Elle est donc egale au nombre de param` etres eectifs de la fonction + 1. argv est un tableau de cha nes de caract` eres correspondant chacune ` a un mot de la ligne de commande. Le premier el ement argv[0] contient donc le nom de la commande (du chier ex ecutable), le second argv[1] contient le premier param` etre. . . . Le second prototype valide de la fonction main est donc int main ( int argc, char *argv[]); Ainsi, le programme suivant calcule le produit de deux entiers, entr es en arguments de lex ecutable : #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int a, b; if (argc != 3) { printf("\nErreur : nombre invalide darguments"); printf("\nUsage: %s int int\n",argv[0]); return(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); printf("\nLe produit de %d par %d vaut : %d\n", a, b, a * b); return(EXIT_SUCCESS); } On lance donc lex ecutable avec deux param` etres entiers, par exemple, a.out 12 8 Ici, argv sera un tableau de 3 cha nes de caract` eres argv[0], argv[1] et argv[2] qui, dans notre exemple, valent respectivement "a.out", "12" et "8". Enn, la fonction de la librairie standard atoi(), d eclar ee dans stdlib.h, prend en argument une cha ne de caract` eres et retourne lentier dont elle est l ecriture d ecimale.

A. Canteaut - Programmation en langage C

69

4.8

Pointeur sur une fonction

Il est parfois utile de passer une fonction comme param` etre dune autre fonction. Cette proc edure permet en particulier dutiliser une m eme fonction pour di erents usages. Pour cela, on utilise un m ecanisme de pointeur. Un pointeur sur une fonction correspond ` a ladresse du d ebut du code de la fonction. Un pointeur sur une fonction ayant pour prototype type fonction(type 1,...,type n); est de type type (*)(type 1,...,type n); etres deux entiers et une Ainsi, une fonction operateur binaire prenant pour param` fonction de type int, qui prend elle-m eme deux entiers en param` etres, sera d enie par : int operateur_binaire(int a, int b, int (*f)(int, int)) Sa d eclaration est donn ee par int operateur_binaire(int, int, int(*)(int, int)); Pour appeler la fonction operateur binaire, on utilisera comme troisi` eme param` etre eectif lidenticateur de la fonction utilis ee, par exemple, si somme est une fonction de prototype int somme(int, int); on appelle la fonction operateur binaire pour la fonction somme par lexpression operateur_binaire(a,b,somme) Notons quon nutilise pas la notation &somme comme param` etre eectif de operateur binaire. Pour appeler la fonction pass ee en param` etre dans le corps de la fonction operateur binaire, on ecrit (*f)(a, b). Par exemple int operateur_binaire(int a, int b, int (*f)(int, int)) { return((*f)(a,b)); } Ainsi, le programme suivant prend comme arguments deux entiers s epar es par la cha ne de caract` eres plus ou fois, et retourne la somme ou le produit des deux entiers. #include <stdlib.h> #include <stdio.h> #include <string.h> void usage(char *); int somme(int, int); int produit(int, int); int operateur_binaire(int, int, int(*)(int, int)); void usage(char *cmd)

70 { printf("\nUsage: %s int [plus|fois] int\n",cmd); return; } int somme(int a, int b) { return(a + b); } int produit(int a, int b) { return(a * b); } int operateur_binaire(int a, int b, int (*f)(int, int)) { return((*f)(a,b)); } int main(int argc, char *argv[]) { int a, b;

Chapitre 4. Les fonctions

if (argc != 4) { printf("\nErreur : nombre invalide darguments"); usage(argv[0]); return(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[3]); if (!strcmp(argv[2], "plus")) { printf("%d\n",operateur_binaire(a,b,somme)); return(EXIT_SUCCESS); } if (!strcmp(argv[2], "fois")) { printf("%d\n",operateur_binaire(a,b,produit)); return(EXIT_SUCCESS); } else { printf("\nErreur : argument(s) invalide(s)"); usage(argv[0]);

A. Canteaut - Programmation en langage C return(EXIT_FAILURE); } }

71

Les pointeurs sur les fonctions sont notamment utilis es dans la fonction de tri des el ements dun tableau qsort et dans la recherche dun el ement dans un tableau bsearch. Ces deux fonctions sont d enies dans la libriarie standard (stdlib.h). Le prototype de la fonction de tri (algorithme quicksort) est void qsort(void *tableau, size_t nb_elements, size_t taille_elements, int(*comp)(const void *, const void *)); Elle permet de trier les nb elements premiers el ements du tableau tableau. Le param` etre taille elements donne la taille des el ements du tableau. Le type size t utilis e ici est un type pr ed eni dans stddef.h. Il correspond au type du r esultat de l evaluation de sizeof. Il sagit du plus grand type entier non sign e. La fonction qsort est param etr ee par la fonction de comparaison utilis ee de prototype : int comp(void *a, void *b); Les deux param` etres a et b de la fonction comp sont des pointeurs g en eriques de type void *. Ils correspondent ` a des adresses dobjets dont le type nest pas d etermin e. Cette fonction de comparaison retourne un entier qui vaut 0 si les deux objets point es par a et b sont egaux et qui prend une valeur strictement n egative (resp. positive) si lobjet point e par a est strictement inf erieur (resp. sup erieur) ` a celui point e par b. Par exemple, la fonction suivante comparant deux cha nes de caract` eres peut etre utilis ee comme param` etre de qsort : int comp_str(char **, char **); int comp_str(char **s1, char **s2) { return(strcmp(*s1,*s2)); } Le programme suivant donne un exemple de lutilisation de la fonction de tri qsort pour trier les el ements dun tableau dentiers, et dun tableau de cha nes de caract` eres. #include <stdlib.h> #include <stdio.h> #include <string.h> #define NB_ELEMENTS 10 void imprime_tab1(int*, int); void imprime_tab2(char**, int); int comp_int(int *, int *); int comp_str(char **, char **); void imprime_tab1(int *tab, int nb)

72 { int i; printf("\n"); for (i = 0; i < nb; i++) printf("%d \t",tab[i]); printf("\n"); return; } void imprime_tab2(char **tab, int nb) { int i; printf("\n"); for (i = 0; i < nb; i++) printf("%s \t",tab[i]); printf("\n"); return; } int comp_int(int *a, int *b) { return(*a - *b); } int comp_str(char **s1, char **s2) { return(strcmp(*s1,*s2)); }

Chapitre 4. Les fonctions

int main() { int *tab1; char *tab2[NB_ELEMENTS] = {"toto", "Auto", "auto", "titi", "a", "b",\ "z", "i , "o","d"}; int i; tab1 = (int*)malloc(NB_ELEMENTS * sizeof(int)); for (i = 0 ; i < NB_ELEMENTS; i++) tab1[i] = random() % 1000; imprime_tab1(tab1, NB_ELEMENTS); qsort(tab1, NB_ELEMENTS, sizeof(int), comp_int); imprime_tab1(tab1, NB_ELEMENTS); /************************/ imprime_tab2(tab2, NB_ELEMENTS); qsort(tab2, NB_ELEMENTS, sizeof(tab2[0]), comp_str); imprime_tab2(tab2, NB_ELEMENTS); return(EXIT_SUCCESS); }

A. Canteaut - Programmation en langage C

73

La librairie standard dispose egalement dune fonction de recherche dun el ement dans un tableau tri e, ayant le prototype suivant : void *bsearch((const void *clef, const void *tab, size_t nb_elements, size_t taille_elements, int(*comp)(const void *, const void *))); Cette fonction recherche dans le tableau tri e tab un el ement qui soit egal ` a l el ement dadresse clef. Les autres param` etres sont identiques ` a ceux de la fonction qsort. Sil existe dans le tableau tab un el ement egal ` a celui point e par clef, la fonction bsearch retourne son adresse (de type void *). Sinon, elle retourne le pointeur NULL. Ainsi, le programme suivant prend en argument une cha ne de caract` eres et d etermine si elle gure dans un tableau de cha nes de caract` eres pr ed eni, sans di erencier minuscules et majuscules. Rappelons que bsearch ne sapplique quaux tableaux tri es ; il faut donc appliquer au pr ealable la fonction de tri qsort. #include #include #include #include <stdlib.h> <stdio.h> <string.h> <ctype.h>

#define NB_ELEMENTS 4 int comp_str_maj(char **, char **); int comp_str_maj(char **s1, char **s2) { int i; char *chaine1, *chaine2; chaine1 = (char*)malloc(strlen(*s1) * sizeof(char)); chaine2 = (char*)malloc(strlen(*s2) * sizeof(char)); for (i = 0; i < strlen(*s1); i++) chaine1[i] = tolower((*s1)[i]); for (i = 0; i < strlen(*s2); i++) chaine2[i] = tolower((*s2)[i]); return(strcmp(chaine1,chaine2)); } int main(int argc, char *argv[]) { char *tab[NB_ELEMENTS] = {"TOTO", "Auto", "auto", "titi"}; char **res; qsort(tab, NB_ELEMENTS, sizeof(tab[0]), comp_str_maj); if ((res = bsearch(&argv[1],tab,NB_ELEMENTS,sizeof(tab[0]),comp_str_maj)) ==\ NULL) printf("\nLe tableau ne contient pas lelement %s\n",argv[1]); else

74

Chapitre 4. Les fonctions

printf("\nLe tableau contient lelement %s sous la forme %s\n",argv[1], \ *res); return(EXIT_SUCCESS); }

4.9

Fonctions avec un nombre variable de param` etres

Il est possible en C de d enir des fonctions qui ont un nombre variable de param` etres. En pratique, il existe souvent des m ethodes plus simples pour g erer ce type de probl` eme : toutefois, cette fonctionnalit e est indispensable dans certains cas, notamment pour les fonctions printf et scanf. Une fonction poss edant un nombre variable de param` etre doit poss eder au moins un param` etre formel xe. La notation . . . (obligatoirement ` a la n de la liste des param` etres dune fonction) sp ecie que la fonction poss` ede un nombre quelconque de param` etres ( eventuellement de types di erents) en plus des param` etres formels xes. Ainsi, une fonction ayant pour prototype int f(int a, char c, ...); prend comme param` etre un entier, un caract` ere et un nombre quelconque dautres param` etres. De m eme le prototype de la fonction printf est int printf(char *format, ...); puisque printf a pour argument une cha ne de caract` eres sp eciant le format des donn ees ` a imprimer, et un nombre quelconque dautres arguments qui peuvent etre de types di erents. Un appel ` a une fonction ayant un nombre variable de param` etres seectue comme un appel ` a nimporte quelle autre fonction. Pour acc eder ` a la liste des param` etres de lappel, on utilise les macros d enies dans le chier en-t ete stdarg.h de la librairie standard. Il faut tout dabord d eclarer dans le corps de la fonction une variable pointant sur la liste des param` etres de lappel ; cette variable a pour type va list. Par exemple, va_list liste_parametres; Cette variable est tout dabord initialis ee ` a laide de la macro va start, dont la syntaxe est va_start(liste_parametres, dernier_parametre); o` u dernier parametre d esigne lidenticateur du dernier param` etre formel xe de la fonction. Apr` es traitement des param` etres, on lib` ere la liste ` a laide de la va end : va_end(liste_parametres); On acc` ede aux di erents param` etres de liste par la macro va arg qui retourne le param` etre suivant de la liste: va_arg(liste_parametres, type)

A. Canteaut - Programmation en langage C

75

o` u type est le type suppos e du param` etre auquel on acc` ede. Notons que lutilisateur doit lui-m eme g erer le nombre de param` etres de la liste. Pour cela, on utilise g en eralement un param` etre formel qui correspond au nombre de param` etres de la liste, ou une valeur particuli` ere qui indique la n de la liste. Cette m ethode est utilis ee dans le programme suivant, o` u la fonction add eectue la somme de ses param` etres en nombre quelconque. #include <stdlib.h> #include <stdio.h> #include <stdarg.h> int add(int,...); int add(int nb,...) { int res = 0; int i; va_list liste_parametres; va_start(liste_parametres, nb); for (i = 0; i < nb; i++) res += va_arg(liste_parametres, int); va_end(liste_parametres); return(res); } int main(void) { printf("\n %d", add(4,10,2,8,5)); printf("\n %d\n", add(6,10,15,5,2,8,10)); return(EXIT_SUCCESS); }

76

Chapitre 4. Les fonctions

77

Chapitre 5

Les directives au pr eprocesseur


Le pr eprocesseur est un programme ex ecut e lors de la premi` ere phase de la compilation. Il eectue des modications textuelles sur le chier source ` a partir de directives. Les di erentes directives au pr eprocesseur, introduites par le caract` ere #, ont pour but : lincorporation de chiers source (#include), la d enition de constantes symboliques et de macros (#define), la compilation conditionnelle (#if, #ifdef,. . . ).

5.1

La directive #include

Elle permet dincorporer dans le chier source le texte gurant dans un autre chier. Ce dernier peut etre un chier en-t ete de la librairie standard (stdio.h, math.h,. . . ) ou nimporte quel autre chier. La directive #include poss` ede deux syntaxes voisines : #include <nom-de-fichier> recherche le chier mentionn e dans un ou plusieurs r epertoires syst` emes d enis par limpl ementation (par exemple, /usr/include/) ; #include "nom-de-fichier" recherche le chier dans le r epertoire courant (celui o` u se trouve le chier source). On peut sp ecier dautres r epertoires ` a laide de loption -I du compilateur (cf. page 10). La premi` ere syntaxe est g en eralement utilis ee pour les chiers en-t ete de la librairie standard, tandis que la seconde est plut ot destin ee aux chiers cr e es par lutilisateur.

5.2

La directive #define

La directive #define permet de d enir : des constantes symboliques, des macros avec param` etres.

78

Chapitre 5. Les directives au pr eprocesseur

5.2.1

D enition de constantes symboliques

La directive #define nom reste-de-la-ligne demande au pr eprocesseur de substituer toute occurence de nom par la cha ne de caract` eres reste-de-la-ligne dans la suite du chier source. Son utilit e principale est de donner un nom parlant ` a une constante, qui pourra etre ais ement modi ee. Par exemple : #define NB_LIGNES 10 #define NB_COLONNES 33 #define TAILLE_MATRICE NB_LIGNES * NB_COLONNES Il ny a toutefois aucune contrainte sur la cha ne de caract` eres reste-de-la-ligne. On peut ecrire #define BEGIN { #define END }

5.2.2

D enition de macros

Une macro avec param` etres se d enit de la mani` ere suivante : #define nom(liste-de-param` etres) corps-de-la-macro o` u liste-de-param` etres est une liste didenticateurs s epar es par des virgules. 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` u x et y sont des symboles quelconques par (x > y ? x : y) Une macro a donc une syntaxe similaire ` a celle dune fonction, mais son emploi permet en g en eral dobtenir de meilleures performances en temps dex ecution. La distinction entre une d enition de constante symbolique et celle dune macro avec param` etres se fait sur le caract` ere qui suit imm ediatement le nom de la macro : si ce caract` ere est une parenth` ese ouvrante, cest une macro avec param` etres, sinon cest une constante symbolique. Il ne faut donc jamais mettre despace entre le nom de la macro et la parenth` ese ouvrante. Ainsi, si lon ecrit par erreur #define CARRE (a) a * a la cha ne de caract` eres CARRE(2) sera remplac ee par (a) a * a (2)

A. Canteaut - Programmation en langage C

79

Il faut toujours garder ` a lesprit que le pr eprocesseur neectue que des remplacements de cha nes de caract` eres. En particulier, il est conseill e de toujours mettre entre parenth` eses le corps de la macro et les param` etres formels qui y sont utilis es. Par exemple, si lon ecrit sans parenth` eses : #define CARRE(a) a * a le pr eprocesseur remplacera CARRE(a + b) par a + b * a + b et non par (a + b) * (a + b). De m eme, !CARRE(x) sera remplac e par ! x * x et non par !(x * x). Enn, il faut etre attentif aux eventuels eets de bord que peut entra ner lusage de macros. Par exemple, CARRE(x++) aura pour expansion (x++) * (x++). Lop erateur dincr ementation sera donc appliqu e deux fois au lieu dune.

5.3

La compilation conditionnelle

La compilation conditionnelle a pour but dincorporer ou dexclure des parties du code source dans le texte qui sera g en er e par le pr eprocesseur. Elle permet dadapter le programme au mat eriel ou ` a lenvironnement sur lequel il sex ecute, ou dintroduire dans le programme des instructions de d ebogage. Les directives de compilation conditionnelle se r epartissent en deux cat egories, suivant le type de condition invoqu ee : la valeur dune expression lexistence ou linexistence de symboles.

5.3.1

Condition li ee ` a la valeur dune expression

Sa syntaxe la plus g en erale est : #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- #endif Le nombre de #elif est quelconque et le #else est facultatif. Chaque condition-i doit etre une expression constante. Une seule partie-du-programme sera compil ee : celle qui correspond ` a la premi` ere condition-i non nulle, ou bien la partie-du-programme- si toutes les conditions sont nulles. Par exemple, on peut ecrire #define PROCESSEUR ALPHA

80 #if PROCESSEUR == ALPHA taille_long = 64; #elif PROCESSEUR == PC taille_long = 32; #endif

Chapitre 5. Les directives au pr eprocesseur

5.3.2

Condition li ee ` a lexistence dun symbole

Sa syntaxe est #ifdef symbole partie-du-programme-1 #else condition-2 partie-du-programme-2 #endif Si symbole est d eni au moment o` u lon rencontre la directive #ifdef, alors partie-duprogramme-1 sera compil ee et partie-du-programme-2 sera ignor ee. Dans le cas contraire, cest partie-du-programme-2 qui sera compil ee. La directive #else est evidemment facultative. Da fa con similaire, on peut tester la non-existence dun symbole par : #ifndef symbole partie-du-programme-1 #else condition-2 partie-du-programme-2 #endif Ce type de directive est utile pour rajouter des instructions destin ees au d ebogage du programme : #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 li ees au d ebogage ne soient pas compil ees. Cette derni` ere directive peut etre remplac ee par loption de compilation -Dsymbole, qui permet de d enir un symbole. On peut remplacer #define DEBUG en compilant le programme par gcc -DDEBUG fichier.c

81

Chapitre 6

La gestion des chiers


Le C ore la possibilit e de lire et d ecrire des donn ees dans un chier. Pour des raisons decacit e, les acc` es ` a un chier se font par linterm ediaire dune m emoiretampon (buer), ce qui permet de r eduire le nombre dacc` es aux p eriph eriques (disque...). Pour pouvoir manipuler un chier, un programme a besoin dun certain nombre dinformations : ladresse de lendroit de la m emoire-tampon o` u se trouve le chier, la position de la t ete de lecture, le mode dacc` es au chier (lecture ou ecriture) . . . Ces informations sont rassembl ees dans une structure dont le type, FILE *, est d eni dans stdio.h. Un objet de type FILE * est appel e ot de donn ees (en anglais, stream). Avant de lire ou d ecrire dans un chier, on notie son acc` es par la commande fopen. Cette fonction prend comme argument le nom du chier, n egocie avec le syst` eme dexploitation et initialise un ot de donn ees, qui sera ensuite utilis e lors de l ecriture ou de la lecture. Apr` es les traitements, on annule la liaison entre le chier et le ot de donn ees gr ace ` a la fonction fclose.

6.1
6.1.1

Ouverture et fermeture dun chier


La fonction fopen

Cette fonction, de type FILE* ouvre un chier et lui associe un ot de donn ees. Sa syntaxe est : fopen("nom-de-fichier","mode") La valeur retourn ee par fopen est un ot de donn ees. Si lex ecution de cette fonction ne se d eroule pas normalement, la valeur retourn ee est le pointeur NULL. Il est donc recommand e de toujours tester si la valeur renvoy ee par la fonction fopen est egale ` a NULL an de d etecter les erreurs (lecture dun chier inexistant...). Le premier argument de fopen est le nom du chier concern e, fourni sous forme dune cha ne de caract` eres. On pr ef erera d enir le nom du chier par une constante symbolique au moyen de la directive #define plut ot que dexpliciter le nom de chier dans le corps du programme. Le second argument, mode, est une cha ne de caract` eres qui sp ecie le mode dacc` es au chier. Les sp ecicateurs de mode dacc` es di` erent suivant le type de chier consid er e. On

82 distingue

Chapitre 6. La gestion des chiers

les chiers textes, pour lesquels les caract` eres de contr ole (retour ` a la ligne . . . ) seront interpr et es en tant que tels lors de la lecture et de l ecriture ; les chiers binaires, pour lesquels les caract` eres de contr ole se sont pas interpr et es. Les di erents modes dacc` es sont les suivants : "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 ecriture texte en ecriture ` a la n binaire en lecture binaire en ecriture binaire en ecriture ` a la n texte en lecture/ ecriture texte en lecture/ ecriture texte en lecture/ ecriture ` a la n binaire en lecture/ ecriture binaire en lecture/ ecriture binaire en lecture/ ecriture ` a la n

Ces modes dacc` es ont pour particularit es : Si le mode contient la lettre r, le chier doit exister. Si le mode contient la lettre w, le chier peut ne pas exister. Dans ce cas, il sera cr e e. Si le chier existe d ej` a, son ancien contenu sera perdu. Si le mode contient la lettre a, le chier peut ne pas exister. Dans ce cas, il sera cr e e. Si le chier existe d ej` a, les nouvelles donn ees seront ajout ees ` a la n du chier pr ec edent. Trois ots standard peuvent etre utilis es en C sans quil soit n ecessaire de les ouvrir ou de les fermer : stdin (standard input) : unit e dentr ee (par d efaut, le clavier) ; stdout (standard output) : unit e de sortie (par d efaut, l ecran) ; stderr (standard error) : unit e dachage des messages derreur (par d efaut, l ecran). Il est fortement conseill e dacher syst ematiquement les messages derreur sur stderr an que ces messages apparaissent ` a l ecran m eme lorsque la sortie standard est redirig ee.

6.1.2

La fonction fclose

Elle permet de fermer le ot qui a et e associ e` a un chier par la fonction fopen. Sa syntaxe est : fclose(flot) o` u flot est le ot de type FILE* retourn e par la fonction fopen correspondant. La fonction fclose retourne un entier qui vaut z ero si lop eration sest d eroul ee normalement (et une valeur non nulle en cas derreur).

A. Canteaut - Programmation en langage C

83

6.2
6.2.1

Les entr ees-sorties format ees


La fonction d ecriture fprintf

La fonction fprintf, analogue ` a printf, permet d ecrire des donn ees dans un chier. Sa syntaxe est fprintf(flot,"cha^ ne de contr^ ole",expression-1, ..., expression-n) o` u flot est le ot de donn ees retourn e par la fonction fopen. Les sp ecications de format utilis ees pour la fonction fprintf sont les m emes que pour printf (cf. page 30).

6.2.2

La fonction de saisie fscanf

La fonction fscanf, analogue ` a scanf, permet de lire des donn ees dans un chier. Sa syntaxe est semblable ` a celle de scanf : fscanf(flot,"cha^ ne de contr^ ole",argument-1,...,argument-n) o` u flot est le ot de donn ees retourn e par fopen. Les sp ecications de format sont ici les m emes que celles de la fonction scanf (cf. page 32).

6.3

Impression et lecture de caract` eres

Similaires aux fonctions getchar et putchar, les fonctions fgetc et fputc permettent respectivement de lire et d ecrire un caract` ere dans un chier. La fonction fgetc, de type int, retourne le caract` ere lu dans le chier. Elle retourne la constante EOF lorsquelle d etecte la n du chier. Son prototype est int fgetc(FILE* flot); o` u flot est le ot de type FILE* retourn e par la fonction fopen. Comme pour la fonction getchar, il est conseill e de d eclarer de type int la variable destin ee ` a recevoir la valeur de retour de fgetc pour pouvoir d etecter correctement la n de chier (cf. page 33). La fonction fputc ecrit caractere dans le ot de donn ees : int fputc(int caractere, FILE *flot) Elle retourne lentier correspondant au caract` ere lu (ou la constante EOF en cas derreur). Il existe egalement deux versions optimis ees des fonctions fgetc et fputc qui sont impl ement ees par des macros. Il sagit respectivement de getc et putc. Leur syntaxe est similaire a celle de fgetc et fputc : ` int getc(FILE* flot); int putc(int caractere, FILE *flot) Ainsi, le programme suivant lit le contenu du chier texte entree, et le recopie caract` ere par caract` ere dans le chier sortie : #include <stdio.h> #include <stdlib.h>

84 #define ENTREE "entree.txt" #define SORTIE "sortie.txt" int main(void) { FILE *f_in, *f_out; int c;

Chapitre 6. La gestion des chiers

if ((f_in = fopen(ENTREE,"r")) == NULL) { fprintf(stderr, "\nErreur: Impossible de lire le fichier %s\n",ENTREE); return(EXIT_FAILURE); } if ((f_out = fopen(SORTIE,"w")) == NULL) { fprintf(stderr, "\nErreur: Impossible decrire dans le fichier %s\n", \ SORTIE); return(EXIT_FAILURE); } while ((c = fgetc(f_in)) != EOF) fputc(c, f_out); fclose(f_in); fclose(f_out); return(EXIT_SUCCESS); }

6.4

Relecture dun caract` ere


int ungetc(int caractere, FILE *flot);

Il est possible de replacer un caract` ere dans un ot au moyen de la fonction ungetc :

Cette fonction place le caract` ere caractere (converti en unsigned char) dans le ot flot. En particulier, si caractere est egal au dernier caract` ere lu dans le ot, elle annule le d eplacement provoqu e par la lecture pr ec edente. Toutefois, ungetc peut etre utilis ee avec nimporte quel caract` ere (sauf EOF). Par exemple, lex ecution du programme suivant #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) {

A. Canteaut - Programmation en langage C

85

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 ache ` a l ecran 0.970.23

6.5

Les entr ees-sorties binaires

Les fonctions dentr ees-sorties binaires permettent de transf erer des donn ees dans un chier sans transcodage. Elles sont donc plus ecaces que les fonctions dentr ee-sortie standard, mais les chiers produits ne sont pas portables puisque le codage des donn ees d epend des machines. Elles sont notamment utiles pour manipuler des donn ees de grande taille ou ayant un type compos e. Leurs prototypes sont : size t fread(void *pointeur, size t taille, size t nombre, FILE *flot); size t fwrite(void *pointeur, size t taille, size t nombre, FILE *flot); o` u pointeur est ladresse du d ebut des donn ees ` a transf erer, taille la taille des objets ` a eni dans stddef.h, correstransf erer, nombre leur nombre. Rappelons que le type size t, d pond au type du r esultat de l evaluation de sizeof. Il sagit du plus grand type entier non sign e. La fonction fread lit les donn ees sur le ot flot et la fonction fwrite les ecrit. Elles retournent toutes deux le nombre de donn ees transf er ees. Par exemple, le programme suivant ecrit un tableau dentiers (contenant les 50 premiers entiers) avec fwrite dans le chier sortie, puis lit ce chier avec fread et imprime les el ements du tableau. #include <stdio.h> #include <stdlib.h> #define NB 50 #define F_SORTIE "sortie" int main(void) { FILE *f_in, *f_out; int *tab1, *tab2; int i;

86

Chapitre 6. La gestion des chiers

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 le fichier %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 le fichier %s\n",F_SORTIE); return(EXIT_FAILURE); } fread(tab2, NB * sizeof(int), 1, f_in); fclose(f_in); for (i = 0 ; i < NB; i++) printf("%d\t",tab2[i]); printf("\n"); return(EXIT_SUCCESS); } Les el ements du tableau sont bien ach es ` a l ecran. Par contre, on constate que le contenu du chier sortie nest pas encod e.

6.6

Positionnement dans un chier

Les di erentes fonctions dentr ees-sorties permettent dacc eder ` a un chier en mode s equentiel : les donn ees du chier sont lues ou ecrites les unes ` a la suite des autres. Il est egalement possible dacc eder ` a un chier en mode direct, cest-` a-dire que lon peut se positionner ` a nimporte quel endroit du chier. La fonction fseek permet de se positionner ` a un endroit pr ecis ; elle a pour prototype : int fseek(FILE *flot, long deplacement, int origine); La variable deplacement d etermine la nouvelle position dans le chier. Il sagit dun d eplacement relatif par rapport ` a lorigine ; il est compt e en nombre doctets. La variable origine peut prendre trois valeurs : egale ` a 0) : d ebut du chier ; SEEK SET ( SEEK CUR ( egale ` a 1) : position courante ;

A. Canteaut - Programmation en langage C SEEK END ( egale ` a 2) : n du chier. La fonction int rewind(FILE *flot); permet de se positionner au d ebut du chier. Elle est equivalente ` a fseek(flot, 0, SEEK SET); La fonction long ftell(FILE *flot); retourne la position courante dans le chier (en nombre doctets depuis lorigine). Par exemple #include <stdio.h> #include <stdlib.h> #define NB 50 #define F_SORTIE "sortie" int main(void) { FILE *f_in, *f_out; int *tab; int i; tab = (int*)malloc(NB * sizeof(int)); for (i = 0 ; i < NB; i++) tab[i] = i;

87

/* ecriture du tableau dans F_SORTIE */ if ((f_out = fopen(F_SORTIE, "w")) == NULL) { fprintf(stderr, "\nImpossible decrire dans le fichier %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 le fichier %s\n",F_SORTIE); return(EXIT_FAILURE); } /* on se positionne a la fin du fichier */ fseek(f_in, 0, SEEK_END);

88 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); } Lex ecution de ce programme ache ` a l ecran : position position position position 200 160 0 24 i = 40 i = 0 i = 6

Chapitre 6. La gestion des chiers

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

89

Chapitre 7

La programmation modulaire
D` es que lon ecrit un programme de taille importante ou destin e` a etre utilis e et maintenu par dautres personnes, il est indispensable de se xer un certain nombre de r` egles d ecriture. En particulier, il est n ecessaire de fractionner le programme en plusieurs chiers sources, que lon compile s eparemment. Ces r` egles d ecriture ont pour objectifs de rendre un programme lisible, portable, r eutilisable, facile ` a maintenir et ` a modier.

7.1

Principes el ementaires

Trois principes essentiels doivent guider l ecriture dun programme C. Labstraction des constantes litt erales Lutilisation explicite de constantes litt erales dans le corps dune fonction rend les modications et la maintenance diciles. Des instructions comme : fopen("mon_fichier", "r"); perimetre = 2 * 3.14 * rayon; sont ` a proscrire. Sauf cas tr` es particuliers, les constantes doivent etre d enies comme des constantes symboliques au moyen de la directive #define. La factorisation du code Son but est d eviter les duplications de code. La pr esence dune m eme portion de code ` a plusieurs endroits du programme est un obstacle ` a d eventuelles modications. Les fonctions doivent donc etre syst ematiquement utilis ees pour eviter la duplication de code. Il ne faut pas craindre de d enir une multitude de fonctions de petite taille. La fragmentation du code Pour des raisons de lisibilit e, il est n ecessaire de d ecouper un programme en plusieurs chiers. De plus, cette r` egle permet de r eutiliser facilement une partie du code pour dautres applications. Une possibilit e est de placer une partie du code dans un chier en-t ete (ayant lextension .h) que lon inclut dans le chier contenant le programme principal ` a laide de la directive #include. Par exemple, pour ecrire un programme qui saisit deux entiers au clavier et ache leur produit, on peut placer la fonction produit

90

Chapitre 7. La programmation modulaire

dans un chier produit.h, et linclure dans le chier main.c au moment du traitement par le pr eprocesseur. /**********************************************************************/ /*** fichier: main.c ***/ /*** saisit 2 entiers et affiche leur produit ***/ /**********************************************************************/ #include <stdlib.h> #include <stdio.h> #include "produit.h" int main(void) { int a, b, c; scanf("%d",&a); scanf("%d",&b); c = produit(a,b); printf("\nle produit vaut %d\n",c); return EXIT_SUCCESS; } /**********************************************************************/ /*** fichier: produit.h ***/ /*** produit de 2 entiers ***/ /**********************************************************************/ int produit(int, int); int produit(int a, int b) { return(a * b); } Cette technique permet juste de rendre le code plus lisible, puisque le chier eectivement compil e (celui produit par le pr eprocesseur) est unique et contient la totalit e du code. Une m ethode beaucoup plus pratique consiste ` a d ecouper le code en plusieurs chiers sources que lon compile s eparemment. Cette technique, appel ee compilation s epar ee, facilite egalement le d ebogage.

7.2

La compilation s epar ee

Si lon reprend lexemple pr ec edent, le programme sera divis e en deux chiers : main.c et produit.c. Cette fois-ci, le chier produit.c nest plus inclus dans le chier principal. Les deux chiers seront compil es s epar ement ; les deux chiers objets produits par la compilation seront li es lors l edition de liens. Le d etail de la compilation est donc : gcc -c produit.c

A. Canteaut - Programmation en langage C gcc -c main.c gcc main.o produit.o La succession de ces trois commandes peut egalement s ecrire gcc produit.c main.c

91

Toutefois, nous avons vu au chapitre 4, page 61, quil etait risqu e dutiliser une fonction sans lavoir d eclar ee. Cest ici le cas, puisque quand il compile le programme main.c, le compilateur ne dispose pas de la d eclaration de la fonction produit. Loption -Wall de gcc signale main.c:15: warning: implicit declaration of function produit Il faut donc rajouter cette d eclaration dans le corps du programme main.c.

7.2.1

Fichier en-t ete dun chier source

Pour que le programme reste modulaire, on place en fait la d eclaration de la fonction produit dans un chier en-t ete produit.h que lon inclut dans main.c ` a laide de #include. Une r` egle d ecriture est donc dassocier ` a chaque chier source nom.c un chier en-t ete nom.h comportant les d eclarations des fonctions non locales au chier nom.c, (ces fonctions sont appel ees fonctions dinterface) ainsi que les d enitions des constantes symboliques et des macros qui sont partag ees par les deux chiers. Le chier en-t ete nom.h doit etre inclus par la directive #include dans tous les chiers sources qui utilisent une des fonctions d enies dans nom.c, ainsi que dans le chier nom.c. Cette derni` ere inclusion permet au compilateur de v erier que la d enition de la fonction donn ee dans nom.c est compatible avec sa d eclaration plac ee dans nom.h. Cest exactement la proc edure que lon utilise pour les fonctions de la librairie standard : les chiers .h de la librairie standard sont constitu es de d eclarations de fonctions et de d enitions de constantes symboliques. Par ailleurs, il faut faire pr ec eder la d eclaration de la fonction du mot-clef extern, qui signie que cette fonction est d enie dans un autre chier. Le programme eectuant le produit se d ecompose donc en trois chiers de la mani` ere suivante. /**********************************************************************/ /*** fichier: produit.h ***/ /*** en-tete de produit.c ***/ /**********************************************************************/ extern int produit(int, int); /**********************************************************************/ /*** fichier: produit.c ***/ /*** produit de 2 entiers ***/ /**********************************************************************/ #include "produit.h" int produit(int a, int b) { return(a * b); }

92

Chapitre 7. La programmation modulaire

/**********************************************************************/ /*** fichier: main.c ***/ /*** saisit 2 entiers et affiche leur produit ***/ /**********************************************************************/ #include <stdlib.h> #include <stdio.h> #include "produit.h" int main(void) { int a, b, c; scanf("%d",&a); scanf("%d",&b); c = produit(a,b); printf("\nle produit vaut %d\n",c); return EXIT_SUCCESS; } Une derni` ere r` egle consiste ` a eviter les possibilit es de double inclusion de chiers en-t ete. Pour cela, il est recommand e de d enir une constante symbolique, habituellement appel ee ebut du chier nom.h dont lexistence est pr ec edemment test ee. Si cette constante NOM H, au d est d enie, cest que le chier nom.h a d ej` a et e inclus. Dans ce cas, le pr eprocesseur ne le prend pas en compte. Sinon, on d enit la constante et on prend en compte le contenu de nom.h. En appliquant cette r` egle, le chier produit.h de lexemple pr ec edent devient : /**********************************************************************/ /*** fichier: produit.h ***/ /*** en-tete de produit.c ***/ /**********************************************************************/ #ifndef PRODUIT_H #define PRODUIT_H extern int produit(int, int); #endif /* PRODUIT_H */ En r esum e, les r` egles d ecriture sont les suivantes : A tout chier source nom.c dun programme on associe un chier en-t ete nom.h qui d enit son interface. Le chier nom.h se compose : des d eclarations des fonctions dinterface (celles qui sont utilis ees dans dautres chiers sources) ; d eventuelles d enitions de constantes symboliques et de macros ; d eventuelles directives au pr eprocesseur (inclusion dautres chiers, compilation conditionnelle).

A. Canteaut - Programmation en langage C Le chier nom.c se compose : de variables permanentes, qui ne sont utilis ees que dans le chier nom.c ; des fonctions dinterface dont la d eclaration se trouve dans nom.h ; d eventuelles fonctions locales ` a nom.c.

93

Le chier nom.h est inclus dans le chier nom.c et dans tous les autres chiers qui font appel ` a une fonction dinterface d enie dans nom.c. Enn, pour plus de lisibilit e, il est recommand e de choisir pour toutes les fonctions dinterface d enies dans nom.c un identicateur pr ex e par le nom du chier source, du type nom fonction.

7.2.2

Variables partag ees

M eme si cela doit etre evit e, il est parfois n ecessaire dutiliser une variable commune ` a plusieurs chiers sources. Dans ce cas, il est indispensable que le compilateur comprenne que deux variables portant le m eme nom mais d eclar ees dans deux chiers di erents correspondent en fait ` a un seul objet. Pour cela, la variable doit etre d eclar ee une seule fois de mani` ere classique. Cette d eclaration correspond ` a une d enition dans la mesure o` u le compilateur r eserve un espace-m emoire pour cette variable. Dans les autres chiers qui lutilisent, il faut faire une r ef erence ` a cette variable, sous forme dune d eclaration pr ec ed ee du mot-clef extern. Contrairement aux d eclarations classiques, une d eclaration pr ec ed ee de extern ne donne pas lieu ` a une r eservation despace m emoire. Ainsi, pour que les deux chiers sources main.c et produit.c partagent une variable enti` ere x, on peut d enir x dans produit.c sous la forme int x; et y faire r ef erence dans main.c par extern int x;

7.3

Lutilitaire make

Losrquun programme est fragment e en plusieurs chiers sources compil es s eparemment, la proc edure de compilation peut devenir longue et fastidieuse. Il est alors extr` emement pratique de lautomatiser ` a laide de lutilitaire make dUnix. Une bonne utilisation de make permet de r eduire le temps de compilation et egalement de garantir que celle-ci est eectu ee correctement.

7.3.1

Principe de base

Lid ee principale de make est deectuer uniquement les etapes de compilation n ecessaires ` la cr a eation dun ex ecutable. Par exemple, si un seul chier source a et e modi e dans un programme compos e de plusieurs chiers, il sut de recompiler ce chier et deectuer l edition de liens. Les autres chiers sources nont pas besoin d etre recompil es. La commande make recherche par d efaut dans le r epertoire courant un chier de nom makefile, ou Makefile si elle ne le trouve pas. Ce chier sp ecie les d ependances entre les

94

Chapitre 7. La programmation modulaire

di erents chiers sources, objets et ex ecutables. Il est egalement possible de donner un autre nom au chier Makefile. Dans ce cas, il faut lancer la commande make avec loption -f nom de fichier.

7.3.2

Cr eation dun Makefile

Un chier Makefile est compos e dune liste de r` egles de d ependance de la forme : cible: liste de d ependances <TAB> commandes UNIX La premi` ere ligne sp ecie un chier cible, puis la liste des chiers dont il d epend (s epar es par des espaces). Les lignes suivantes, qui commencent par le caract` ere TAB, indiquent les commandes Unix ` a ex ecuter dans le cas o` u lun des chiers de d ependance est plus r ecent que le chier cible. Ainsi, un chier Makefile pour le programme eectuant le produit de deux entiers peut etre ## Premier exemple de Makefile prod: produit.c main.c produit.h gcc -o prod -O3 produit.c main.c prod.db: produit.c main.c produit.h gcc -o prod.db -g -O3 produit.c main.c Lex ecutable prod d epend des deux chiers sources produit.c et main.c, ainsi que du chier en-t ete produit.h. Il r esulte de la compilation de ces deux chiers avec loption doptimisation -O3. Lex ecutable prod.db utilis e par le d ebogueur est, lui, obtenu en compilant ces deux chiers avec loption -g n ecessaire au d ebogage. Les commentaires sont pr ec ed es du caract` ere #. Pour eectuer la compilation et obtenir un chier cible, on lance la commande make suivie du nom du chier cible souhait e, ici make prod ou make prod.db Par d efaut, si aucun chier cible nest sp eci e au lancement de make, cest la premi` ere cible du chier Makefile qui est prise en compte. Par exemple, si on lance pour la premi` ere fois make, la commande de compilation est eectu ee puisque le chier ex ecutable prod nexiste pas : % make gcc -o prod -O3 produit.c main.c Si on lance cette commande une seconde fois sans avoir modi e les chiers sources, la compilation nest pas eectu ee puisque le chier prod est plus r ecent que les deux chiers dont il d epend. On obtient dans ce cas : % make make: prod is up to date.

A. Canteaut - Programmation en langage C

95

Le Makefile pr ec edent nutilise pas pleinement les fonctionnalit es de make. En eet, la commande utilis ee pour la compilation correspond en fait ` a trois op erations distinctes : la compilation des chiers sources produit.c et main.c, qui produit respectivement les chiers objets produit.o et main.o, puis l edition de liens entre ces deux chiers objet, qui produit lex ecutable prod. Pour utiliser pleinement make, il faut distinguer ces trois etapes. Le nouveau chier Makefile devient alors : ## Deuxieme exemple de Makefile prod: produit.o main.o gcc -o prod produit.o main.o main.o: main.c produit.h gcc -c -O3 main.c produit.o: produit.c produit.h gcc -c -O3 produit.c Les chiers objet main.o et produit.o d ependent respectivement des chiers sources main.c et produit.c, et du chier en-t ete produit.h. Ils sont obtenus en eectuant la compilation de ces chiers sources sans edition de liens (option -c de gcc), et avec loption doptimisation -O3. Le chier ex ecutable prod est obtenu en eectuant l edition de liens des chiers produit.o et main.o. Lorsquon invoque la commande make pour la premi` ere fois, les trois etapes de compilation sont eectu ees : % make gcc -c -O3 produit.c gcc -c -O3 main.c gcc -o prod produit.o main.o Si lon modie le chier produit.c, le chier main.o est encore ` a jour. Seules deux des trois etapes de compilation sont ex ecut ees : % make gcc -c -O3 produit.c gcc -o prod produit.o main.o De la m eme fa con, il convient de d etailler les etapes de compilation pour obtenir le chier ex ecutable prod.db utilis e pour le d ebogage. Le chier Makefile devient alors : ## Deuxieme exemple de Makefile # Fichier executable prod prod: produit.o main.o gcc -o prod produit.o main.o main.o: main.c produit.h gcc -c -O3 main.c produit.o: produit.c produit.h gcc -c -O3 produit.c # Fichier executable pour le debuggage prod.db

96

Chapitre 7. La programmation modulaire

prod.db: produit.do main.do gcc -o prod.db produit.do main.do main.do: main.c produit.h gcc -o main.do -c -g -O3 main.c produit.do: produit.c produit.h gcc -o produit.do -c -g -O3 produit.c Pour d eterminer facilement les d ependances entre les di erents chiers, on peut utiliser loption -MM de gcc. Par exemple, % gcc -MM produit.c main.c produit.o: produit.c produit.h main.o: main.c produit.h On rajoute habituellement dans un chier Makefile une cible appel ee clean permettant de d etruire tous les chiers objets et ex ecutables cr e es lors de la compilation. clean: rm -f prod prod.db *.o *.do La commande make clean permet donc de nettoyer le r epertoire courant. Notons que lon utilise ici la commande rm avec loption -f qui evite lapparition dun message derreur si le chier ` a d etruire nexiste pas.

7.3.3

Macros et abbr eviations

Pour simplier l ecriture dun chier Makefile, on peut utiliser un certain nombre de macros sous la forme nom de macro = corps de la macro Quand la commande make est ex ecut ee, toutes les instances du type $(nom de macro) dans le Makefile sont remplac ees par le corps de la macro. Par exemple, on peut d enir une macro CC pour sp ecier le compilateur utilis e (cc ou gcc), une macro PRODUCTFLAGS pour d enir les options de compilation utilis ees pour g en erer un chier produit, une macro DEBUGFLAGS pour les options de compilation utilis ees pour g en erer un chier produit pour le d ebogage... Le chier Makefile suivant donne un exemple : ## Exemple de Makefile avec macros # definition CC = gcc # definition PRODUCTFLAGS # definition DEBUGFLAGS = du compilateur des options de compilation pour obtenir un fichier .o = -c -O3 des options de compilation pour obtenir un fichier .do -c -g -O3

# Fichier executable prod prod: produit.o main.o $(CC) -o prod produit.o main.o

A. Canteaut - Programmation en langage C main.o: main.c produit.h $(CC) $(PRODUCTFLAGS) main.c produit.o: produit.c produit.h $(CC) $(PRODUCTFLAGS) produit.c # Fichier executable pour le debuggage prod.db prod.db: produit.do main.do $(CC) -o prod.db produit.do main.do main.do: main.c produit.h $(CC) -o main.do $(DEBUGFLAGS) main.c produit.do: produit.c produit.h $(CC) -o produit.do $(DEBUGFLAGS) produit.c La commande make produit alors % make gcc -c -O3 produit.c gcc -c -O3 main.c gcc -o prod produit.o main.o

97

Cette ecriture permet de faciliter les modications du chier Makefile : on peut maintenant ais ement changer les options de compilation, le type de compilateur... Un certain nombre de macros sont pr ed enies. En particulier, $@ d esigne le chier cible courant : $* d esigne le chier cible courant priv e de son suxe : $< d esigne le chier qui a provoqu e laction. Dans le Makefile pr ec edent, la partie concernant la production de main.do peut s ecrire par exemple main.do: main.c produit.h $(CC) -o $@ $(DEBUGFLAGS) $<

7.3.4

R` egles g en erales de compilation

Il est egalement possible de d enir dans un Makefile des r` egles g en erales de compilation correspondant ` a certains suxes. On peut sp ecier par exemple que tout chier .o est obtenu en compilant le chier .c correspondant avec les options d enies par la macro PRODUCTFLAGS. Pour cela, il faut tout dabord d enir une liste de suxes qui sp ecient les chiers cibles construits ` a partir dune r` egle g en erale. Par exemple, avant de d enir des r` egles de compilation pour obtenir les chiers .o et .do, on ecrit : .SUFFIXES: .o .do Une r` egle de compilation est ensuite d enie de la fa con suivante : on donne le suxe du chier que make doit chercher, suivi par le suxe du chier que make doit produire. Ces deux suxes

98

Chapitre 7. La programmation modulaire

sont suivis par :; puis par une commande Unix (d enie de la fa con la plus g en erale possible). Les r` egles de production des chiers .o et .do sont par exemple : # regle de production dun fichier .c.o:; $(CC) -o $@ $(PRODUCTFLAGS) # regle de production dun fichier .c.do:; $(CC) -o $@ $(DEBUGFLAGS) .o $< .do $<

Si les chiers .o ou .do d ependent egalement dautres chiers, il faut aussi sp ecier ces d ependances. Ici, il faut pr eciser par exemple que ces chiers d ependent aussi de produit.h. Le chier Makefile a donc la forme suivante : ## Exemple de Makefile # definition CC = gcc # definition PRODUCTFLAGS # definition DEBUGFLAGS = du compilateur des options de compilation pour obtenir un fichier .o = -c -O3 des options de compilation pour obtenir un fichier .do -c -g -O3

# suffixes correspondant a des regles generales .SUFFIXES: .c .o .do # regle de production dun fichier .o .c.o:; $(CC) -o $@ $(PRODUCTFLAGS) $< # regle de production dun fichier .do .c.do:; $(CC) -o $@ $(DEBUGFLAGS) $< # Fichier executable prod prod: produit.o main.o $(CC) -o prod produit.o main.o produit.o: produit.c produit.h main.o: main.c produit.h # Fichier executable pour le debuggage prod.db prod.db: produit.do main.do $(CC) -o prod.db produit.do main.do produit.do: produit.c produit.h main.do: main.c produit.h clean: rm -f prod prod.db *.o *.do

99

Annexe A

La librairie standard
Cette annexe donne la syntaxe des principales fonctions de la librairie standard. Une liste exhaustive de toutes les fonctions disponibles gure ` a lannexe B de louvrage de Kernighan et Richie [6]. Pour obtenir plus dinformations sur ces fonctions, il sut de consulter les pages de man correspondant.

A.1
A.1.1

Entr ees-sorties <stdio.h>


Manipulation de chiers

Lusage des fonctions de manipulation de chiers suivantes est d etaill e au chapitre 6, page 81. fonction fopen fclose fflush action ouverture dun chier fermeture dun chier ecriture des buers en m emoire dans le chier

A.1.2

Entr ees et sorties format ees

La syntaxe de ces fonctions et leur action sont d ecrites aux paragraphes 1.11 et 6.2-6.3. fonction fprintf fscanf printf scanf sprintf sscanf 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 ecriture sur un chier lecture depuis un chier ecriture sur la sortie standard lecture depuis lentr ee standard ecriture dans la cha ne de caract` eres s lecture depuis la cha ne de caract` eres s

100

Annexe A. La librairie standard

A.1.3

Impression et lecture de caract` eres


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` ere depuis un chier ecriture dun caract` ere sur un chier equivalent de fgetc mais impl ement e par une macro equivalent de fputc mais impl ement e par une macro lecture dun caract` ere depuis lentr ee standard ecriture dun caract` ere sur la sortie standard lecture dune cha ne de caract` eres depuis un chier ecriture dune cha ne de caract` eres sur un chier lecture dune cha ne de caract` eres sur lentr ee standard ecriture dune cha ne de caract` eres sur la sortie standard

fonction fgetc fputc getc putc getchar putchar fgets fputs gets puts

A. Canteaut - Programmation en langage C

101

A.2

Manipulation de caract` eres <ctype.h>

Toutes les fonctions ci-dessous permettent de tester une propri et e du caract` ere pass e en param` etre. Elles renvoient la valeur 1 si le caract` ere v erie la propri et e et 0 sinon. Leur prototype est : int fonction(char c) fonction isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit renvoie 1 si le caract` ere est une lettre ou un chire une lettre un caract` ere de commande un chire d ecimal un caract` ere imprimable ou le blanc une lettre minuscule un caract` ere imprimable (pas le blanc) un caract` ere imprimable qui nest ni une lettre ni un chire un blanc une lettre majuscule un chire hexad ecimal

On dispose egalement de deux fonctions permettant la conversion entre lettres minuscules et lettres majuscules : fonction tolower toupper prototype int tolower(int c) int toupper(int c) action convertit c en minuscule si cest une lettre majuscule, retourne c sinon. convertit c en majuscule si cest une lettre minuscule, retourne c sinon.

102

Annexe A. La librairie standard

A.3

Manipulation de cha nes de caract` eres <string.h>


prototype char *strcpy(char *ch1, char *ch2) char *strncpy(char *ch1, char *ch2, int n) action copie la cha ne ch2 dans la cha ne ch1 ; retourne ch1. copie n caract` eres de la cha ne ch2 dans la cha ne ch1 ; retourne ch1. copie la cha ne ch2 ` a la n de la cha ne ch1 ; retourne ch1. copie n caract` eres de la cha ne ch2 ` a la n de la cha ne ch1 ; retourne ch1. compare ch1 et ch2 pour lordre lexicographique ; retourne une valeur n egative si ch1 est inf erieure ` a ch2, une valeur positive si ch1 est sup erieure ` a ch2, 0 si elles sont identiques. compare les n premiers caract` eres de ch1 et ch2. retourne un pointeur sur la premi` ere occurence de c dans chaine, et NULL si c ny gure pas. retourne un pointeur sur la derni` ere occurence de c dans chaine, et NULL si c ny gure pas. retourne un pointeur sur la premi` ere occurence de ch2 dans ch1, et NULL si ch2 ny gure pas. retourne la longueur de chaine.

fonction strcpy strncpy

strcat

char *strcat(char *ch1, char *ch2)

strncat

char *strncat(char *ch1, char *ch2, int n)

strcmp

int strcmp(char *ch1, char *ch2)

strncmp strchr

int strncmp(char *ch1, char *ch2, int n) char *strchr(char *chaine, char c)

strrchr

char *strrchr(char *chaine, char c)

strstr

char *strshr(char *ch1, char *ch2)

strlen

int strlen(char *chaine)

A. Canteaut - Programmation en langage C

103

A.4

Fonctions math ematiques <math.h>

Le r esultat et les param` etres de toutes ces fonctions sont de type double. Si les param` etres eectifs sont de type float, ils seront convertis en double par le compilateur. fonction acos asin atan cos sin tan cosh sinh tanh exp log log10 pow sqrt fabs fmod ceil floor action arc cosinus arc sinus arc tangente cosinus sinus tangente cosinus hyperbolique cosinus hyperbolique tangente hyperbolique exponentielle logarithme n ep erien logarithme en base 10 puissance racine carr ee valeur absolue reste de la division partie enti` ere sup erieure partie enti` ere inf erieure

104

Annexe A. La librairie standard

A.5
A.5.1

Utilitaires divers <stdlib.h>


Allocation dynamique
action allocation dynamique et initialisation ` a z ero. allocation dynamique modie la taille dune zone pr ealablement allou ee par calloc ou malloc. lib` ere une zone m emoire

Ces fonctions sont d ecrites au chapitre 3, paragraphe 3.4. fonction calloc malloc realloc free

A.5.2

Conversion de cha nes de caract` eres en nombres


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

Les fonctions suivantes permettent de convertir une cha ne de caract` eres en un nombre. fonction atof atoi atol

A.5.3

G en eration de nombres pseudo-al eatoires

La fonction rand fournit un nombre entier pseudo-al eatoire dans lintervalle [0,RAND MAX], ed enie au moins egale ` a 215 1. Lal ea fournit par la o` u RAND MAX est une constante pr fonction rand nest toutefois pas de tr` es bonne qualit e. La valeur retourn ee par rand d epend de linitialisation (germe) du g en erateur. Cette derni` ere est egale ` a 1 par d efaut mais elle peut etre modi ee ` a laide de la fonction srand. fonction rand srand prototype int rand(void) void srand(unsigned int germe) action fournit un nombre entier pseudoal eatoire modie la valeur de linitialisation du g en erateur pseudo-al eatoire utilis e par rand.

A.5.4

Arithm etique sur les entiers


prototype int abs(int n) long labs(long n) div t div(int a, int b) action valeur absolue dun entier valeur absolue dun long int quotient et reste de la division euclidienne de a par b. Les r esultats sont stock es dans les champs quot et rem (de type int) dune structure de type div t. quotient et reste de la division euclidienne de a par b. Les r esultats sont stock es dans les champs quot et rem (de type long int) dune structure de type ldiv t.

fonction abs labs div

ldiv

ldiv t ldiv(long a, long b)

A. Canteaut - Programmation en langage C

105

A.5.5

Recherche et tri

Les fonctions qsort et bsearch permettent respectivement de trier un tableau, et de rechercher un el ement dans un tableau d ej` a tri e. Leur syntaxe est d etaill ee au chapitre 4, page 71.

A.5.6

Communication avec lenvironnement


prototype void abort(void) void exit(int etat) action terminaison anormale du programme terminaison du programme ; rend le contr ole au syst` eme en lui fournissant la valeur etat (la valeur 0 est consid er ee comme une n normale). ex ecution de la commande syst` eme d enie par la cha ne de caract` eres s.

fonction abort exit

system

int system(char *s)

106

Annexe A. La librairie standard

A.6

Date et heure <time.h>

Plusieurs fonctions permettent dobtenir la date et lheure. Le temps est repr esent e par des objets de type time t ou clock t, lesquels correspondent g en eralement ` a des int ou ` a des long int. fonction time prototype time t time(time t *tp) action retourne le nombre de secondes ecoul ees depuis le 1er janvier 1970, 0 heures G.M.T. La valeur retourn ee est assign ee ` a *tp. retourne la di erence t1 - t2 en secondes. convertit le temps syst` eme *tp en une cha ne de caract` eres explicitant la date et lheure sous un format pr ed etermin e. retourne le temps CPU en microsecondes utilis e depuis le dernier appel ` a clock.

difftime ctime

double difftime(time t t1, time t t2) char *ctime(time t *tp)

clock

clock t clock(void)

107

Annexe B

Le d ebogueur GDB
Le logiciel gdb est un logiciel GNU permettant de d eboguer les programmes C (et C++). Il permet de r epondre aux questions suivantes : ` a quel endroit sarr ete le programme en cas de terminaison incorrecte, notamment en cas derreur de segmentation? quelles sont les valeurs des variables du programme ` a un moment donn e de lex ecution? quelle est la valeur dune expression donn ee ` a un moment pr ecis de lex ecution? Gdb permet donc de lancer le programme, darr eter lex ecution ` a un endroit pr ecis, dexaminer et de modier les variables au cours de lex ecution et aussi dex ecuter le programme pas-` a-pas.

B.1

D emarrer gdb

Pour pouvoir utiliser le d ebogueur, il faut avoir compil e le programme avec loption -g de gcc. Cette option g en` ere des informations symboliques n ecessaires au d ebogueur. Par exemple: gcc -g -Wall -ansi -o exemple exemple.c On peut ensuite lancer gdb sous le shell par la commande gdb nom de lex ecutable Toutefois, il est encore plus pratique dutiliser gdb avec linterface oerte par Emacs. Pour lancer gdb sous Emacs, il faut utiliser la commande M-x gdb o` u M-x signie quil faut appuyer simultan ement sur la touche M eta (Alt sur la plupart des claviers) et sur x. Emacs demande alors le nom du chier ex ecutable ` a d eboguer : il ache dans le mini-buer Run gdb (like this): gdb Quand on entre le nom dex ecutable, gdb se lance : le lancement fournit plusieurs informations sur la version utilis ee et la licence GNU. Puis, le prompt de gdb sache : (gdb)

108 On peut alors commencer ` a d eboguer le programme.

Annexe B. Le d ebogueur GDB

On est souvent amen e au cours du d ebogage ` a corriger une erreur dans le chier source et ` a recompiler. Pour pouvoir travailler avec le nouvel ex ecutable sans avoir ` a quitter gdb, il faut le red enir ` a laide de la commande file : (gdb) file nom executable

B.2

Quitter gdb

Une fois le d ebogage termin e, on quitte gdb par la commande (gdb) quit Parfois, gdb demande une conrmation : The program is running. Exit anyway? (y or n)

Il faut evidemment taper y pour quitter le d ebogueur.

B.3

Ex ecuter un programme sous gdb

Pour ex ecuter un programme sous gdb, on utilise la commande run : (gdb) run [arguments du programme] o` u arguments du programme sont, sil y en a, les arguments de votre programme. On peut egalement utiliser comme arguments les op erateurs de redirection, par exemple : (gdb) run 3 5 > sortie gdb lance alors le programme exactement comme sil avait et e lanc e avec les m emes arguments : ./a.out 3 5 > sortie Comme la plupart des commandes de base de gdb, run peut etre remplac e par la premi` ere lettre du nom de la commande, r. On peut donc ecrire egalement (gdb) r 3 5 > sortie On est souvent amen e ` a ex ecuter plusieurs fois un programme pour le d eboguer. Par d efaut, gdb r eutilise donc les arguments du pr ec edent appel de run si on utilise run sans arguments. ` tout moment, la commande show args ache la liste des arguments pass A es lors du dernier appel de run : (gdb) show args Argument list to give program being debugged when it is started is "3 5 > sortie". (gdb) Si rien ne sy oppose et que le programme sex ecute normalement, on atteint alors la n du programme. gdb ache alors ` a la n de lex ecution Program exited normally. (gdb)

A. Canteaut - Programmation en langage C

109

B.4

Terminaison anormale du programme

Dans toute la suite, on prendra pour exemple le programme de la page 110, dont le but est de lire deux matrices enti` eres dont les tailles et les coecients sont fournis dans un chier entree.txt, puis de calculer et dacher leur produit. On ex ecutera ce programme sur lexemple suivant (contenu du chier entree.txt) 3 1 0 1 2 0 1 1

2 4 2 3 4 5 1 2 3 4 Pour d eboguer, on ex ecute donc la commande (gdb) run < entree.txt Ici le programme sarr ete de fa con anormale (erreur de segmentation). Dans ce cas, gdb permet dindentier lendroit exact o` u le programme sest arr et e. Il ache par exemple (gdb) run < entree.txt Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Affichage de A: Program received signal SIGSEGV, Segmentation fault. 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 (gdb) On en d eduit que lerreur de segmentation sest produite ` a lex ecution de la ligne 38 du programme source, lors dun appel ` a la fonction affiche avec les arguments M = 0x8049af8, etre Emacs utilis ee pour nb lignes = 1073928121, nb col = 134513804. Par ailleurs, la fen d eboguer se coupe en 2, et ache dans sa moiti e inf erieure le programme source, en pointant par une ` eche la ligne qui a provoqu e larr et du programme : => for (j=0; j < nb_col; j++) printf("%2d\t",M[i][j]); printf("\n");

Dans un tel cas, on utilise alors la commande backtrace (raccourci bt), qui ache l etat de la pile des appels lors de larr et du programme. Une commande strictement equivalente ` a backtrace est la commande where. (gdb) backtrace #0 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 #1 0x8048881 in main () at exemple.c:78 (gdb)

110

1 60 61

59

#include <stdio.h> #include <stdlib.h>

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

for (i=0; i < nb_lignes1; i++) { for (j=0; j < nb_col2; j++) /* declaration des fonctions secondaires */ 62 { int **lecture_matrice(unsigned int, unsigned int); 63 for (k=0; k < nb_lignes2; k++) void affiche(int **, unsigned int, unsigned int); 64 P[i][j] += A[i][k] * B[k][j]; int **produit(int **, int **, unsigned int, unsigned int 65 , unsigned } int, unsigned int); 66 } 67 return(P); int **lecture_matrice(unsigned int nb_lignes, unsigned int 68 } nb_col) { 69 int **M; 70 int i, j; 71 int main() 72 { scanf("%d", &nb_lignes); 73 int **A, **B, **P; scanf("%d", &nb_col); 74 unsigned int nb_lignesA, nb_lignesB, nb_colA, nb_colB; M = (int**)malloc(nb_lignes * sizeof(int*)); 75 for (i=0; i<nb_lignes; i++) 76 A = lecture_matrice(nb_lignesA, nb_colA); M[i] = (int*)malloc(nb_col * sizeof(int)); 77 printf("\n Affichage de A:\n"); for (i=0; i<nb_lignes; i++) 78 affiche(A, nb_lignesA, nb_colA); { 79 B = lecture_matrice(nb_lignesB, nb_colB); for (j=0; j<nb_col; j++) 80 printf("\n Affichage de B:\n"); scanf("%d",&M[i][j]); 81 affiche(B, nb_lignesB, nb_colB); } 82 P = produit(A, B, nb_lignesA, nb_colA, nb_lignesB, nb_colB); return(M); 83 printf("\n Affichage du produit de A par B:\n"); } 84 affiche(P, nb_lignesA, nb_colB); 85 return(EXIT_SUCCESS); void affiche(int **M, unsigned int nb_lignes, unsigned int 86 } nb_col) { 87 int i, j; if (M == NULL) { printf("\n Erreur: la matrice a afficher est egale a NULL\n"); return; } for (i=0; i < nb_lignes; i++) { for (j=0; j < nb_col; j++) printf("%2d\t",M[i][j]); printf("\n"); } return; }

43

44

45

46

47

48

int **produit(int **A, int **B, unsigned int nb_lignes1, unsigned int nb_col1, unsigned int nb_lignes2, unsigned int nb_col2) { int **P; int i, j, k;

49

50

51

52

53

54

55

Annexe B. Le d ebogueur GDB

56

57

58

if (nb_col1 != nb_lignes2) { printf("\n Impossible deffectuer le produit : dimensions incompatibles\n"); return(NULL); } P = (int**)malloc(nb_lignes1 * sizeof(int*)); for (i=0; i<nb_lignes1; i++) P[i] = (int*)calloc(nb_col2, sizeof(int)); /* calcul de P[i][j] */

A. Canteaut - Programmation en langage C

111

On apprend ici que lerreur a et e provoqu e par la ligne 38 du programme, ` a lint erieur dun appel ` a la fonction affiche qui, elle, avait et e appel ee ` a la ligne 78 par la fonction main. Lerreur survient donc ` a lachage de la premi` ere matrice lue. Gdb fournit d ej` a une id ee de la source du bogue en constatant que les valeurs des arguments de la fonction affiche ont lair anormales.

B.5

Acher les donn ees

Pour en savoir plus, on peut faire acher les valeurs de certaines variables. On utilise pour cela la commande print (raccourci p) qui permet dacher la valeur dune variable, dune expression... Par exemple ici, on peut faire (gdb) print i $1 = 0 (gdb) print j $2 = 318 (gdb) print M[i][j] Cannot access memory at address 0x804a000. Lerreur provient clairement du fait que lon tente de lire l el ement dindice [0][318] de la matrice qui nest pas d eni (puisque le chier entree.txt contenait une matrice ` a 3 lignes et 2 colonnes). Par d efaut, print ache lobjet dans un format naturel (un entier est ach e sous forme d ecimale, un pointeur sous forme hexad ecimale...). On peut toutefois pr eciser le format dachage ` a laide dun sp ecicateur de format sous la forme (gdb) print /f expression o` u la lettre f pr ecise le format dachage. Les principaux formats correspondent aux lettres suivantes : d pour la repr esentation d ecimale sign ee, x pour lhexad ecimale, o pour loctale, c pour un caract` ere, f pour un ottant. Un format dachage sp ecique au d ebogueur pour les entiers est /t qui ache la repr esentation binaire dun entier. (gdb) print j $3 = 328 (gdb) p /t j $4 = 101001000 Les identicateurs $1 ... $4 qui apparaissent en r esultat des appels ` a print donnent un nom aux valeurs retourn ees et peuvent etre utilis es par la suite (cela evite de retaper des constantes et minimise les risques derreur). Par exemple (gdb) print nb_col $5 = 134513804 (gdb) print M[i][$5-1] Cannot access memory at address 0x804a000. Lidenticateur $ correspond ` a la derni` ere valeur ajout ee et $$ ` a lavant-derni` ere. On peut visualiser les 10 derni` eres valeurs ach ees par print avec la commande show values.

112

Annexe B. Le d ebogueur GDB

Une fonctionnalit e tr` es utile de print est de pouvoir acher des zones-m emoire contigu es (on parle de tableaux dynamiques). Pour une variable x donn ee, la commande print x@longueur ache la valeur de x ainsi que le contenu des longueur-1 zones-m emoires suivantes. Par exemple (gdb) print M[0][0]@10 $4 = {1, 0, 0, 17, 0, 1, 0, 17, 1, 1} ache la valeur de M[0][0] et des 9 entiers suivants en m emoire. De m eme, (gdb) print M[0]@8 $5 = {0x8049b08, 0x8049b18, 0x8049b28, 0x11, 0x1, 0x0, 0x0, 0x11} ache la valeur de M[0] (de type int*) et des 7 objets de type int* qui suivent en m emoire. Quand il y a une ambigu t e sur le nom dune variable (dans le cas o` u plusieurs variables locales ont le m eme nom, ou que le programme est divis e en plusieurs chiers source qui contiennent des variables portant le m eme nom), on peut pr eciser le nom de la fonction ou du chier source dans lequel la variable est d enie au moyen de la syntaxe nom de fonction::variable nom de fichier::variable Pour notre programme, on peut pr eciser par exemple (gdb) print affiche::nb_col $6 = 134513804 La commande whatis permet, elle, dacher le type dune variable. Elle poss` ede la m eme syntaxe que print. Par exemple, (gdb) whatis M type = int ** Dans le cas de types structures, unions ou enum erations, la commande ptype d etaille le type en fournissant le nom et le type des di erents champs (alors whatis nache que le nom du type). Enn, on peut egalement acher le prototype dune fonction du programme ` a laide de la commande info func : (gdb) info func affiche All functions matching regular expression "affiche": File exemple.c: void affiche(int **, unsigned int, unsigned int); (gdb)

A. Canteaut - Programmation en langage C

113

B.6

Appeler des fonctions

` laide de la commande print, on peut A egalement appeler des fonctions du programme en choisissant les arguments. Ainsi pour notre programme, on peut d etecter que le bogue vient du fait que la fonction affiche a et e appel ee avec des arguments etranges. En eet, si on appelle affiche avec les arguments corrects, on voit quelle ache bien la matrice souhait ee : (gdb) print affiche(M, 3, 2) 1 0 0 1 1 1 $8 = void On remarque que cette commande ache la valeur retourn ee par la fonction (ici void). Une commande equivalente est la commande call : (gdb) call fonction(arguments)

B.7

Modier des variables

On peut aussi modier les valeurs de certaines variables du programme ` a un moment donn e de lex ecution gr ace ` a la commande (gdb) set variable nom variable = expression Cette commande aecte ` a nom variable la valeur de expression. Cette aectation peut egalement se faire de mani` ere equivalente ` a laide de la commande print : (gdb) print nom variable = expression qui ache la valeur de expression et laecte ` a variable.

B.8

Se d eplacer dans la pile des appels

` un moment donn A e de lex ecution, gdb a uniquement acc` es aux variables d enies dans ce contexte, cest-` a-dire aux variables globales et aux variables locales ` a la fonction en cours dex ecution. Si lon souhaite acc eder ` a des variables locales ` a une des fonctions situ ees plus haut dans la pile dappels (par exemple des variables locales ` a main ou locales ` a la fonction appelant la fonction courante), il faut au pr ealable se d eplacer dans la pile des appels. La commande where ache la pile des appels. Par exemple, dans le cas de notre programme, on obtient (gdb) where #0 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 #1 0x8048881 in main () at exemple.c:78 On constate ici que lon se situe dans la fonction affiche, qui a et e appel ee par main. Pour linstant, on ne peut donc acc eder quaux variables locales ` a la fonction affiche. Si lon tente dacher une variable locale ` a main, gdb produit le message suivant : (gdb) print nb_lignesA No symbol "nb_lignesA" in current context.

114

Annexe B. Le d ebogueur GDB

La commande up permet alors de se d eplacer dans la pile des appels. Ici, on a (gdb) up #1 0x8048881 in main () at exemple.c:78 Plus g en eralement, la commande (gdb) up [nb positions] permet de se d eplacer de n positions dans la pile. La commande (gdb) down [nb positions] permet de se d eplacer de n positions dans le sens inverse. La commande frame numero permet de se placer directement au num ero numero dans la pile des appels. Si le numero nest pas sp eci e, elle ache lendroit o` u lon se trouve dans la pile des appels. Par exemple, si on utilise la commande up, on voit gr ace ` a frame que lon se situe maintenant dans le contexte de la fonction main : (gdb) up #1 0x8048881 in main () at exemple.c:78 (gdb) frame #1 0x8048881 in main () at exemple.c:78 On peut alors acher les valeurs des variables locales d enies dans le contexte de main. Par exemple (gdb) print nb_lignesA $9 = 1073928121 (gdb) print nb_colA $10 = 134513804

B.9

Poser des points darr et

Un point darr et est un endroit o` u lon interrompt temporairement lex ecution du programme enn dexaminer (ou de modier) les valeurs des variables ` a cet endroit. La commande permettant de mettre un point darr et est break (raccourci en b). On peut demander au programme de sarr eter avant lex ecution dune fonction (le point darr et est alors d eni par le nom de la fonction) ou avant lex ecution dune ligne donn ee du chier source (le point darr et est alors d eni par le num ero de la ligne correspondant). Dans le cas de notre programme, on peut poser par exemple deux points darr et, lun avant lex ecution de la fonction affiche et lautre avant la ligne 24 du chier, qui correspond ` a linstruction de retour ` a la fonction appelante de lecture matrice : (gdb) break affiche Breakpoint 1 at 0x80485ff: file exemple.c, line 30. (gdb) break 24 Breakpoint 2 at 0x80485e8: file exemple.c, line 24. En pr esence de plusieurs chiers source, on peut sp ecier le nom du chier source dont on donne le num ero de ligne de la mani` ere suivante (gdb) break nom fichier:numero ligne

A. Canteaut - Programmation en langage C (gdb) break nom fichier:nom fonction

115

Sous Emacs, pour mettre un point darr et ` a la ligne num ero n (ce qui signie que le programme va sarr eter juste avant dex ecuter cette ligne), il sut de se placer ` a la ligne n du chier source et de taper C-x SPC o` u SPC d esigne la barre despace. Quand on ex ecute le programme en pr esence de points darr et, le programme sarr ete d` es quil rencontre le premier point darr et. Dans notre cas, on souhaite comprendre comment les variables nb lignesA et nb colA, qui correspondent au nombre de lignes et au nombre de colonnes de la matrice lue, evoluent au cours de lex ecution. On va donc ex ecuter le programme depuis le d epart ` a laide de la commande run et examiner les valeurs de ces variables ` a chaque point darr et. (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Breakpoint 2, lecture_matrice (nb_lignes=3, nb_col=2) at exemple.c:24 (gdb) Le premier message ach e par gdb demande si lon veut reprendre lex ecution du programme depuis le d ebut. Si lon r epond oui (en tapant y), le programme est relanc e (avec par d efaut les m emes arguments que lors du dernier appel de run). Il sarr ete au premier point darr et rencontr e, qui est le point darr et num ero 2 situ e` a la ligne 24 du chier. On peut alors faire acher les valeurs de certaines variables, les modier... Par exemple, ici, (gdb) $11 = (gdb) $12 = print nb_lignes 3 print nb_col 2

La commande continue (raccourci en c) permet de poursuivre lex ecution du programme jusquau point darr et suivant (ou jusqu` a la n). Ici, on obtient (gdb) continue Continuing. Affichage de A: Breakpoint 1, affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:30 (gdb) On remarque ici que les variables correspondant aux nombres de lignes et de colonnes avaient la bonne valeur ` a lint erieur de la fonction lecture matrice, et quelles semblent prendre une valeur al eatoire d` es que lon sort de la fonction. Lerreur vient du fait que les arguments nb lignes et nb col de lecture matrice doivent etre pass es par adresse et non par valeur, pour que leurs valeurs soient conserv ees ` a la sortie de la fonction.

116

Annexe B. Le d ebogueur GDB

B.10

G erer les points darr et

Pour conna tre la liste des points darr et existant ` a un instant donn e, il faut utiliser la commande info breakpoints (qui peut sabbr eger en info b ou m eme en i b). (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080485ff in affiche at exemple.c:30 2 breakpoint keep y 0x080485e8 in lecture_matrice at exemple.c:24 On peut enlever un point darr et gr ace ` a la commande delete (raccourci d) : (gdb) delete numero point arr^ et En labsence dargument, delete d etruit tous les points darr et. La commande clear permet egalement de d etruire des points darr et mais en sp eciant, non plus le num ero du point darr et, mais la ligne du programme ou le nom de la fonction o` u ils gurent. Par exemple, (gdb) clear nom de fonction enl` eve tous les points darr et qui existaient ` a lint erieur de la fonction. De la m eme fa con, si on donne un num ero de la ligne en argument de clear, on d etruit tous les points darr et concernant cette ligne. Enn, on peut aussi d esactiver temporairement un point darr et. La 4e colonne du tableau ach e par info breakpoints contient un y si le point darr et est activ e et un n sinon. La commande et disable numero point arr^ d esactive le point darr et correspondant. On peut le r eactiver par la suite avec la commande et enable numero point arr^ Cette fonctionnalit e permet d eviter de d etruire un point darr et dont on aura peut- etre besoin plus tard, lors dune autre ex ecution par exemple.

B.11

Les points darr et conditionnels

On peut egalement mettre un point darr et avant une fonction ou une ligne donn ee du programme, mais en demandant que ce point darr et ne soit eectif que sous une certaine condition. La syntaxe est alors (gdb) break ligne ou fonction if condition Le programme ne sarr etera au point darr et que si la condition est vraie. Dans notre cas, le point darr et de la ligne 24 (juste avant de sortir de la fonction lecture matrice) nest vraiment utile que si les valeurs des variables nb lignes et nb col qui nous int eressent sont anormales. On peut donc utilement remplacer le point darr et num ero 2 par un point darr et conditionnel : (gdb) break 24 if nb_lignes != 3 || nb_col != 2 Breakpoint 8 at 0x80485e8: file exemple.c, line 24. (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x080485ff in affiche at exemple.c:30

A. Canteaut - Programmation en langage C breakpoint already hit 1 time 3 breakpoint keep y 0x080485e8 in lecture_matrice at exemple.c:24 stop only if nb_lignes != 3 || nb_col != 2 (gdb)

117

Si on relance lex ecution du programme avec ces deux points darr et, on voit que le programme sarr ete au point darr et num ero 1, ce qui implique que les variables nb lignes et nb col ont bien la bonne valeur ` a la n de la fonction lecture matrice : (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Affichage de A: Breakpoint 1, affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:30 (gdb) On peut aussi transformer un point darr et existant en point darr et conditionnel avec la commande cond (gdb) cond numero point arret condition Le point darr et num ero numero point arret est devenu un point darr et conditionnel, qui ne sera eectif que si condition est satisfaite. De m eme pour transformer un point darr et conditionnel en point darr et non conditionnel (cest-` a-dire pour enlever la condition), il sut dutiliser la commande cond sans pr eciser de condition.

B.12

Ex ecuter un programme pas ` a pas

Gdb permet, ` a partir dun point darr et, dex ecuter le programme instruction par instruction. La commande next (raccourci n) ex ecute uniquement linstruction suivante du programme. Lors que cette instruction comporte un appel de fonction, la fonction est enti` erement ex ecut ee. Par exemple, en partant dun point darr et situ e` a la ligne 77 du programme (il sagit de la ligne printf("\n Affichage de A:\n"); dans la fonction main), 2 next successifs produisent leet suivant (gdb) where #0 main () at exemple.c:77 (gdb) next Affichage de A: (gdb) next

118

Annexe B. Le d ebogueur GDB

Program received signal SIGSEGV, Segmentation fault. 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 (gdb) La premier next ex ecute la ligne 77 ; le second ex ecute la ligne 78 qui est lappel ` a la fonction affiche. Ce second next conduit ` a une erreur de segmentation. La commande step (raccourci s) a la m eme action que next, mais elle rentre dans les fonctions : si une instruction contient un appel de fonction, la commande step eectue la premi` ere instruction du corps de cette fonction. Si dans lexemple pr ec edent, on ex ecute deux fois la commande step ` a partir de la ligne 78, on obtient (gdb) where #0 main () at exemple.c:78 (gdb) step affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:30 (gdb) step (gdb) where #0 affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:35 #1 0x8048881 in main () at exemple.c:78 (gdb) On se trouve alors ` a la deuxi` eme instruction de la fonction affiche, ` a la ligne 35. Enn, lorsque le programme est arr et e` a lint erieur dune fonction, la commande finish termine lex ecution de la fonction. Le programme sarr ete alors juste apr` es le retour ` a la fonction appelante. Par exemple, si lon a mis un point darr et ` a la ligne 14 (premi` ere instruction a cet endroit fait sortir de scanf de la fonction lecture matrice), la commande finish ` lecture matrice : (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Breakpoint 2, lecture_matrice (nb_lignes=3, nb_col=134513804) at exemple.c:14 (gdb) where #0 lecture_matrice (nb_lignes=3, nb_col=134513804) at exemple.c:14 #1 0x804885b in main () at exemple.c:76 (gdb) finish Run till exit from #0 lecture_matrice (nb_lignes=3, nb_col=134513804) at exemple.c:14 0x804885b in main () at exemple.c:76 Value returned is $1 = (int **) 0x8049af8 (gdb)

A. Canteaut - Programmation en langage C

119

B.13

Acher la valeur dune expression ` a chaque point darr et

On a souvent besoin de suivre l evolution dune variable ou dune expression au cours du programme. Plut ot que de r ep eter la commande print ` a chaque point darr et ou apr` es chaque next ou step, on peut utiliser la commande display (m eme syntaxe que print) qui permet dacher la valeur dune expression ` a chaque fois que le programme sarr ete. Par exemple, si lon veut faire acher par gdb la valeur de M[i][j] ` a chaque ex ecution de la ligne 38 (ligne printf("%2d\t",M[i][j]); dans les deux boucles for de affiche), on y met un point darr et et on fait (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Affichage de A: Breakpoint 1, affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 (gdb) display i 1: i = 0 (gdb) display j 2: j = 0 (gdb) display M[i][j] 3: M[i][j] = 1 (gdb) continue Continuing. Breakpoint 1, affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 3: M[i][j] = 0 2: j = 1 1: i = 0 (gdb) c Continuing. Breakpoint 1, affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 3: M[i][j] = 0 2: j = 2 1: i = 0 (gdb) next 3: M[i][j] = 0 2: j = 2 1: i = 0 (gdb)

120

Annexe B. Le d ebogueur GDB

On remarque que la commande display ache les valeurs des variables ` a chaque endroit o` u le programme sarr ete (que cet arr et soit provoqu e par un point darr et ou par une ex ecution pas-` a-pas avec next ou step). A chaque expression faisant lobjet dun display est associ ee un num ero. La commande info display (raccourci i display) ache la liste des expressions faisant lobjet dun display et les num eros correspondants. (gdb) info display Auto-display expressions now in effect: Num Enb Expression 3: y M[i][j] 2: y j 1: y i (gdb) Pour annuler une commande display, on utilise la commande undisplay suivie du num ero correspondant (en labsence de num ero, tous les display sont supprim es) (gdb) undisplay 1 (gdb) info display Auto-display expressions now in effect: Num Enb Expression 3: y M[i][j] 2: y j (gdb) Comme pour les points darr et, les commandes (gdb) disable disp numero display (gdb) enable disp numero display respectivement d esactive et active lachage du display correspondant.

B.14

Ex ecuter automatiquement des commandes aux points darr et

On peut parfois souhaiter ex ecuter la m eme liste de commandes ` a chaque fois que lon rencontre un point darr et donn e. Pour cela, il sut de d enir une seule fois cette liste de commandes ` a laide de commands avec la syntaxe suivante : (gdb) commands numero point arret commande 1 ... commande n end o` u numero point arret d esigne le num ero du point darr et concern e. Cette fonctionnalit e est notamment utile car elle permet de placer la commande continue ` a la n de la liste. On peut donc automatiquement passer de ce point darr et au suivant sans avoir ` a entrer continue. Supposons par exemple que le programme ait un point darr et ` a la ligne 22 (ligne scanf("%d",&M[i][j]);

A. Canteaut - Programmation en langage C

121

de la fonction lecture matrice. A chaque fois que lon rencontre ce point darr et, on d esire acher les valeurs de i, j, M[i][j] et reprendre lex ecution. On entre alors la liste de commandes suivantes associ ee au point darr et 1 : (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >echo valeur de i \n >print i >echo valeur de j \n >print j >echo valeur du coefficient M[i][j] \n >print M[i][j] >continue >end (gdb) Quand on lance le programme, ces commandes sont eectu ees ` a chaque passage au point darr et (et notamment la commande continue qui permet de passer automatiquement au point darr et suivant). On obtient donc (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt Breakpoint 1, lecture_matrice (nb_lignes=3, nb_col=2) at exemple.c:22 valeur de i $38 = 0 valeur de j $39 = 0 valeur du coefficient M[i][j] $40 = 0 Breakpoint 1, lecture_matrice (nb_lignes=3, nb_col=2) at exemple.c:22 valeur de i $41 = 0 valeur de j $42 = 1 valeur du coefficient M[i][j] $43 = 0 Breakpoint 1, lecture_matrice (nb_lignes=3, nb_col=2) at exemple.c:22 valeur de i $44 = 1 valeur de j $45 = 0 valeur du coefficient M[i][j]

122 $46 = 0 ...

Annexe B. Le d ebogueur GDB

Breakpoint 1, lecture_matrice (nb_lignes=3, nb_col=2) at exemple.c:22 valeur de i $53 = 2 valeur de j $54 = 1 valeur du coefficient M[i][j] $55 = 0 Affichage de A: Program received signal SIGSEGV, Segmentation fault. 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 (gdb) Il est souvent utile dajouter la commande silent ` a la liste de commandes. Elle supprime lachage du message Breakpoint ... fourni par gdb quand il atteint un point darr et. Par exemple, la liste de commande suivante (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >echo valeur de i \n >print i >echo valeur de j \n >print j >echo valeur du coefficient M[i][j] \n >print M[i][j] >continue >end produit leet suivant ` a lex ecution : (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/canteaut/COURS_C/DEBUG/exemple < entree.txt valeur de i $56 = 0 valeur de j $57 = 0 valeur du coefficient M[i][j] $58 = 0 valeur de i

A. Canteaut - Programmation en langage C $59 = 0 valeur de $60 = 1 valeur du $61 = 0 ... valeur de $71 = 2 valeur de $72 = 1 valeur du $73 = 0

123

j coefficient M[i][j]

i j coefficient M[i][j]

Affichage de A: Program received signal SIGSEGV, Segmentation fault. 0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at exemple.c:38 (gdb) Notons enn que la liste de commandes associ ee ` a un point darr et appara t lorsque lon ache la liste des points darr et avec info breakpoints.

B.15

Les raccourcis des noms de commande

Tous les noms de commande peuvent etre remplac es par leur plus court pr exe nonambigu e. Par exemple, la commande clear peut s ecrire cl car aucune autre commande de gdb ne commence par cl. La lettre c seule ne peut pas etre utilis ee pour clear car plusieurs commandes de gdb commencent par c (continue, call...). Emacs permet la compl etion automatique des noms de commande gdb : quand on tape le d ebut dune commande et que lon appuie sur TAB, le nom de la commande est compl et e sil ny a pas dambigu t e. Sinon, Emacs fournit toutes les possibilit es de compl etion. Il sut alors de cliquer (bouton du milieu) pour choisir la commande. Les commandes les plus fr equentes de gdb sont abbr eg ees par leur premi` ere lettre, m eme sil existe dautres commandes commen cant par cette lettre. Par exemple, la commande continue peut etre abbr eg ee en c. Cest le cas pour les commandes break, continue, delete, frame, help, info, next, print, quit, run et step.

B.16

Utiliser lhistorique des commandes

Il est possible dactiver sous gdb lhistorique des commandes, an de ne pas avoir ` a retaper sans cesse la m eme commande. Pour activer cette fonctionnalit e, il sut dentrer la commande

(gdb) set history expansion

124

Annexe B. Le d ebogueur GDB

Dans ce cas, comme sous Unix, !! rappelle la derni` ere commande ex ecut ee et !caract` ere rappelle la derni` ere commande commen cant par ce caract` ere. Par exemple, (gdb) !p print nb_lignes $75 = 1073928121 (gdb) Cette fonctionnalit e nest pas activ ee par d efaut car il peut y avoir ambigu t e entre le signe ! permettant de rappeler une commande et le ! correspondant ` a la n egation logique du langage C.

B.17

Interface avec le shell

On peut ` a tout moment sous gdb ex ecuter des commandes shell. Les commandes cd, pwd et make sont disponibles. Plus g en eralement, la commande gdb suivante (gdb) shell commande ex ecute commande.

B.18

R esum e des principales commandes

A. Canteaut - Programmation en langage C commande backtrace break (M-x SPC) clear commands cond continue delete disable disable disp display down enable enable disp file finish frame help info breakpoints info display info func next run print ptype quit set history expansion set variable shell show args show values step undisplay up whatis where h i b action indique o` u lon se situe dans la pile des appels (synonyme de where) pose un point darr et ` a une ligne d enie par son num ero ou au d ebut dune fonction. d etruit tous les points darr et sur une ligne ou dans une fonction d enit une liste de commandes ` a eectuer automatiquement ` a un point darr et ajoute une condition ` a un point darr et continue lex ecution (apr` es un point darr et) d etruit le point darr et dont le num ero est donn e d esactive un point darr et d esactive un display ache la valeur dune expression ` a chaque arr et du programme descend dans la pile des appels r eactive un point darr et r eactive un display red enit lex ecutable termine lex ecution dune fonction permet de se placer ` a un endroit donn e dans la pile des appels et ache le contexte fournit de laide ` a propos dune commande ache les points darr et donne la liste des expressions ach ees par des display ache le prototype dune fonction ex ecute linstruction suivante (sans entrer dans les fonctions) lance lex ecution du programme (par d efaut avec les arguments utilis es pr ec edemment) ache la valeur dune expression d etaille un type structure quitte gdb active lhistorique des commandes modie la valeur dune variable permet dex ecuter des commandes shell ache les arguments du programme r eache les 10 derni` eres valeurs ach ees ex ecute linstruction suivante (en entrant dans les fonctions) supprime un display monte dans la pile des appels donne le type dune expression indique o` u lon se situe dans la pile des appels (synonyme de backtrace)

125 page 109 114 116 120 117 115 116 116 120 119 114 116 120 108 118 114

bt b cl

c d

n r p q

116 120 112 117 108 111 112 108 123 113 124 108 111 118 120 114 112 109

126

Annexe B. Le d ebogueur GDB

Bibliographie

127

Bibliographie
[1] Andr e (J.) et Goossens (M.). Codage des caract` eres et multi-linguisme : de lAscii ` a Unicode et Iso/Iec-10646. Cahiers GUTenberg n 20, mai 1985. http://www.gutenberg.eu.org/pub/GUTenberg/publications/cahiers.html. [2] Braquelaire (J.-P.). M ethodologie de la programmation en C. Dunod, 2000, troisi` eme edition. [3] Cassagne (B.). Introduction au langage C. http://clips.imag.fr/commun/bernard.cassagne/Introduction ANSI C.html. [4] Delannoy (C.). Programmer en langage C. Eyrolles, 1992. [5] Faber (F.). Introduction ` a la programmation en ANSI-C. http://www.ltam.lu/Tutoriel Ansi C/. [6] Kernighan (B.W.) et Richie (D.M.). The C programming language. Prentice Hall, 1988, seconde edition. [7] L eon (L.) et Millet (F.). C si facile. Eyrolles, 1987. [8] Loukides (M.) et Oram (A.). Programming with GNU software. OReilly, 1997. [9] Moussel (P.). Le langage C. http://www.mrc.lu/LangC/. [10] Sendrier (N.). Notes d introduction au C. Cours de DEA, Universit e de Limoges, 1998.

128

Index

Index
!= (di erent de), 21 ! (n egation logique), 21 * (multiplication), 20 * (indirection), 44 ++ (incr ementation), 23 + (addition), 20 , (op erateur virgule), 23 -- (d ecr ementation), 23 -> (pointeur de membre de structure), 55 - (soustraction), 20 . (membre de structure), 38 / (division), 20 == (test d egalit e), 21 = (aectation), 19 #define, 77 #elif, 79 #else, 79 #endif, 79 #if, 79 #ifdef, 80 #ifndef, 80 #include, 61, 77 % (reste de la division), 20 && (et logique), 21 & (adresse), 24, 31 & (et bit-` a-bit), 22 || (ou logique), 21 | (ou inclusif bit-` a-bit), 22 ? (op erateur conditionnel ternaire), 23 << (d ecalage ` a gauche), 22 >> (d ecalage ` a droite), 22 ^ (ou exclusif bit-` a-bit), 22 ~ (compl ement ` a 1 bit-` a-bit), 22 abort, 105 abs, 104 acos, 103 addition (+), 20 adresse, 14, 43 op erateur (&), 24, 31 transmission de param` etres, 65 aectation compos ee, 22 simple (=), 19 al ea, 104 allocation dynamique, 47, 104 calloc, 49 free, 50 malloc, 47 realloc, 104 arbre binaire, 57 arc cosinus (acos), 103 arc sinus (asin), 103 arc tangente (atan), 103 argc, 68 arguments de main, 68 argv, 68 ASCII, 14 asin, 103 assemblage, 9 atan, 103 atof, 104 atoi, 68, 104 atol, 104 auto, 62 bloc dinstructions, 13 boucles, 2627 break, 25, 28 bsearch, 71 calloc, 49, 104 caract` eres, 14 ASCII, 14 char, 14 constantes, 19 ecriture, 32, 83, 100 isalnum, 101

Index isalpha, 101 iscntrl, 101 isdigit, 101 isgraph, 101 islower, 101 ISO-Latin-1, 14 isprint, 101 ispunct, 101 isspace, 101 isupper, 101 isxdigit, 101 lecture, 32, 83, 100 manipulation (ctype.h), 101 non imprimables, 19 tolower, 101 toupper, 101 type, 14 case, 25 cast, 24 cc, 10 ceil, 103 cha ne de caract` eres, 19, 36, 53 constante, 19 conversion, 68, 104 ecriture, 99, 100 gets, 100 lecture, 99, 100 longueur, 53, 102 manipulation (string.h), 102 puts, 100 sprintf, 99 sscanf, 99 strcat, 102 strchr, 102 strcmp, 102 strcpy, 102 strlen, 53, 102 strncat, 102 strncmp, 102 strncpy, 102 strrchr, 102 strstr, 102 champ de bits, 39 champ de structure, 37 char, 14 classe de m emorisation automatique, 62 statique, 61, 62, 64 temporaire, 63 clock, 106 commentaire, 12 compilation, 911, 80 make, 93 conditionnelle, 79 s epar ee, 9098 compl ement ` a 1 (~), 22 const, 66 constantes, 1719 symboliques, 78 continue, 28 conventions d ecriture, 33 conversion de type explicite (cast), 24 implicite, 20 cos, 103 cosh, 103 cosinus (cos), 103 cosinus hyperbolique (cosh), 103 ctime, 106 ctype.h, 101 date, 106 d eclaration, 13 d ecr ementation (--), 23 default, 25 define, 77 d enition de constantes, 78 de macros, 78 di erent de (!=), 21 difftime, 106 div, 104 division (/), 20 division (div), 104 division (ldiv), 104 do--while, 26 double, 17 edition de liens, 10 egalit e (==), 21 elif (pr eprocesseur), 79 else, 25 else (pr eprocesseur), 79 endif (pr eprocesseur), 79

129

130 entiers, 16 constantes, 18 int, 16 long, 16 repr esentation, 18 short, 16 types, 16 unsigned, 16 entr ees - sorties, 29, 99 binaires, 85 chier, 83 enum, 40 enum eration, 40 EOF, 32, 83 et (&), 22 et logique (&&), 21 exit, 67, 105 EXIT FAILURE, 67 EXIT SUCCESS, 67 exp, 103 exponentielle (exp), 103 expression, 12 extern, 91, 93 fabs, 103 fclose, 82 fgetc, 83, 100 chier acc` es direct, 86 binaire, 82 ecriture, 83, 99 en-t ete, 61, 77, 91 ex ecutable, 10 fclose, 82 fermeture, 82 fflush, 99 fgetc, 83, 100 n (EOF), 32, 83 ot de donn ees, 81 fopen, 81 fprintf, 83, 99 fputc, 83, 100 fread, 85 fscanf, 83, 99 fseek, 86 ftell, 87 fwrite, 85 gestion, 8188, 99 getc, 83 lecture, 83, 99 objet, 9 ouverture, 81 positionnement, 86 putc, 83 rewind, 87 source, 9 standard, 82 texte, 82 ungetc, 84 FILE *, 81 float, 17 floor, 103 ot de donn ees, 81 ottants, 17 constantes, 18 double, 17 float, 17 long double, 17 repr esentation, 18 types, 17 fmod, 103 fonction, 5975 appel, 60 dinterface, 91 d enition, 59 d eclaration, 60, 91 math ematique (math.h), 103 param` etres eectifs, 59, 60 param` etres formels, 59 pointeur sur, 69 prototype, 60 r ecursive, 59 return, 59 transmission de param` etres, 64 fopen, 81 for, 27 formats dimpression, 30 de saisie, 31 fprintf, 83, 99 fputc, 83, 100 fread, 85 free, 50, 104 fscanf, 83, 99

Index

Index fseek, 86 ftell, 87 fwrite, 85 gcc, 10, 80, 96 getc, 83, 100 getchar, 32, 100 gets, 100 goto, 29 heure, 106 identicateur, 11 if (pr eprocesseur), 79 if--else, 25 ifdef (pr eprocesseur), 80 ifndef (pr eprocesseur), 80 include, 61, 77 incr ementation (++), 23 indirection (*), 44 instructions boucles, 2627 compos ees, 13 de branchement, 2526, 2829 int, 16 isalnum, 101 isalpha, 101 iscntrl, 101 isdigit, 101 isgraph, 101 islower, 101 ISO-Latin-1, 14 isprint, 101 ispunct, 101 isspace, 101 isupper, 101 isxdigit, 101 labs, 104 ldiv, 104 librairie standard, 10, 61, 77, 99106 ctype.h, 101 limits.h, 17 math.h, 103 stdarg.h, 74 stddef.h, 71 stdio.h, 99 stdlib.h, 104 string.h, 102 time.h, 106 limits.h, 17 liste cha n ee, 56 log, 103 log10, 103 logarithme, 103 long double, 17 long int, 16 Lvalue, 43 macro, 78 main, 13, 67 arguments, 68 type, 67 make, 93 Makefile, 93 malloc, 47, 104 math.h, 103 membre de structure, 37 mot-clef, 12 multiplication (*), 20 n egation logique (!), 21 NULL, 47

131

op erateurs, 1925 adresse, 24, 31 aectation compos ee, 22 arithm etiques, 20 bit-` a-bit, 22 conditionnel ternaire, 23 conversion de type, 24 incr ementation, 23 indirection (*), 44 logiques, 21 membre de structure (.), 38 pointeur de membre de structure (->), 55 priorit es, 24 relationnels, 21 virgule, 23 ou exclusif (^), 22 ou inclusif (|), 22 ou logique (||), 21 ouverture de chier, 81 param` etres

132 eectifs, 59, 60 formels, 59 partie enti` ere inf erieure (floor), 103 sup erieure (ceil), 103 pointeurs, 4357 allocation dynamique, 47, 104 arithm etique, 46 d eclaration, 44 fonction, 69 indirection (*), 44 initialisation, 47 NULL, 47 structures, 54 tableaux, 50 transmission de param` etres, 65 pow, 103 pr eprocesseur, 9, 7780 #define, 77 #elif, 79 #else, 79 #endif, 79 #if, 79 #ifdef, 80 #ifndef, 80 #include, 61, 77 compilation conditionnelle, 79 printf, 29, 99 priorit es des op erateurs, 24 proc edure, 59 prototype, 60 puissance, 20, 103 putc, 83, 100 putchar, 32, 100 puts, 100 qsort, 71 racine carr ee (sqrt), 103 rand, 104 RAND MAX, 104 realloc, 104 register, 62 reste de la division (%), 20 reste de la division (fmod), 103 return, 59 rewind, 87 scanf, 31, 99 short, 16 signed, 16 sin, 103 sinh, 103 sinus (sin), 103 sinus hyperbolique (sinh), 103 sizeof, 17 size t, 71 soustraction (-), 20 sprintf, 99 sqrt, 103 srand, 104 sscanf, 99 static, 62, 64 stdarg.h, 74 stddef.h, 71 stderr, 82 stdin, 82 stdio.h, 99 stdlib.h, 104 stdout, 82 strcat, 102 strchr, 102 strcmp, 102 strcpy, 102 string.h, 102 strlen, 53, 102 strncat, 102 strncmp, 102 strncpy, 102 strrchr, 102 strstr, 102 struct, 37 structure, 3739 autor ef erenc ee, 56 initialisation, 38 pointeur, 54 switch, 25 tableau, 3537, 50 initialisation, 36 pointeurs, 50 tan, 103 tangente (tan), 103 tangente hyperbolique (tanh), 103 tanh, 103

Index

Index time, 106 time.h, 106 tolower, 101 toupper, 101 transmission de param` etres, 64 tri (qsort), 71 typedef, 41 types caract` ere, 14 compos es, 3541 d enition (typedef), 41 entiers, 16 FILE *, 81 ottants, 17 pr ed enis, 1417 qualicateurs, 66 ungetc, 84 union, 39 unsigned, 16 va arg, 74 va end, 74 valeur absolue abs, 104 fabs, 103 labs, 104 va list, 74 variable automatique, 62 classe de m emorisation, 6163 constante, 66 dur ee de vie, 61 globale, 62 locale, 63 partag ee, 93 permanente, 61 port ee, 62 statique, 61, 62 temporaire, 61 volatile, 66 va start, 74 virgule, 23 void, 59 volatile, 66 while, 26

133

Vous aimerez peut-être aussi