Vous êtes sur la page 1sur 127

Langages et Concepts de Programmation Structures de donnes et algorithmes en C

Cours 1A 2011-2012

Jean-Jacques Girardot, Marc Roelens


girardot@emse.fr, roelens@emse.fr Octobre 2011

cole Nationale Suprieure des Mines de Saint-Etienne 158 Cours Fauriel 42023 SAINT-TIENNE CEDEX

2 Version du 6 octobre 2011.

Premire partie Cours

Introduction
Avant-Propos
Ce cours [2] fait suite au cours dIntroduction lInformatique [1]. Comme le prcdent, ce cours se veut aussi bien une continuation de lapprentissage de lalgorithmique et de la programmation, quune poursuite de ltude du langage C. En parallle ce cours, les lves ont raliser un mini-projet de programmation, destin mettre en uvre de faon pratique les concepts et techniques abordes dans le cours. Dans le cadre du ple de modlisation mathmatique, les lves suivent en parallle le cours de Recherche oprationnelle. Ce cours se termine par deux sances de travaux pratiques, consistant implmenter grce aux comptences acquises dans le prsent cours un algorithme de rsolution dun problme classique de recherche oprationnelle.

Droulement du cours
Sances 1 et 2
But Savoir crire des programmes accdant leurs donnes en-dehors du programme luimme. Thorie La reprsentation externe des donnes. Entres-sorties en C. Fichiers texte et chiers binaires. Pratique Lire des donnes simples (entiers, ottants, chanes de caractres), des tableaux. Formater des donnes en sortie, lire des donnes formates en entre.

Sances 3 et 4
But Savoir manipuler des structures de donnes du langage C. Thorie Notion denregistrement (regroupement de donnes de types diffrents), champs dun enregistrement. Structures de donnes comme paramtres ou rsultat de procdures. 5

6 Pratique Construire une structure de donnes adapte un problme pos. Spcier les procdures de manipulation de cette structure de donnes. Implmenter ces procdures.

Sances 5, 6 et 7
But Initiation au traitement des listes Thorie Algorithmes sur listes. Allocation dynamique de structures de donnes, pointeurs. Pratique Programmes de recherche en liste, dinterclassement, de tri de listes.

Chapitre 1 Entres-sorties en C
1.1 Cours

Ce cours va nous permettre daborder les entres-sorties, qui sont les oprations permettant nos programmes de communiquer avec le monde extrieur. Dans ces oprations, nous nous intresserons entre autres celles qui permettent de lire et dcrire des donnes sur les chiers. Enn, nous proterons de loccasion pour introduire le traitement des erreurs lexcution dun programme au travers de la variable globale errno et de la procdure de bibliothque perror.

1.1.1

Introduction

Les donnes que nous avons manipules jusqu prsent rsidaient uniquement en mmoire centrale. Cres par le programme en cours dexcution, elles taient traites, ventuellement imprimes, puis disparaissaient lorsque le programme se terminait. Certaines donnes informatiques, une fois cres, doivent au contraire tre conserves pour une utilisation ultrieure : un jeu se souvient des high scores et des noms de joueurs, les donnes comptables dune entreprise sont prserves pendant des annes, les pages du web sont gardes pour pouvoir tre fournies la demande, etc. Nous avons dj dcrit brivement la structure des systmes de chiers, et la dsignation des chiers eux-mmes. Nous avons utilis jusqu prsent ces chiers pour la seule reprsentation des programmes, quils soient en source, objet, ou excutables. Nous allons aborder maintenant lutilisation des chiers pour la reprsentation et la conservation des donnes manipules par les programmes.

1.1.2

Reprsentation des donnes dans un chier

Il faut bien distinguer ce niveau deux reprsentations diffrentes des donnes de nos programmes : 7

CHAPITRE 1. ENTRES-SORTIES EN C

la reprsentation interne, cest--dire la forme sous laquelle ces donnes sont manipules par le programme lui-mme ; cette reprsentation est donc totalement dpendante du langage de programmation ; la reprsentation externe, cest--dire le codage de ces donnes dans le but de les conserver dans un chier ; cette reprsentation peut dpendre des moyens fournis par le systme dexploitation. Nous avons vu, par exemple, que les donnes de nos programmes taient accessibles sous forme de variables, ayant chacune un nom et un type. Ces dclarations de variables sont partie intgrante du langage de programmation, et le programmeur na donc que peu de choix (divers formats dentiers et de nombres ottants, noms des variables). A contrario, le langage C ne connat pas la notion de chier (ce nest pas une notion propre au langage) ; les donnes stockes dans un chier externe nont aucun format impos par le langage lui-mme : il est de la responsabilit totale du programmeur de prciser sous quel(s) format(s) ses donnes sont reprsentes dans les chiers externes. Ces donnes utiles pour un programme peuvent ainsi tre reprsentes, sous une forme ventuellement diffrente (compacte, code. . .) de celle qui est utilise par le programme. Cest toujours au programmeur de garantir que lutilisation de ces donnes se fait bon escient ! On rappelle quau niveau du systme dexploitation, un chier est une simple suite doctets, en gnral physiquement enregistrs sur des supports magntiques, opto-lectroniques, etc. Ces chiers ont un nom externe (et une position : les rpertoires), des proprits (ou attributs : peut-on le lire, le modier ? quand a-t-il t cr, etc) et un contenu, les donnes elles-mmes. Sous cette forme, les donnes ne sont pas directement accessibles par le processeur. Le systme dexploitation permet, grce aux primitives dentres-sorties, davoir accs un chier, den lire et ventuellement den modier le contenu, et de librer laccs ce chier, an quil puisse tre utilis par dautres programmes.

1.1.3

Bibliothque standard des entres-sorties

Ainsi que cela a t indiqu, le langage C ne contient pas la notion de chier (rappelons que le langage C est un langage simple, voire simpliste. . . mais nanmoins extrmement puissant). Cependant, comme ce sont des objets incontournables pour bien des programmes, on a dni pour ce langage des bibliothques de structures de donnes et de procdures permettant de raliser les procdures dentres-sorties : cette bibliothque, standardise, est souvent appele bibliothque standard des entres-sorties ou encore STanDard Input-Output library, do le nom du chier den-tte contenant les structures de donnes et prototypes des procdures, savoir le chier stdio.h dj maintes fois signal. Il serait possible tout un chacun de ne pas vouloir utiliser cette bibliothque standard des entres-sorties, mais on perdrait alors tout avantage de la standardisation : universalit, performance prouve. . . Remarque : lun des avantages de cette bibliothque est aussi le fait que le langage C saffranchit presque totalement des problmes de portabilit entre systmes dexploitation diffrents

1.1. COURS

(Windows et Unix, notamment). On trouvera ainsi des versions spcialises de la bibliothque sur chaque environnement utilis.

1.1.4

Dnition des ots

Les mcanismes dentres-sorties en C font appel la notion de ot. Un ot est une squence doctets, accessibles squentiellement, qui peut tre lue et/ou crite par le programme. Les programmes C manipulent en particulier : un ot dentre, dit entre standard, accessible par lintermdiaire dun objet nomm stdin ; un ot de sortie, dit sortie standard, accessible par lintermdiaire dun objet nomm stdout ; un ot de gestion des erreurs, accessible par lintermdiaire dun objet nomm stderr. Ces trois ots sont habituellement associs au terminal (clavier, cran) de lutilisateur. Par exemple, la procdure printf dj prsente ralise toutes ses sorties sur le ot stdout. Les objets stdin, stdout et stderr sont dclars dans le chier dinclusion stdio.h, que nous connaissons bien. Les dclarations prcises de ces objets sont : extern FILE *stdin; extern FILE *stdout; extern FILE *stderr; Le mot-clef extern indique que ces donnes ne sont pas dnies dans le programme source lui-mme, mais dans un module externe (la bibliothque standard du C), qui sera inclus lors de la phase ddition des liens. Les objets sont de type FILE *, ce qui veut dire quils sont des adresses (ltoile) de structures spciques (de nom FILE) dcrivant les caractristiques des ots correspondants. Nous navons pas besoin de savoir prcisment ce que contiennent ces structures, car le seul traitement que nous leur appliquerons sera de les utiliser comme paramtres de procdures dentres-sorties qui, elles, connaissent lesdites structures. Retenons simplement que, comme toute adresse, ladresse 0 dnie dans stdio.h par la macro-dnition NULL, reprsente une adresse incorrecte, et sera toujours utilise lorsquune procdure de manipulation de chier voudra indiquer quelle a chou. Outre ces ots standard , nous allons voir que des mcanismes du systme permettent daccder, sous la forme de ots, au contenu des chiers grs par le systme dexploitation.

1.1.5
1.1.5.1

Flots et chiers
Cration dun ot partir dun chier

La construction dun ot partir dun chier externe se fait par la procdure fopen(3) 1 . On notera que dans la suite, on parlera douverture de chier pour dsigner la construction dun
1. Cette notation particulire indique que la procdure fopen fait partie de la section 3 du manuel en ligne : la section 3 contient en fait toutes les bibliothques de procdures, et en particulier les oprations dentres-sorties du C. On peut consulter la documentation, sous Linux, par la commande man 3 fopen

10

CHAPITRE 1. ENTRES-SORTIES EN C

ot partir dun chier (le nom fopen drivant de le open, soit ouverture de chier). Le prototype de cette procdure fopen, dni dans le traditionnel chier stdio.h, est : FILE *fopen (char nom[],char mode[]); Le rsultat de cette procdure fopen est ladresse dun objet de type FILE qui dsigne le ot cr. En cas derreur, le rsultat est ladresse 0, ou NULL. Le premier paramtre nom indique le nom externe du chier sous forme de chane de caractres, qui peut reprsenter un nom absolu comme /etc/passwd, ou relatif comme toto.data ou ../toto.out. Le second paramtre indique la nature du ot que lon veut construire ; cest galement une chane de caractres, qui peut prendre les valeurs et signications suivantes : "r" le ot est ouvert en lecture seulement ; il y a erreur si le chier nexiste pas ou nest pas accessible ; "w" le ot est ouvert en criture seulement ; si le chier existe dj, son contenu est pralablement dtruit ; le chier est cr sil nexiste pas dj ; il y a erreur si le chier ne peut tre cr, ou si son contenu ne peut tre modi par lutilisateur ; "a" le ot est ouvert en criture seulement, mais le contenu est conserv ; toutes les critures sont faites en n de chier (le a signie append) ; il y a erreur si le chier ne peut tre modi par lutilisateur ; "r+" le ot est ouvert en lecture et criture, son contenu nest pas modi ; il y a erreur si le chier nexiste pas, nest pas accessible ou nest pas modiable ; "w+" le ot est ouvert en lecture et criture, son contenu est pralablement dtruit ; le chier est cr sil nexiste pas pralablement ; il y a erreur si le chier nest pas accessible, nest pas modiable ou ne peut tre cr ; "a+" le ot est ouvert en lecture et criture, son contenu initial est conserv, toutes les critures sont faites la n du chier ; il y a erreur si le chier nest pas accessible, ou ne peut tre cr ; 1.1.5.2 Destruction dun ot

Lorsque le programme na plus besoin dun ot, il peut en demander la destruction explicite par la procdure : int fclose (FILE *pf); le rsultat de la procdure tant 0 si tout se passe bien, ou -1 (encore dni dans stdio.h par la macro-dnition EOF pour End Of File) en cas de problme. Remarque 1 : la destruction dun ot nimplique pas celle du chier associ, mais simplement la libration des structures de donnes du programme permettant daccder cette ressource ; Remarque 2 : tout ot ncessite des ressources systmes pour accder un chier, et ces ressources sont en nombre limit. On a donc tout intrt fermer explicitement tout ot devenu inutile !

1.1. COURS

11

Remarque 3 : bien que non construits explicitement (par le programmeur) par des appels fopen, les ots prdnis stdin, stdout et stderr peuvent galement tre ferms sils ne sont pas utiles grce la procdure fclose. Remarque 4 : lors de la n de vie normale du programme (en particulier en cas dappel de la procdure exit), ou dune n de vie anormale (plantage du programme), les ots sont implicitement dtruits. Cependant, il est de bonne pratique de les dtruire explicitement sils ne sont plus utiles. 1.1.5.3 Rouverture dun ot

On dispose dun autre appel de re-cration de ot : FILE *freopen (char nom[],char mode[],FILE *old); Lors de cet appel, le ot dsign par old est pralablement ferm, et le chier dsign par nom est ouvert selon le mode mode en lieu et place de lancien ot old. Comme fopen, cette procdure rend la valeur NULL en cas derreur. Voici un exemple dutilisation de cette procdure : if (freopen("toto.out","w",stdout)==NULL) { perror("toto.out"); exit(2); } printf("Hello world\n"); ... Lappel freopen r-ouvre le ot stdout en criture sur le chier toto.out, et lappel ultrieur la procdure printf crira donc le message dans le chier toto.out et non pas sur lcran. Remarque importante : prdnis ; lcriture lexemple ci-dessus donne la seule bonne faon de modier les ots

stdout=fopen("toto.out","w"); est totalement bannir, et certains environnements la refusent carrment (pour des raisons trop complexes pour tre dtailles ici). Enn, pour le coin de la culture, il existe galement une procdure fdopen, souvent documente dans la mme page de manuel que fopen et freopen, dont le fonctionnement dpasse encore le niveau de ce cours dintroduction.

12

CHAPITRE 1. ENTRES-SORTIES EN C

1.1.6
1.1.6.1

Quelques proprits des ots


Unit dchange avec un ot

Comme cela a t indiqu, lunit lmentaire dchange dinformation dans un ot est le caractre, ou plus prcisment encore loctet. Ceci implique en particulier que lopration lmentaire sur un ot dentre consiste lire un octet, et que lopration lmentaire sur un ot de sortie consiste crire un octet. Nous verrons que nous disposons doprations toutefois plus complexes, permettant dchanger plus dun octet. 1.1.6.2 Accs squentiel

Comme son nom le suggre, un ot reprsente un mcanisme de producteur-consommateur : un octet lu dans un ot est consomm par le programme et disparat donc du ot, ce qui signie que si lon demande lire nouveau un octet, ce sera loctet suivant du ot qui sera fourni. Cela signie aussi que le programme doit mmoriser (dans une variable par exemple) tout octet lu sil veut lutiliser ultrieurement. De la mme faon, lcriture dun octet dans un ot, cet octet est crit la n du ot, et sajoute donc aux octets prcdemment crits. En entre comme en sortie, lorsque le ot est construit partir dun chier, le systme gre une position courante dans le chier, qui indique lendroit du chier o sera lu ou crit le prochain octet. Attention : lorsquun ot est utilis simultanment en lecture et en criture, le systme ne mmorise quune seule position ! Cest donc au programmeur de savoir parfaitement o il en est, an de ne pas lire ou crire des donnes des endroits non convenables du chier. Bien sr, lorsque quun ot est construit en lecture seulement partir dun chier externe, le chier lui-mme nest pas modi. On verra que les procdures de lecture et dcriture dans les ots sont capables de gnrer des codes derreur : sur un ot en lecture, cette erreur correspond en gnral la n du ot ; sur un ot en criture, cette erreur se produit en gnral lorsque le ot est plein (par exemple, lorsquil nest plus possible daccrotre la taille du chier sous-jacent par manque de place dans le systme de chiers).

1.1.7

Oprations dcriture dans un ot

La bibliothque standard du langage C propose de nombreuses oprations dcriture de caractres ou de chanes de caractres, faisant rfrence implicitement ou explicitement des ots. La table 1.1 dcrit quelques oprations dcriture du langage. Quelques remarques gnrales sur ces procdures : ces procdures sont en gnral utilises en mode texte : ce sont des caractres dit imprimables en code ASCII qui sont transfrs vers le ot ; les procdures ayant un paramtre de type ot (celles qui commencent par f plus putc) crivent sur ce ot ; les autres crivent sur le ot stdout ;

1.1. COURS Nom Prototype fprintf int fprintf(FILE *f, const char *d, ...) fputc int fputc(int c, FILE *f) Description criture formate sur le ot f criture du caractre c sur le ot f fputs int fputs(const char *s, criture de la chane s FILE *f) sur le ot f printf int printf(const char *d, criture formate ...) sur stdout putc int putc(int c, FILE *f) criture du caractre c sur le ot f putchar int putchar(int c) criture du caractre c sur stdout puts int puts(const char *s) criture de la chane s sur stdout sprintf int sprintf(char *s, criture formate const char *d, ...) sur la chane s TABLE 1.1 Quelques oprations de sortie de C

13

ces procdures retournent un rsultat de type entier ; un rsultat gal -1 (macro EOF dnie dans stdio.h) indique une erreur : ot non ouvert en criture, plus de place pour ajouter les donnes dans le ot ; 1.1.7.1 criture de caractres

Les procdures putc, putchar et fputc permettent dcrire un caractre (c) dans un ot f (ou stdout par dfaut pour putchar). Remarquer que le caractre est transfr sous forme dun entier : ces procdures effectuent une opration de modulo pour obtenir un caractre dans lintervalle de 0 255. Le rsultat en cas de succs de ces procdures est le caractre transmis. On peut tester ce rsultat pour dtecter une erreur : if (putc(X,f)==EOF) { perror("putc a echoue"); ... Notes : les oprations putc et fputc ont un effet strictement identique ; putc est en fait une macro, il faut donc prendre garde des critures comme putc(i++,f) ! putchar est galement une macro, un appel putchar(c) correspondant putc(c,stdout).

14 1.1.7.2 criture de chanes

CHAPITRE 1. ENTRES-SORTIES EN C

Lopration puts permet dcrire une chane de caractres complte sur la sortie standard stdout. La procdure fputs permet dcrire une chane sur un ot pass en paramtre. Notes : puts et fputs retournent comme rsultat le nombre de caractres crits (et EOF en cas derreur) ; lopration puts ajoute un passage la ligne aprs impression de son paramtre ; ce nest pas le cas pour fputs. 1.1.7.3 criture formate

Nous avons dj utilis plusieurs reprises lopration printf qui permet deffectuer des sorties sur la sortie standard, qui est habituellement la fentre servant de terminal lutilisateur. Cette procdure a la syntaxe suivante : printf(format, exp1, exp2, exp3. . .) La spcication de format est une chane de caractres. Le contenu de cette chane est recopie sur la sortie standard, caractre par caractre, sauf lorsque le caractre est un %. Dans ce cas, le ou les caractres qui suivent sont des descriptions ddition : %d pour afcher un nombre entier sous forme dcimale ; %c pour afcher un nombre entier sous forme caractre ; %x pour afcher un nombre entier sous forme hexadcimale ; %s pour afcher une chane de caractres ; %f pour afcher un nombre ottant en virgule xe ; %e pour afcher un nombre ottant en virgule ottante, avec un exposant ; %g pour afcher un nombre ottant en choisissant la meilleure reprsentation possible (une des deux prcdentes) ; %% enn, permet lcriture du caractre %. Les directives %d, %c et %x attendent des donnes de type entier (valeurs dclares en char, int et long), qui sont converties au format long lorsquelles sont transmises comme paramtre. Les directives %f et %e attendent des donnes de type ottant (valeurs dclares en float et double), qui sont converties au format double lorsquelles sont transmises comme paramtre. Plusieurs descriptions ddition peuvent gurer dans le mme format, qui vont correspondre aux paramtres expi. Cest une erreur que davoir un nombre diffrent de descriptions de format et de paramtres. Quelques exemples dutilisation : le mme nombre, reprsentant le code ASCII du caractre A, est imprim sous forme caractre, dcimale et hexadcimale ; printf("%c %d %x\n",65,65,65); ce qui donne, lexcution : A 65 41 deux impressions, selon des formats diffrents, du mme nombre ottant ;

1.1. COURS printf("%f %e\n",543.21,543.21); ce qui donne, lexcution : 543.210000 5.432100e+02 Entre le caractre % et le type ddition (cdefsx, etc), on peut trouver : des indicateurs, dont voici certains :

15

0 indique que le champ ddition doit tre complt par des 0 plutt que par des blancs ; - indique que la valeur dite doit sappuyer gauche, et non droite ; (un blanc) indique de laisser un blanc devant un nombre positif ; + indique quun nombre positif doit tre dit prcd dun signe +. une taille ddition minimale ; une prcision (un nombre, prcd dun point dcimal). un modieur de longueur (caractre h, l, etc.), permettant de spcier la longueur (court, long) du nombre convertir. Exemples de formats et des sorties correspondantes : lappel printf("*%4d*%4d*\n",33,-33); donne lexcution : * 33* -33* lappel printf("*%4d*%04d*\n",33,33); donne lexcution : * 33*0033* lappel printf("*%-4d*%-4d*\n",33,-33); donne lexcution : *33 *-33 *~ lappel printf("*%+4d*%+4d*\n",33,-33); donne lexcution : * +33* -33* lappel printf("*%8.2f*%8.4f*\n",1.531,1.531); donne lexcution : 1.53* 1.5310* * Notes : la procdure printf crit sur la sortie standard stdout ; la procdure fprintf a un comportement identique, mais crit sur le ot pass en premier argument ; lappel la procdure printf(format,...) est donc quivalent lappel fprintf(stdout,format,...) ; les oprations fprintf, printf et sprintf, fournissent un rsultat qui est le nombre de caractres effectivement gnrs dans le ot de sortie, ou -1 (EOF) en cas derreur ;

16 Nom fgetc fgets fscanf getc Prototype int fgetc(FILE *f)

CHAPITRE 1. ENTRES-SORTIES EN C Description Lecture dun caractre sur le ot f Lecture dune chane sur le ot f Lecture formate sur le ot f Lecture dun caractre sur le ot f Lecture dun caractre sur stdin Lecture dune chane sur stdin Lecture formate sur stdin Lecture formate depuis une chane Rejet dun caractre lu

char *fgets(char *s, int l,FILE *f) int fscanf(FILE *f, const char *d, ...) int getc(FILE *f)

getchar int getchar(void) gets scanf sscanf ungetc char *gets(char *s) int scanf(const char *d, ...) int sscanf(const char *s, const char *d, ...) int ungetc(int c, FILE *f)

TABLE 1.2 Procdures dentre sur ots le prototype des procdures printf, fprintf et sprintf se terminent par ... : ceci indique que le nombre darguments de ces procdures est variable (il dpend du nombre de paramtres imprimer), et que le compilateur ne doit pas soffusquer de cela ; le compilateur vrie nanmoins les premiers arguments (en nombre et en type) ; lopration sprintf, qui nest pas proprement parler une opration dentre-sortie, puisque lcriture seffectue dans un tableau de caractres dont ladresse est fournie par le programme (le premier paramtre), est dcrite ici du fait de ses similitudes avec printf et fprintf.

1.1.8

Oprations de lecture dans un ot

Tout comme le contenu dun ot de sortie peut tre gnr caractre par caractre (par une procdure comme fputc), ou au moyen dune procdure plus complexe (telle printf), un ot en entre peut tre consomm caractre par caractre, ou encore au moyen de procdures plus labores. La table 1.2 dcrit certaines des oprations sappliquant des ots en entre. Quelques remarques gnrales sur ces procdures : ces procdures de lecture sont en gnral utilises avec des chiers en mode texte, contenant des caractres imprimables ; les procdures comportant un paramtre de type ot (celles dont le nom commence par f, plus getc et ungetc) effectuent une lecture sur le ot indiqu ; les autres effectuent une lecture (implicite) sur lentre standard stdin ;

1.1. COURS

17

les procdures rendant un rsultat de type int indiquent une erreur par un rsultat gal -1 (EOF) ; les procdures gets et fgets, rendant un rsultat de type char* (une chane de caractres), indiquent une erreur par un rsultat gal 0 (NULL) ; 1.1.8.1 Lecture en mode caractre

Les procdures getc, getchar et fgetc permettent de lire un caractre dans un ot f (ou stdin par dfaut pour getchar). Le caractre lu est rendu comme rsultat de ces procdures en cas de succs : cest le code ASCII (entre 0 et 255) qui est retourn. On rappelle que la valeur -1 (EOF) est retourne en cas derreur (plus de donnes dans le ot, ot non ouvert en lecture). Notes : les oprations fgetc et getc correspondent deux ralisations diffrentes dune mme procdure ; getc est en fait une macro ; getchar est galement une macro, un appel getchar() tant fonctionnellement quivalent getc(stdin) ; on insiste sur le fait que ces trois procdures retournent une valeur de type int ! Cest donc une erreur que de stocker le rsultat dans une variable de type char : le caractre de code ASCII 255 est alors assimil lerreur -1 (EOF) ! lopration ungetc permet de rejeter un caractre, qui est rintroduit dans le ot dentre, et sera fourni nouveau lors dune nouvelle lecture de caractre. Cette opration a pour but de permettre un programme danalyser un ot qui est lu caractre par caractre par getc, puis, par exemple lorsque le premier caractre dun nombre est reconnu, de rejeter ce caractre pour effectuer une nouvelle lecture, cette fois au moyen dune procdure spcialise comme scanf. On notera que la norme du langage prcise que le caractre rejet doit tre le dernier caractre lu, et quun seul caractre peut tre ainsi rejet ; noter quil nexiste pas de macro ungetchar. . . 1.1.8.2 Lectures de chanes

Les procdures gets et fgets permettent de lire une chane de caractres dans un ot (le ot pass en argument pour fgets, stdin pour gets). La lecture dans le ot seffectue jusqu ce que : le caractre n de ligne soit lu ; il ny ait plus aucun caractre dans le ot (n de chier) ; dans le cas de fgets, on ait lu n-1 caractres. Dans tous les cas, la chane de caractres est termine par le caractre ASCII de code 0. Notes : ces deux procdures lisent des lignes , qui se terminent par un passage la ligne (format dit texte sous Windows) ;

18

CHAPITRE 1. ENTRES-SORTIES EN C lopration gets(tab) est considre comme potentiellement dangereuse 2 , car elle lit une chane de caractres (qui peut tre de trs grande taille) et la recopie dans un tableau, tab avec un risque de dbordement du tableau. On lui prfrera une expression telle que fgets(tab,sz,stdin) qui permet de spcier la taille sz du tableau pass en paramtre.

1.1.8.3

Lecture formate

La procdure scanf permet de lire le ot dentre stdin, en dcodant les octets de ce ot selon des spcications dcrites par un format. Ces spcications sont trs voisines de celles qui sont utilises par printf. La syntaxe de lopration est : scanf(format, ...) Un format est une chane de caractres, qui va tre compare avec les caractres lus sur le ot dentre de la faon suivante : un blanc dans le format accepte un nombre arbitraire, ventuellement nul, de blancs en entre. Les blancs sont lespace proprement dit, de code ASCII 32, le passage la ligne, de code 10, le caractre de tabulation, de code 8, etc. un caractre standard accepte un caractre identique du ot dentre. un caractre % introduit une description de lecture. Voici quelques exemples : %d attend un entier au format dcimal ventuellement prcd de blancs, puis dun signe. %x attend un entier au format hexadcimal ( chiffres de 0 9 et A F). %c attend un caractre unique ; le rsultat est le code ASCII du caractre. %e ou %f attendent un ottant, ventuellement prcd de blancs. Le nombre peut tre reprsent avec signe, partie entire, dcimale et exposant. %s attend une squence de caractres non blancs ; la lecture sarrte au premier blanc rencontr. %[liste-de-caractres] attend une squence compose des caractres de la liste. %% attend un caractre %. Lorsquune spcication %d, %x, %c, %e, %f, etc. a dcod un nombre, le rsultat est plac dans la cellule mmoire dont ladresse a t fournie comme paramtre complmentaire de scanf : cette cellule mmoire peut tre ladresse dune variable, dun lment de tableau, dun champ de structure. . . Il faut fournir une telle adresse pour chaque description de lecture du format. Si la variable est de type long, et non int, la spcication de conversion doit scrire %ld ou %lx. Si la variable est de type short, et non int, la spcication de conversion doit scrire %hd ou %hx. Si la variable est de type double, et non float, la spcication de conversion doit scrire %le ou %lf. Le rsultat de la procdure est un entier indiquant le nombre effectif de variables qui ont pu tre lues par le programme, ou EOF en cas derreur de lecture, de n de chier rencontre, etc.
2. elle a donn lieu beaucoup de failles de scurit

1.1. COURS Exemple Le programme suivant montre la procdure en action : #include <stdio.h> int main (int argc,char *argv[]) { int n,v,w,x; v=w=x=-1; n=scanf("a %d b %d c %d d",&v,&w,&x); printf("n=%d,v=%d,w=%d,x=%d\n",n,v,w,x); return 0; }

19

Voici quelques exemples dexcution, lentre fournie au programme prcdant la rponse de celui-ci : $ prog a 333 x 222 u 444 n=1,v=333,w=-1,x=-1 $ prog a55b66 c 222d n=3,v=55,w=66,x=222 $ prog u23 n=0,v=-1,w=-1,x=-1 $ prog a 0x2F4 b 333 c 444 w n=1,v=0,w=-1,x=-1 Notes : la procdure fscanf permet doprer des lectures depuis un ot dont la rfrence est donne comme premier paramtre (c.f. lexemple du paragraphe 1.1.11, page 22) ; elle a un comportement identique celui de scanf ; un appel scanf(...) quivaut fscanf(stdin,...) ; la procdure sscanf permet la lecture formate partir dune chane de caractres, et donc linterprtation dune suite de caractres en tant que nombre : sscanf("743.25","%f",&v); affecte la valeur ottante 743.25 la variable v (dclare en float) ; les trois procdures scanf, fscanf et sscanf rendent un rsultat qui est le nombre de paramtres correctement lus (attention : ce nest pas le nombre de caractres lus !), ou -1 (EOF) en cas derreur. on notera que les prototypes de ces trois procdures se terminent par ..., ce qui indique que le nombre darguments est variable : il dpend en effet du nombre de paramtres lire.

20

CHAPITRE 1. ENTRES-SORTIES EN C

1.1.9

Un exemple : head

On se propose dcrire le programme head , qui lit les premires lignes (au plus 10) de lentre standard, et les crit sur la sortie standard 3 . Voici le programme source : #include <stdio.h> #define SIZE 4000 int main (int argc,char *argv[]) { int i; char buffer[SIZE]; char *res; for (i=0;i<10; i++) { res=fgets(buffer,SIZE,stdin); if (res==NULL) break; else fputs(buffer,stdout); } } Notes nous utilisons ici une facilit du prprocesseur pour dnir une macro : #define SIZE 4000 Cette ligne associe la variable du prprocesseur SIZE la suite des 4 caractres 4000 . Chaque fois que le prprocesseur rencontre cette variable dans le texte source sur programme, ce quil fait deux reprises, il la remplace par sa valeur, les 4 caractres 4000 . Tous se passe comme si lon avait crit : char buffer[4000]; et res=fgets(buffer,4000,stdin); aux deux endroits o cette variable est utilise. Cette technique simplie la maintenance dun programme, en permettant, comme ici, de rendre cohrentes dclarations et utilisations. de mme, la variable du prprocesseur NULL, est utilise ici pour tester la valeur rendue par la procdure fgets() (cette variable est dnie dans le chier stdio.h). on a utilis la procdure fgets permettant de lire (de faon plus sre que gets) des lignes dans le ot dentre ; on pourra tudier, titre dexercice, ce qui se passe si lune des lignes dentre contient plus de SIZE caractres.
3. voir le manuel man head

1.1. COURS

21

1.1.10

Autres procdures

Nous allons prsenter ici quelques procdures supplmentaires lies aux mcanismes dentres-sorties. 1.1.10.1 Gestion des erreurs

Les entres-sorties sont une source frquente derreurs. An de pouvoir prciser quelle type derreur est intervenue, la bibliothque standard du C offre les mcanismes suivants : une variable globale nomme errno, qui contient un numro reprsentant le type de la dernire erreur intervenue ; une procdure perror(3), qui est destine imprimer sur le ot de gestion des erreurs un message reprsentatif (en clair) de la dernire erreur intervenue. noter que lutilisation de la variable externe errno ncessite linclusion du chier den-tte errno.h. noter galement que la variable errno ne concerne pas uniquement les entressorties, mais toutes les procdures de bibliothque du C, ainsi que les appels aux procdures du systme. On trouvera ci-dessous quelques macro-dnitions de numros derreurs, avec le message (anglais) associ ainsi quune traduction approximative : Erreur E2BIG Signication la liste des arguments du programme est trop longue EBADF Bad le descriptor descripteur de ot incorrect EEXIST File already exists le chier existe dj EIO I/O error erreur dentre-sortie ENOENT No such le or directory le chier ou le rpertoire indiqu nexiste pas EPERM Permission denied le programme na pas les droits requis pour lopration demande EROFS Read-only lesystem le systme de chiers est en lecture seule La liste complte des codes derreur est accessible dans la page de manuel nomm intro de la section 2, et est donc consultable par la commande : man 2 intro Le prototype de la procdure perror est le suivant : void perror (char *msg); La procdure perror se comporte comme suit : si ladresse passe en argument nest pas nulle, et ne dsigne pas une chane de caractres ne contenant que le caractre 0 (chane vide), la chane de caractres dsigne est imprime telle quelle (sans formatage) suivie dun caractre : sur le ot de gestion des erreurs (stderr) ; un message descriptif de la dernire erreur rencontre est ensuite imprim, suivi dun saut de ligne, toujours sur stderr. Message Arg list too long

22

CHAPITRE 1. ENTRES-SORTIES EN C

Note importante : la variable errno nest pas modie par un appel qui russit, cest donc bien toujours la dernire erreur rencontre par le programme qui est imprime. 1.1.10.2 Autres procdures de manipulation de ots

On dispose des procdures suivantes : int feof (FILE *pf); retourne une valeur non nulle (vraie) lorsquune n de chier a t dtecte sur le ot pf. Attention : cet indicateur devient non nul aprs quun essai de lecture infructueux a t fait ! int ferror (FILE *pf); retourne une valeur non nulle (vraie) lorsquune erreur a t dtecte sur le ot pf. Attention : comme pour le prcdent, cet indicateur devient non nul aprs quune erreur a t effectivement dtecte ! void clearerr (FILE *pf); remet zro (faux) les deux indicateurs de n de chier et derreur sur le ot pf. 1.1.10.3 Autres procdures de manipulations de chiers

On dispose dans la bibliothque standard du C de procdures permettant daccder au systme de gestion des chiers, de faon indpendante du systme sous-jacent. Voici quelques-unes de ces procdures : int remove (char *nom); dtruit le chier dont le nom est pass en argument, rend 0 si le chier a t dtruit, -1 sinon (erreur dans errno) ; int rename (char *old,char *new); renomme le nom du chier old sous le nom new, rend 0 si lappel a russi, -1 sinon (erreur dans errno) ; FILE *tmpfile (void); cre un ot vers un chier temporaire en mode lecture et criture, le chier est dtruit lorsque le ot est ferm ; lappel rend NULL sil choue (erreur dans errno).

1.1.11

Un exemple : high scores

On veut, dans un programme, rcuprer dans un chier de nom scores deux valeurs, un entier et un nom. Le format du chier scores est le suivant : un entier sous forme dcimale, un caractre * servant de sparateur, et une suite de caractres constituant le nom. Le nom est compos de lettres, chiffres, et espaces. Voici un exemple de contenu du chier : 623147*The terminator Voici un exemple de programme ralisant les oprations dsires : #include <stdio.h> int main (int argc,char *argv[]) { int high; char ident[128];

1.1. COURS FILE *hs; int cr; hs=fopen("scores","r"); if (hs==NULL) { perror("Unable to open the \"scores\" file"); return 1; } cr=fscanf(hs,"%d*%[0-9a-zA-Z ]",&high,ident); if (cr!=2) { fprintf(stderr,"Incorrect \"scores\" file format\n"); return 1; } printf("Best score : %s : %d\n",ident,high); fclose(hs); return 0; }

23

On note : le format utilis dans fscanf [0-9a-zA-Z ] sinterprte comme : suite ventuellement nulle de caractres appartenant lintervalle (ASCII) de 0 9 (chiffres dcimaux), ou de a z (lettres minuscules) ou de A Z (lettres majuscules), ou encore espace si fscanf ne rend pas la valeur attendue (2 car 2 variables sont lues en cas de fonctionnement normal), on nutilise pas la procdure perror : cest une erreur de format du chier, pas une erreur du systme dentres-sorties.

1.1.12

Un exemple, head, 2

On se propose de modier le programme head pour quil travaille sur un chier dont le nom lui est transmis comme paramtre, au lieu de stdin. #include <stdio.h> #define SIZE 4000 int main (int argc,char *argv[]) { char buffer[SIZE]; /* la macro FILENAME_MAX est definie dans stdio.h */ /* elle donne le nombre max de caracteres dun */ /* "pathname", c-a-d le nom absolu dun fichier */ char name[FILENAME_MAX]; FILE *hs;

24 int i;char *res;

CHAPITRE 1. ENTRES-SORTIES EN C

/* si on na pas donne dargument, on stoppe ! */ if (argc != 2) { printf("File name missing\n"); return 2; } /* on recopie le nom du fichier, en ne prenant pas */ /* plus que les FILENAME_MAX-1 premiers caracteres */ strncpy(name,argv[1],FILENAME_MAX-1); /* on noublie pas le \0 final ! */ name[FILENAME_MAX-1]=0; /* on ouvre le fichier "name" en lecture seule ; en */ /* cas derreur, on affiche un message et on stoppe */ hs=fopen(name,"r"); if (hs==NULL) { perror(name); return 1; } for (i=0; i<10; i++) { res=fgets(buffer,SIZE,hs); if (res==NULL) break; else fputs(buffer,stdout); } fclose(hs); return 0; }

1.2

Complments de cours

Le lecteur avis trouvera ici quelques complments sur le cours, rpondant des questions pouvant surgir sur le fonctionnement prcis des mcanismes dentres-sorties en C.

1.2.1
1.2.1.1

Caractres et chanes de caractres en C


Le type char

Dans le langage C, on dispose du type prdni char, que lon traduit souvent par caractre. Il faut bien comprendre que ce type permet simplement de reprsenter un entier sur 8 bits, soit

1.2. COMPLMENTS DE COURS

25

256 valeurs. En gnral, le type char est sign (codage sur 8 bits en complment 2) et permet donc de reprsenter les valeurs entires comprises entre -128 et +127. Il existe une version non-signe : cest le type unsigned char. Ce type permet de reprsenter les entiers sur 8 bits, donc les valeurs comprises entre 0 et +255. Dans certains environnements toutefois (et la norme ANSI du langage C lautorise), le type char est non-sign : les valeurs reprsentables sont ainsi les entiers compris entre 0 et +255. La norme ANSI prvoit mme un troisime cas de gure : le traitement du type char en tant que pseudo non sign (voir la norme ANSI pour plus de prcisions). 1.2.1.2 Les constantes caractres

Le langage C accepte lutilisation de constantes (entires) dnies par un caractre : dans ce cas, la valeur numrique est dnie comme tant le code ASCII de ce caractre (sous forme dun entier de type int). Ainsi, crire dans un programme : int c; c=A; est totalement quivalent crire : int c; c=65; ou encore : int c; c=\101; ou encore : int c; c=\x41; car le caractre A (a majusucule) correspond au code ASCII 65, qui scrit en octal 101 = 1 82 + 1 et en hexadcimal 41 = 4 16 + 1. En gnral, on utilise la notation octale ou hexadcimale uniquement pour les caractres non autoriss dans le code source du programme (caractres de contrle). On rappelle par ailleurs que certains caractres ont un codage spcial, introduit par le caractre backslash \ : a : alert, bip sonore ; b : backspace, espace arrire ; f : formfeed, saut de page ; n : newline, saut de ligne ; r : carriage return, retour chariot en dbut de ligne ; t : horizontal tab, tabulation horizontale ;

26 v : vertical tab, tabulation verticale ; \ : backslash ; : simple quote ; " : double quote ; ? : point dinterrogation ;

CHAPITRE 1. ENTRES-SORTIES EN C

1.2.2
1.2.2.1

Chanes de caractres
Le type chane de caractres

Pour reprsenter et stocker une squence de caractres (un mot, une phrase), le langage C ne propose pas de type de donnes particulier : il utilise simplement la notion de tableau (plus prcisment, un tableau de char) pour stocker ces squences. Une telle squence porte le nom de chane de caractres ou string en anglais. Comme le nombre de caractres contenus dans le tableau peut tre variable, le langage C utilise la convention suivante : toute chane est termine par un caractre dit nul, cest--dire de valeur 0 (le code ASCII correspondant est NUL). Ainsi, les nombreuses procdures de bibliothque qui utilisent ce type de donnes admettent des paramtres de type char* cest--dire adresse de caractre , soit en ralit adresse de premier caractre dun tableau . 1.2.2.2 Constantes chanes de caractres

Le langage C utilise une notation particulire pour les chanes de caractres : on crit la squence de caractres entre deux caractres double-quote. Par exemple : "une chaine simple" "une chaine avec un \n saut de ligne au milieu" "\001\002\003\x04\x05" Si lon a une chane de caractres assez longue, et que lon souhaite (pour des questions de lisibilit, par exemple) la fractionner sur plusieurs lignes, il faut lcrire comme suit : printf("Cette chaine de caracteres est vraiment trop " "longue pour etre ecrite sur une seule ligne \n" "et on peut donc lecrire comme ca !\n"); Il est en effet interdit dintgrer un saut de ligne lintrieur dune chane de caractres (un saut de ligne dans le code source, pas le caractre particulier \n qui est un caractre autoris, voir lexemple ci-dessus). Le compilateur traite alors cette dnition en effectuant une concatnation : il alloue un seul espace mmoire pour stocker la totalit des octets (et les caractres nuls intermdiaires sont supprims). Ces constantes peuvent tre utilises : comme valeur dinitialisation pour un tableau de caractres (dont la taille est prcise ou non) ;

1.2. COMPLMENTS DE COURS nimporte quel endroit o lon pourrait utiliser un variable de type char*. Par exemple : char tab1[10] = "coucou"; char tab2[] = "salut"; char *str="hello";

27

Dans le premier cas, le compilateur alloue une zone mmoire pour le tableau tab1 de 10 octets, le premier lment tant le caractre c (code ASCII 99), le second tant le caractre o (code ASCII 111), le sixime tant le caractre u, le septime tant le caractre NUL (valeur numrique 0), les 3 lments restants tant indtermins. Le second est peu prs identique, sauf que le compilateur dtermine lui-mme la taille du tableau allouer : cette taille comprend le caractre nul nal (donc une taille totale de 6 caractres dans cet exemple). Enn, dans le troisime cas, le compilateur alloue pour la variable str une zone mmoire permettant de stocker une adresse (4 octets sur un systme 32 bits, 8 octets sur un systme 64 bits). Il alloue ensuite une zone mmoire anonyme (aucune variable ne dsigne cette zone mmoire) de 6 octets (au moins) qui contient les caractres h, e, l, l, o et le caractre nul nal : ladresse de cette zone mmoire est alors stocke dans la variable str. 1.2.2.3 Particularits

Lorsque lon utilise une variable de type tableau de caractres, il est possible de linitialiser (lui donner une valeur au moment de sa dclaration) mais, comme pour tous les types tableaux en C, on ne peut affecter globalement un tableau. char tab[] = "coucou" ; /* initialisation : OK */ ... tab = "hello" ; /* affectation : INCORRECT */ Si lon utilise une variable de type adresse, on peut laffecter (attention : on modie bien la variable de type adresse et non pas le contenu du tableau dsign !). char *str = "coucou" ; /* initialisation : OK */ ... str = "hello" ; /* affectation : OK */ Attention toutefois : la premire zone mmoire est dnitement perdue aprs la modication de la variable str ! Attention enn aux comparaisons sur les chanes de caractres : les oprateurs arithmtiques ne peuvent comparer que les adresses et pas le contenu des zones mmoires correspondantes ! char tab1[] = "coucou" ; char *str = "coucou" ; ... if (tab1 == str) /* ce test est toujours faux */

28

CHAPITRE 1. ENTRES-SORTIES EN C ... if (tab1 == "coucou") /* ce test est toujours faux */ ... if (str == "coucou") /* ce test est faux en general */ /* mais sera parfois vrai !! */ if (strcmp(str,tab1)==0) /* ceci est la bonne facon */ /* decrire le test !! */

1.2.3

Fichiers texte et chiers binaires

Le systme dexploitation Linux, comme la quasi-totalit des Unix, ne connat quun seul type de chier : le chier dit rgulier, considr comme une succession doctets. On peut donc utiliser, sur un mme ot, aussi bien des procdures de type caractre (comme putc, getc, printf) que des procdures de type binaire (comme fread ou fwrite). Dans le cas du systme Windows, il y a distinction entre deux types de chiers : les chiers binaires, qui ressemblent aux chiers rguliers sous Linux ; les chiers texte. Un chier texte a une structure particulire : il est compos de lignes, chaque ligne tant termine par une combinaison de deux caractres, qui sont les codes ASCII 13 (Control-M ou Carriage Return ou Retour chariot) et 10 (Control-J ou Line Feed ou Saut de ligne) 4 ; il est termin, aprs la dernire ligne, par un caractre de n de chier qui est le code ASCII 26 (Control-Z ou Substitute ou Substitution) 5 . Lors de la construction dun ot par fopen sous Windows, le chier est suppos par dfaut tre de type texte. Si lon veut travailler avec un chier en mode binaire, on doit le prciser en ajoutant au mode (le deuxime paramtre de fopen) un caractre b. Voici un exemple de manipulations : FILE *fic_texte,*fic_bin; /* ouverture dun fichier texte */ fic_texte=fopen("toto.txt","r"); if (fic_texte==NULL) { perror("toto.txt"); exit(2); } /* ouverture dun fichier binaire */ fic_bin=fopen("toto.dat","wb"); ...
4. cette convention provient de la gestion des imprimantes, o le passage la ligne suivante ncessitait deux oprations : ramener le chariot portant la tte dimpression en dbut de ligne, puis faire tourner le rouleau entranant le papier dune ligne. 5. toujours sur les imprimantes, ce caractre indiquait un changement dalimentation de papier.

1.2. COMPLMENTS DE COURS

29

Note : sous Linux, le b ventuel en n de mode est tout simplement ignor. Il est recommand de manipuler les chiers binaires avec fread et fwrite (dcrites ci-aprs), et les chiers texte avec les entres-sorties caractre, chane ou formates comme fprintf, fscanf, fputs, fgets, fputc, fgetc. . . 1.2.3.1 criture en format binaire

Il est donc possible dcrire dans un ot des donnes dite binaires , car directement issues de la reprsentation interne en mmoire des donnes utilisateurs. Cette opration dcriture se fait grce la procdure fwrite dont le prototype est : size_t fwrite(void *addr,size_t sz,size_t nb,FILE *pf); Cette procdure permet dcrire dans le ot dsign par pf les donnes en mmoire partir de ladresse dsigne par addr, et reprsentant nb objets contigus, chacun tant de taille sz. Note : la syntaxe particulire void *addr permet de dsigner en C une adresse mmoire gnrique. En effet, en toute rigueur, le type adresse dpend de la nature de lobjet dsign (adresse dentier, adresse de ottant, adresse de caractre. . .) ; pour viter davoir une procdure dcriture pour chaque type dadresse, on utilise ce type gnrique qui permet dunier tous les types dadresses. Noter aussi lutilisation du type dni size_t, dsignant une taille de donne : selon les systmes, cest un entier sur 32 bits ou 64 bits, mais son nom est aussi standardis. Voici quelques exemples dutilisation de cette procdure : FILE *pf; int tab[10]; char *str="Ceci est une chaine"; /* on ouvre le fichier "toto.out" */ pf=fopen("toto.out","w"); if (pf==NULL) { perror("toto.out"); exit(2); } /* ecriture du tableau de 10 entiers */ if (fwrite(tab,sizeof(int),10,pf)!=10) { perror("fwrite"); fclose(pf); exit(2); } /* ecriture dune chaine de caracteres */ if (fwrite(str,1,strlen(str),pf)!=strlen(str)) { perror("fwrite"); fclose(pf); exit(2);

30

CHAPITRE 1. ENTRES-SORTIES EN C } /* ecriture de ladresse memoire de str */ if (fwrite(&str,sizeof(char*),1,pf)!=1) { perror("fwrite"); fclose(pf); exit(2); } ...

On note que le rsultat de la procdure fwrite est le nombre dobjets crits ; si lappel a russi, ce rsultat est donc identique au troisime argument (une diffrence indique une erreur, do les tests dans lexemple ci-dessus). 1.2.3.2 Lecture en format binaire

On dispose galement du symtrique de la procdure fwrite, appele fread. Son prototype est : size_t fread (void *addr,size_t sz,size_t nb,FILE *pf); Le rsultat rendu par la procdure est le nombre dlments effectivement lus (ce nombre est toujours positif ou nul et toujours infrieur ou gal nb). Voici quelques exemples dutilisation de cette procdure : FILE *pf; int tab[10]; char str[20]; size_t nb,strsz; double d; int i; pf=fopen("toto.dat","r"); if (pf==NULL) { perror("toto.dat"); exit(2); } nb=fread(tab,sizeof(int),10,pf); for (i=0;i<nb;i++) (void) printf("tab[%d]=%d\n",i,tab[i]); strsz=fread(str,sizeof(char),19,pf); str[strsz]=0; puts(str); if (fread(&d,sizeof(double),1,pf)==1) (void) printf("d=%f\n",d); fclose(pf); ...

1.2. COMPLMENTS DE COURS

31

Note : dans le cas dune lecture de chane de caractres avec fread, il faut bien prendre garde : lire un caractre de moins que la taille rserve pour la chane ; ajouter ensuite le 0 nal. La procdure fread transfre des octets en mmoire, et ne connat donc absolument rien de la structuration de ces octets.

1.2.4

Gros-boutistes et petits-boutistes

Les deux termes ci-dessus sont des libres traductions 6 des termes anglais little-endian et bigendian. De la mme faon que certains crivent de gauche droite alors que dautres crivent de droite gauche (ou de bas en haut), on trouve plusieurs faons de coder les entiers sur plus de 1 octet : les gros-boutistes crivent dabord les octets de poids fort (dabord reprsente ici un arrangement en mmoire) et ensuite les octets de poids faible ; les petits-boutistes crivent dabord les octets de poids faible et ensuite les octets de poids fort. Par exemple, pour coder la valeur dcimale 4219 sur 2 octets partir de ladresse mmoire N : un gros-boutiste la reprsentera par la squence 0x107B, ce qui signie encore que la cellule mmoire ladresse N contient la valeur 0x10 (en dcimal, 16=4219/256), ladresse N+1 contenant la valeur 0x7B (en dcimal, 123=4219 mod 256) ; un petit-boutiste la reprsentera par la squence 0x7B10, ce qui signie encore que la cellule mmoire ladresse N contient la valeur 0x7B, ladresse N+1 contenant la valeur 0x10. Rappelons que le processeur Intel Pentium (et toutes ses variations) est un processeur petitboutiste, et que par consquent, les systmes dexploitation Windows et Linux sur PC base de processeur Intel sont petits-boutistes. La majorit des autres processeurs, comme le Motorola PowerPC sur MacIntosh, le Sun Sparc sur stations Sun, les MIPS R10000, R12000 sur stations SGI, le HP-PA sur stations Hewlett-Packard, et donc les systmes associs, sont gros-boutistes. Avertissement : Il a t indiqu que les procdures dcriture et de lecture binaires fwrite et fread transfraient directement les reprsentations internes en mmoire : on obtient donc des rsultats diffrents selon que lon est sur un systme gros-boutiste ou un systme petit-boutiste ! En cas de transfert dun chier binaire sur un autre environnement, on nest pas du tout sr que le mme programme C, compil sous le nouvel environnement, saura relire les donnes du programme compil sous lenvironnement initial. Ceci peut donc gnrer de gros problmes de portabilit des chiers binaires entre systmes de conventions diffrentes.
6. inspires des Voyages de Gulliver de J. Swift ; certains prfrent les termes gros-boutien et petit-boutien

32

CHAPITRE 1. ENTRES-SORTIES EN C Cest pour cette raison que, si lon cherche la portabilit des programmes dvelopps, un grand soin doit tre apport la manipulation des chiers de donnes binaires (dtection du type de convention adopte, procdures adaptes).

1.2.5

Tampons en mmoire

On a dit que les ots correspondaient en gnral un chier du systme de chier. Il serait cependant trs inefcace pour le fonctionnement du programme de devoir physiquement lire ou crire sur le disque dur chaque fois que lon lit ou que lon crit un octet dans un ot : une entre-sortie physique peut prendre quelques milli-secondes, ce qui est trs long par rapport une instruction machine de quelques nano-secondes ! Pour cela, la bibliothque standard du C associe chaque ot un tampon mmoire (le terme anglais de buffer est largement plus utilis que le terme franais. . .). La plupart des entressorties consistent alors simplement crire ou lire des caractres dans le buffer, laccs physique (en lecture ou en criture) tant ralis lorsque : on ralise une opration dcriture sur le ot amenant le tampon tre plein ; on ralise une opration de lecture sur le ot amenant le tampon tre vide ; le programme le demande explicitement par un appel la procdure int fflush (FILE *pf); dautres circonstances dtailles ci-aprs. De plus, chaque ot peut avoir trois modes de fonctionnement : un mode dit bloc (ou block-buffered) ; les tampons sont de taille BUFSIZ (comme dhabitude, dni dans stdio.h, valant usuellement 4096), un tampon est plein lorsque les BUFSIZ caractres ont t crits dans le tampon, il est vide lorsque les BUFSIZ caractres ont t lus ; un mode dit ligne (ou line-buffered) ; les tampons sont toujours de taille BUFSIZ, mais sont galement crits ds quune n de ligne est crite dans le tampon (ots en criture), et les lectures se font par lignes, cest--dire par squence dau plus BUFSIZ caractres suivis dun caractre de n de ligne ; un mode dit brut ; dans ce cas, le tampon nest pas utilis et toute lecture ou criture donne lieu une criture ou une lecture physique. Par dfaut, les ots prdnis stdin et stdout sont en mode ligne, le ot stderr est en mode brut, les ots construits partir de chiers sont en mode bloc.

1.2.6

Positionnement dans un ot

Certains ots, et notamment les ots construits partir de chiers, sont dits positionnables (traduction approximative du terme anglais seekable), cest--dire que lon peut associer au ot une notion de position courante. Cette position correspond dans ce cas un dcalage en octets (ou caractres) par rapport au dbut du chier. On rappelle que toute lecture ou criture se fait alors la position courante dans le ot (et que la position courante est modie par lopration de lecture ou dcriture). On peut obtenir la position dans un ot par la procdure :

1.3. TRAVAUX PRATIQUES long ftell (FILE *pf); position que lon peut ultrieurement utiliser dans une autre procdure : int fseek (FILE *pf,long pos,int whence);

33

Dans cette dernire procdure, le dernier paramtre permet dindiquer que la position est : relative au dbut du chier si le dernier paramtre est SEEK_SET ; relative la position courante si le dernier paramtre est SEEK_CUR ; relative la n du chier si le dernier paramtre est SEEK_END. les trois valeurs SEEK_SET, SEEK_CUR et SEEK_END tant dnies comme dhabitude dans stdio.h. On dispose galement de la procdure : void rewind (FILE *pf); dont lappel produit une action identique : fseek(pf,0,SEEK_SET); cest--dire un repositionnement au dbut du chier.

1.3
1.3.1

Travaux pratiques
Arguments de la procdure main

crire un programme en C qui afche chacun de ses arguments comme sur lexemple ci-dessous : $ ./affarg truc bidule 123 ./affarg a 4 arguments : arg[0] = "./affarg" arg[1] = "truc" arg[2] = "bidule" arg[3] = "123"

1.3.2

Consulter le manuel

Lire attentivement la page de manuel de la procdure strcmp(3) puis rpondre aux questions suivantes : quel chier den-tte doit-on inclure dans un programme pour utiliser sans ennui cette procdure ? prciser le nombre et le type des paramtres de cette procdure ; quel type de rsultat rend cette procdure ? quel est le fonctionnement prcis de cette procdure ?

34

CHAPITRE 1. ENTRES-SORTIES EN C

Ecrivez un programme nomm strcmp, admettant deux arguments, et qui rend comme status code 0 si les deux arguments sont identiques, 1 si le premier argument est infrieur (au sens de lordre lexicographique) au second, 2 sinon. NB1 : le programme strcmp nafche rien ! NB2 : dans une fentre de terminal, on peut consulter le status code dun programme en utilisant la commande echo et la pseudo-variable $? : $ $ 0 $ $ 1 $ $ 2 strcmp toto toto echo $? strcmp bidule toto echo $? strcmp toto bidule echo $?

1.3.3

Conversion de nombres

Lisez le manuel de la procdure atoi(3) permettant de convertir une chane de caractres reprsentant un nombre entier crit en dcimal, en une valeur entire gale ce nombre. Puis crivez un programme qui afche lcran le nombre entier 2147483647 ainsi que le nombre entier obtenu par la conversion de la chane de caractres "2147483647" par la procdure atoi(3). Puis remplacez 2147483647 par 2147483648 et commentez le rsultat obtenu. NB : ces valeurs sont codes en dur dans le programme.

1.3.4

criture dentiers

crire un programme en C, admettant un argument qui est un entier N, et qui afche tous les entiers de 1 N, chacun sur une ligne distincte, sur la sortie standard. Voici un exemple de fonctionnement : $ ./affint 4 1 2 3 4 NB : bien traiter le cas o le nombre de paramtres est incorrect, le cas o le paramtre nest pas un entier, le cas o cest un entier ngatif ou nul. Le status code du programme doit tre 0 en cas de russite, 1 si le paramtre est manquant ou incorrect. Puis modier le programme prcdent de faon que, sil est lanc sans argument, il prenne comme valeur de N la valeur par dfaut 10.

1.4. EXERCICES FAIRE. . . SIL RESTE DU TEMPS Enn, modier le programme de faon ce que lon puisse donner en dernier paramtre (aprs lventuelle valeur de N) le nom dun chier dans lequel lcriture des entiers doit tre faite. $ ./affint 4 1 2 3 4 $ ./affint 4 toto.txt $ cat toto.txt 1 2 3 4 $ ./affint 4 /toto.txt /toto.txt: Permission denied

35

NB : prendre soin bien traiter les erreurs dentres-sorties, toute erreur devant conduire un status code gal 2.

1.3.5

Nombre de caractres et de lignes dans un chier

crire un programme en C qui compte le nombre de caractres et le nombre de lignes dun chier, le nom du chier tant lunique paramtre de la ligne de commande : $ ./compte toto.txt toto.txt: 15 caracteres, 4 lignes $ ./compte /toto.txt /toto.txt: No such file or directory Modier le programme prcdent an que lon puisse effectuer lopration sur plusieurs chiers, les noms tant les divers arguments de la ligne de commande : $ ./compte toto.txt compte.c toto.txt: 15 caracteres, 4 lignes compte.c: 245 caracteres, 23 lignes

1.4
1.4.1

Exercices faire. . . sil reste du temps


Conversion hexadcimale

Vous avez vu le fonctionnement de la procdure atoi(3) : int atoi(const char *nptr);

36

CHAPITRE 1. ENTRES-SORTIES EN C

permettant de convertir une chane de caractres reprsentant un nombre en base 10 en un entier ayant pour valeur ce nombre. crivez une procdure permettant de convertir une chane de caractres reprsentant un nombre en base 16 (nombre hexadcimal) en un entier ayant pour valeur ce nombre. Cette procdure doit avoir pour prototype : int ahtoi(const char *nptr); La chane convertir est code en dur dans le programme et vaut "A380". Rappel : en base 16, on compte comme suit : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11. . .

1.4.2

Fichiers : format texte ou binaire

crivez un petit programme qui crit la valeur numrique 12345 dans deux chiers diffrents. Lcriture des deux chiers se fait dans le mme programme : dans le premier chier 12345.txt, cette valeur est crire en utilisant la procdure fprintf(3) dont le prototype est le suivant : int fprintf(FILE *stream, const char *format, ...); dans le second chier 12345.bin, la valeur sera crite en utilisant la procdure fwrite(3) : size_t fwrite(const void *ptr,size_t size, size_t nmemb, FILE *stream); Dans un terminal, comparez le contenu effectif de ces deux chiers avec la commande od(3) : $ od -c 12345.txt $ od -c 12345.bin Commentez cette diffrence.

1.4.3

Format de chier et manipulation de matrices

Dnir un format de chier (texte) permettant de stocker des matrices numriques non ncessairement carres de taille maximale NMAX (nombre maximal de lignes et nombre maximal de colonnes). Dnir et implmenter des procdures permettant de lire et dcrire des matrices dans un ot. Les tester avec des chiers simples (matrices 1x1, 2x2, 2x3). crire un programme permettant de fabriquer des matrices de taille nxm (n et m sont deux paramtres prciss comme arguments du programme), et dont les lments sont des valeurs entires alatoires entre 0 et 100. Gnrer ainsi une matrice 3x5, puis une matrice 5x4. Note : un soin particulier doit tre apport au traitement des erreurs lors de la lecture, notamment. Rcuprer le programme matmult.c qui effectue la multiplication de deux matrices stockes dans le programme source. Utiliser les procdures mises au point ci-dessus de faon que le programme modi admette des arguments (2 ou 3) dsignant les chiers contenant les matrices multiplier :

1.4. EXERCICES FAIRE. . . SIL RESTE DU TEMPS

37

les deux premiers arguments (obligatoires) sont les noms des chiers contenant les deux matrices multiplier ; le troisime argument, facultatif, prcise le nom du chier ( crer) contenant la matrice rsultat ; si cet argument est absent, la matrice est afche sur stdout. Tester le programme sur les deux matrices de lexercice prcdent (matrices 3x5 et 5x4).

38

CHAPITRE 1. ENTRES-SORTIES EN C

Chapitre 2 Structures de donnes en C


2.1 Cours

Ce cours va prsenter la notion de structure de donnes, concept permettant de regrouper, de dsigner, de manipuler, des donnes de natures diffrentes.

2.1.1

Introduction

Jusqu prsent, nos programmes utilisaient des donnes simples : caractres, entiers, ottants, et tableaux des prcdents. Ce ntait donc que dans lesprit du programmeur quun lien fonctionnel pouvait ventuellement stablir entre des donnes de natures diffrentes. Par exemple, dans le cas des matrices, nous avions t amens utiliser deux donnes : une variable qui contient la taille de la matrice ; une variable de type tableau deux dimensions qui contient les valeurs de la matrice. Lorsque le nombre de donnes augmente, il devient trs problmatique de les manipuler sous cette forme clate : chaque fois que lon veut manipuler une nouvelle entit, il faut dclarer un nombre de variables important. De plus, si lon souhaite utiliser une procdure sur lune de ces entits, il faut lui passer en argument toutes les variables utiles pour le traitement : ceci devient rapidement trs lourd, source derreurs (si on intervertit deux arguments, par exemple), et potentiellement source de dgradation de performance. Cest pour rsoudre ce type de problme que les langages de haut niveau proposent un mcanisme dagrgation, permettant de regrouper des donnes. On dsigne en gnral par structure de donnes un tel objet agrg. On trouve galement la terminologie denregistrement ou de record.

2.1.2
2.1.2.1

Types structurs en C
Dnition dun type structur

Le langage C permet au programmeur de construire ses propres types de donnes agrges. Pour cela, le programmeur doit prciser : le nom donn au nouveau type ; 39

40

CHAPITRE 2. STRUCTURES DE DONNES EN C

sa composition, cest--dire le nom et la nature des donnes quil contient. Ceci se dcrit comme suit : struct nom_structure { type1 var1; type2 var2; ... typen varn; }; qui dnit le type struct nom_structure (attention : le mot-cl struct fait partie intgrante du nom de type) comme tant compos dune donne de type type1 de nom var1, dune donne de type type2 de nom var2 . . . Le nom complmentaire de la structure (ici, nom_structure) permet de dclarer ultrieurement des variables de ce type, exactement comme on le fait avec les types de base : struct essai { int toto; float bidule; }; struct essai x; Ce nom de structure doit respecter les mmes contraintes quun nom de variable en C : compos uniquement de lettres, chiffres ou du caractre _, ne commenant pas par un chiffre. Le nombre de caractres autoriss dpend de lenvironnement. Chaque donne lintrieur de la structure prend le nom de champ (eld en anglais). Les contraintes sur un nom de champ sont les mmes que celles pesant sur un nom de variable. 2.1.2.2 Structures et tableaux, structures imbriques

On peut tout fait utiliser un champ de type tableau lintrieur dune structure : ceci se fait de faon similaire la dclaration dune variable de type tableau. Voici par exemple une dclaration dun type matrice, toujours sur le principe de lutilisation du coin nord-ouest dune matrice de taille maximale xe : #define NMAX 32 struct matrice { int n; int mat[NMAX][NMAX]; };

2.1. COURS

41

Une matrice au sens dni ci-dessus contient alors la taille relle de la matrice, et les donnes de la matrice. On peut aussi dclarer un champ comme tant dun type structur pralablement dni. Par exemple : #define MAX_VAL 32 #define MAX_LIGNE 64 struct vector { int nb; double val[MAX_VAL]; }; struct matrix { int nb; struct vector v[MAX_LIGNE]; }; Une telle structure permet par exemple, de stocker un tableau deux dimensions, le nombre de lignes tant variable (limit une valeur maximale, comme dhabitude, ici MAX_LIGNE), mais le nombre dlments dans chaque ligne tant galement variable (limit la valeur MAX_VAL). noter galement quil ny a aucune ambigut sur le champ nb de la structure vector et le champ nb de la structure matrix, car ils portent des noms identiques mais dans des structures diffrentes. Notons enn que les deux dclarations de types ci-dessus peuvent tre regroupes et donner la version parfaitement identique : struct matrix { int nb; struct vector { int nb; double val[MAX_VAL]; } v[MAX_LIGNE]; }; Enn, un type structur dni par lutilisateur tant utilisable tout comme les types de base du langage C, on peut dclarer des tableaux de structures : struct matrix tableau_de_matrices[10]; Pour rsumer, tableau et structures sont deux oprateurs dagrgation de donnes que lon peut combiner autant de fois que dsir.

42 2.1.2.3

CHAPITRE 2. STRUCTURES DE DONNES EN C Accs aux champs dune structure

La dsignation du champ toto de la variable x se fait par la construction du langage (dite qualication directe) x.toto Cette notation est utilisable aussi bien en partie droite dune expression, auquel cas la valeur de lexpression est la valeur contenue dans le champ correspondant de la variable, quen partie gauche dune expression daffectation, auquel cas le champ est modi et prend la valeur indique en partie droite. Voici quelques exemples dutilisation des champs de la variable x : ... x.toto=4; x.bidule=1.1; for (;x.toto--;) x.bidule*=(1.-x.bidule/2.); (void) printf("x.bidule=%f\n",x.bidule); ... et quelques exemples dutilisation des champs dans le cas de structures imbriques : ... struct matrix M; int i,j; ... for (i=0;i<M.nb;i++) { (void) printf("Ligne %d:\n",i); for (j=0;j<M.v[i].nb;j++) (void) printf("\tval[%d]=%f\n",M.v[i].val[j]); } Noter la succession des qualications [] et . dans lexpression de la dernire ligne M.v[i].val[j] : M est une variable de type struct matrix, dont on considre le champ v, qui est lui-mme un tableau, dont on considre llment i, qui est une structure contenant les champs nb et val, ce dernier tant enn un tableau dont on considre llment j ! Les qualications se lisent de gauche droite . Enn, dans le cas o lon dispose de ladresse dune structure, par une variable de type adresse de cette structure, par exemple, on peut accder aux champs directement partir de ladresse par la construction (dite qualication indirecte) du langage C -> : struct matrix M; struct matrix *addr; ...

2.1. COURS addr = &M; if (addr->nb > 2) ... cette dernire notation tant totalement quivalente : if ((*addr).nb > 2)

43

2.1.3

Structures et procdures

Comme toute variable en C, une structure (ou plus exactement, une variable de type structure) peut tre utilise comme argument ou comme rsultat dune procdure. Cependant, comme les donnes doivent tre copies (aussi bien les arguments de la procdure que le rsultat), il est beaucoup plus efcace de passer aux procdures ladresse des structures. Ceci permet galement aux procdures appeles de modier le contenu des structures. Voici par exemple une procdure dimpression dune matrice dnie par une variable de type struct matrix, utilisant un passage de ladresse de la variable : void imprime_matrice (struct matrix *M) { int i,j; for (i=0;i<M->nb;i++) { (void) printf("Ligne %d:\n",i); for (j=0;j<M->v[i].nb;j++) (void) printf("\tval[%d]=%f\n",M->v[i].val[j]); } } ... struct matrix mat; ... imprime_matrice(&mat); ...

2.1.4

Structures rfrenant des structures

Nous avons vu que des structures peuvent contenir dautres structures. Si lon reprend lexemple des matrices, cette imbrication des structures de donnes correspond bien limbrication naturelle de lobjet reprsent : une matrice est un ensemble de lignes, une ligne tant un ensemble de valeurs. Il est cependant des cas o cette imbrication ne peut tre satisfaisante. Illustrons-le sur un exemple. On cherche modliser informatiquement des personnes : chaque personne a des attributs qui lui sont propres, comme le nom, le prnom, la date de naissance, la taille, le poids. . . Il peut cependant tre souhaitable de conserver dans la structure de donnes de la personne les

44

CHAPITRE 2. STRUCTURES DE DONNES EN C

relations familiales comme le conjoint, le pre, la mre, les frres et soeurs, les enfants. . .. Il nest alors pas possible que chaque personne contienne explicitement ces personnes lies car les structures devraient se contenir mutuellement (le pre contient le ls, le ls contient le pre) ! On pourrait penser une premire solution consistant numroter les personnes : par exemple, toutes les personnes sont stockes dans un tableau, et le numro dindice dans le tableau est alors un identiant non ambigu de chaque personne. Mais la seule donne du numro ne permet pas davoir accs directement la personne concerne : on a besoin dune procdure de recherche de ce numro, ce qui peut tre pnalisant en termes de performances. Pour trouver une solution efcace ce problme, reprenons notre modle de Von Neuman : chaque donne dans la mmoire de lordinateur est repre, entre autres, par son adresse, cest-dire le numro de la (premire) case mmoire contenant la donne. On peut donc dire que cette adresse est un identiant unique de la donne ! Cest donc cette adresse que nous allons utiliser, on parle en gnral de rfrence la donne, ou encore de pointeur sur la donne. On rappelle quen C, on peut dnir des variables de type adresse sur un type (de base, ou structure, ou mme adresse. . .) par la construction : type_de_donnee_referencee *nom_variable; le caractre * indiquant le fait que cest une variable de type adresse que lon dnit. On rappelle galement que lon peut dsigner ladresse dune variable par la construction : &variable Voici quelques exemples de dclarations : /* i est une variable de type int */ int i; /* addr est une variable de type adresse de int */ /* initialisee a ladresse de la variable i */ int *addr = &i; /* tab_addr est un tableau dadresses de caracteres */ /* soit encore un tableau de chaines de caracteres */ /* tab_addr[0] est (ladresse de) la chaine "hello" */ /* tab_addr[1] est (ladresse de) la chaine "world" */ char *tab_addr[10] = { "hello","world" }; On rappelle encore que le nom dun tableau dsigne en fait ladresse du premier lment de ce tableau. Voici donc une faon de construire une structure de donnes de type personne, permettant de rfrencer (ventuellement) un conjoint. Comme ladresse 0 (macro-dnition NULL) est une adresse mmoire dont on est certain quelle ne peut contenir une donne valide, on utilisera cette adresse lorsquune personne na pas de conjoint.

2.1. COURS struct person { char nom[32]; char prenom[32]; float poids; float taille; struct person *conjoint; };

45

Voici maintenant une procdure permettant de remplir les champs de la structure (noter que lon passe ladresse de la structure la procdure, car celle-ci doit modier le contenu de cette structure) : /* procedure de "remplissage" de la structure */ void def_personne (struct person *pers, char *nom,char *prenom, double poids,double taille, struct person *conjoint) { strncpy(pers->nom,nom,31); pers->nom[31]=0; strncpy(pers->prenom,prenom,31); pers->prenom[31]=0; pers->poids=(float) poids; pers->taille=(float) taille; pers->conjoint=conjoint; } Enn, voici quelques exemples dutilisation de cette procdure de remplissage : int main (int argc,char *argv[]) { struct person alice, bob, oscar; /* donnees concernant alice, mariee a bob */ def_personne(&alice,"dubois","alice",55.,1.68,&bob); /* donnees concernant bob, marie a alice */ def_personne(&bob,"dubois","bob",72.,1.82,&alice); /* donnees concernant oscar, sans conjoint */ def_personne(&oscar,"alone","oscar",85.5,1.95,NULL); ... }

2.1.5

Allocation dynamique de structures et tableaux

Nous avons insist plusieurs fois sur le fait que lon devait dclarer les variables avant de les utiliser : lun des rles de cette dclaration est prcisment de permettre au compilateur dallouer

46

CHAPITRE 2. STRUCTURES DE DONNES EN C

les zones mmoire ncessaires pour le stockage des donnes. Ceci impose au programmeur de connatre le nombre de donnes ncessaires au programme : dans le cas des tableaux et des matrices, nous avons ainsi souvent dclar des tableaux dune taille maximale xe, et utilis une partie seulement de ce tableau, mais il est totalement impossible, moins de modier la valeur de la taille du tableau et donc de recompiler le programme, de traiter des donnes de taille suprieure ! Il est de nombreux cas o le nombre de donnes est inconnu a priori, et o il nest pas non plus possible de rserver une place mmoire maximale xe. Le langage C offre au programmeur la possibilit dallouer dynamiquement et la demande, des emplacements en mmoire pour stocker les donnes. 2.1.5.1 La procdure malloc

La bibliothque standard du C fournit une procdure dont le prototype, dni dans le chier den-tte stdlib.h, est le suivant : void *malloc (size_t sz); Cette procdure (dont le nom drive de memory allocator) alloue (cre) une zone mmoire susceptible de stocker une donne dau moins sz octets. Le rsultat de la procdure est une adresse gnrique de type void*, reprsentant ladresse mmoire o la procdure a cr la zone mmoire demande. Le contenu initial de cette zone mmoire est indtermin (on y retrouve ce quil y avait cet endroit de la mmoire, cest--dire peu prs nimporte quoi !). Cette valeur peut tre stocke dans toute variable de type adresse. Voici un exemple dutilisation de cette procdure, reprenant le petit programme prcdent qui manipule des structures de type personne : #include <stdio.h> #include <string.h> #include <stdlib.h> struct person { char nom[32]; char prenom[32]; float poids; float taille; struct person *conjoint; }; /* procedure de "remplissage" de la structure */ void def_personne (struct person *pers, char *nom,char *prenom, double poids,double taille, struct person *conjoint)

2.1. COURS { strncpy(pers->nom,nom,31); pers->nom[31]=0; strncpy(pers->prenom,prenom,31); pers->prenom[31]=0; pers->poids=(float) poids; pers->taille=(float) taille; pers->conjoint=conjoint; } int main (int argc,char *argv[]) { struct person *alice, *bob, *oscar; alice=(struct person*) malloc(sizeof(struct person)); bob=(struct person*) malloc(sizeof(struct person)); oscar=(struct person*) malloc(sizeof(struct person)); /* donnees concernant alice, mariee a bob */ def_personne(alice,"dubois","alice",55.,1.68,bob); /* donnees concernant bob, marie a alice */ def_personne(bob,"dubois","bob",72.,1.82,alice); /* donnees concernant oscar, sans conjoint */ def_personne(oscar,"alone","oscar",85.5,1.95,NULL); ... }

47

Plusieurs remarques sur ce programme : noter la dclaration de variables de type adresse de structure personne au lieu de structure personne ; lutilisation de loprateur sizeof qui permet de connatre la taille en octets dun type ou dune variable ; son utilisation permet de rserver la bonne taille mmoire pour stocker la structure voulue ; la conversion explicite de ladresse gnrique rendue par la procdure malloc alice=(struct person*) malloc(sizeof(struct person)); sans cette conversion explicite, le compilateur peut indiquer un avertissement ; la forme gnrale dune conversion explicite de type est : (type_converti) expression_type_initial comme par exemple : (double) 3 (int) 3.45 (struct person*) 0 lutilisation dans les appels la procdure def_personne des valeurs contenues dans les variables alice, bob ou oscar, sans & devant : on copie les valeurs contenues dans ces variables, qui sont de type adresse ;

48

CHAPITRE 2. STRUCTURES DE DONNES EN C bien noter la distinction entre la variable de type adresse, dont lemplacement mmoire est rserv par le compilateur, et les objets de type structur, dont les emplacements mmoire sont allous dynamiquement ; on ne peut pas dailleurs associer dynamiquement un nom de variable cet emplacement mmoire ! noter enn quune zone mmoire alloue dynamiquement au sein dune procdure reste alloue mme lorsque la procdure est termine ! Ce comportement est notoirement diffrent des variables locales la procdure, dont lemplacement mmoire est allou par le compilateur, mais qui sont dtruites (emplacement mmoire rendu disponible) lorsque la procdure est termine.

2.1.5.2

La procdure calloc

On a dni les tableaux comme un arrangement contigu en mmoire dlments de type identique. De la mme faon que lon peut allouer dynamiquement des structures, on peut allouer dynamiquement des tableaux : on dispose pour cela dune procdure nomme calloc, dont le prototype est : void *calloc (size_t nb_elems,size_t elem_size); Cette procdure alloue une zone mmoire sufsante pour stocker un tableau contenant nb_elems lments, chaque lment occupant une taille elem_size. Comme la procdure malloc, cette procdure retourne comme rsultat une adresse gnrique, que lon peut stocker dans une variable de type tableau, et lon rappelle ici quune variable de type tableau reprsente en fait ladresse du premier lment. Une diffrence notoire par rapport la procdure malloc : lespace mmoire ainsi allou est initialis zro. Voici un exemple dutilisation dun tableau dont la taille est connue lexcution, et dont lallocation se fait donc dynamiquement : #include <stdio.h> #include <stdlib.h> int main (int argc,char *argv[]) { int N; int *tab; /* la valeur de N (taille du tableau) est le */ /* premier argument du programme. Sil ny a */ /* pas dargument, on prend la valeur 100 */ if (argc==1) N=100; else N=atoi(argv[1]); /* on alloue un tableau de N elements, chaque */ /* element est un int (on utilise sizeof) */

2.1. COURS tab=(int*) calloc(N,sizeof(int)); if (tab!=NULL) { (void) printf("tableau tab a ladresse %p\n",tab); return 0; } perror("calloc"); return 2; }

49

Note : on a utilis dans la procdure printf le format %p, permettant dimprimer des expressions de type adresse (constantes, variables, adresses de variables). Ce programme donne, lexcution : $ alloctab 1000 tableau tab a ladresse 10015008 $ alloctab 300000000 calloc: Resource temporarily unavailable Dans le second cas, la zone mmoire demande dpasse la mmoire disponible de lordinateur, do le message derreur. Il est noter que cette procdure permet dallouer un tableau une seule dimension. Il nest pas possible dallouer dynamiquement un tableau p dimensions si les p-1 dernires dimensions ne sont pas connues au moment de la compilation. Par contre, on peut crer un tableau dynamique, dont chaque lment est lui-mme un tableau dynamique, comme dans lexemple ci-dessous : /* creation dune matrice dynamique nxp */ double **matrice_dynamique (int n,int p) { double **mat; int i,j; mat=(double**) calloc(n,sizeof(double*)); if (mat==NULL) return NULL; for (i=0;i<n;i++) { mat[i]=(double*) calloc(p,sizeof(double)); if (mat[i]==NULL) return NULL; for (j=0;j<p;j++) mat[i][j]=(double) i / (double) j+1; } return mat; }

50

CHAPITRE 2. STRUCTURES DE DONNES EN C

Il faut bien comprendre la signication de ce code : mat est un tableau (une adresse) dont chaque lment est un tableau (une adresse) de valeurs de type double, do la dclaration double **mat (ou de faon quivalente double *mat[]) ; on commence par allouer le tableau qui va contenir les lignes, cest--dire n tableaux (adresses) ; do lappel initial calloc pour allouer n lments, chacun tant lui-mme de type adresse de double* ; ensuite, on alloue chaque lment du tableau mat, qui est un tableau de p valeurs, chacune de type double, tout ceci tant fait dans la boucle sur i ; on donne une valeur chaque lment de la ligne, do la seconde boucle sur j ; Noter au passage que lon utilise pour accder aux lments de la matrice lexpression mat[i][j], cest--dire la mme expression que pour un tableau deux dimensions ; le compilateur effectue toutefois un travail diffrent : partir de ladresse contenue dans la variable mat, il calcule ladresse de stockage du i-me lment (mat[i]), et rcupre la valeur contenue cette adresse ; partir de valeur prcdemment rcupre (qui est une adresse), il calcule ladresse de stockage du j-me lment, et rcupre la valeur contenue cette adresse. Dans le cas dun tableau deux dimensions, le compilateur peut directement calculer ladresse de stockage de llment (i, j) partir de ladresse du premier lment du tableau. De la mme faon, au point de vue de lencombrement mmoire, ce type de stockage ncessite, outre les n p emplacements pour stocker les valeurs, le stockage du tableau des n adresses des lignes (soit 4n octets supplmentaires sur un systme 32 bits). 2.1.5.3 La procdure free

Ainsi que ceci a t not, un espace mmoire allou dynamiquement nest pas dtruit en n de procdure. Si on veut le librer, on doit le faire explicitement, par un appel la procdure free dont le prototype est : void free (void *addr); Le paramtre pass en argument cette procdure est ladresse (de type adresse gnrique) dune zone mmoire pralablement obtenue par un appel malloc ou calloc. Note 1 : de mme quil est de bonne pratique de fermer un chier dont le programme na plus besoin, il est de bonne pratique de librer les zones mmoires dont on na plus besoin ; en n de programme, les zones mmoires sont de toutes faon libres et rendues au systme dexploitation ; Note 2 : le plus souvent, la mmoire libre nest pas vraiment rendue disponible pour le systme ; elle est nouveau disponible pour des appels ultrieurs de malloc et calloc ;

2.1. COURS

51

Note 3 : si lon effectue de nombreux appels malloc et free, on peut obtenir une mmoire en gruyre (succession de zones libres et de zones alloues) qui peuvent pnaliser les performances. Il existe pour pallier ce problme une version optimise de la bibliothque dallocation dynamique. On peut galement trouver des versions de dbogage de cette mme bibliothque, qui permettent de trouver plus facilement des bogues se traduisant par un dysfonctionnement du mcanisme dallocation mmoire ; Note 4 : que se passe-t-il si on passe en argument free une adresse non obtenue par malloc ? Certains manuels prcisent : Undened results will occur if some random value is passed as the argument to free. Dautres sont encore plus alarmistes : Absolute chaos is guaranteed if some random value is passed as the argument to free.

2.1.6

Dnition de type nomm

On a vu que, pour un type structur, le mot-cl struct (ou enum ou union, voir plus loin) fait partie intgrante du nom de type. Le langage C offre la possibilit de nommer un type dni ; ceci se fait grce au mot-cl typedef (type denition), prcdant une dnition ressemblant une dnition de variable : typedef struct toto { int x; float y; struct toto *addr; } T_toto; Dans ce cas, on ne dnit pas une variable mais un nom de type (dans lexemple T_toto devient le nom dni pour le type struct toto). On peut dailleurs utiliser ce mcanisme pour donner un nom un type tableau, en utilisant la mme syntaxe : typedef int vector[100]; On dnit ainsi un type tableau de 100 entiers , au lieu de dnir une variable de ce type. Une fois que lon a dni un type nomm, on lutilise exactement comme un type standard du langage : vector V; T_toto tmp; ... V[50]=tmp.x; ...

52

CHAPITRE 2. STRUCTURES DE DONNES EN C

Note : le type FILE utilis pour les entres-sorties est en fait un type nomm dni par un typedef dans le chier den-tte stdio.h ; on rappelle ce sujet que les entres-sorties ne sont pas un lment du langage C lui-mme, mais font partie dune bibliothque standard.

2.2
2.2.1

Complments de cours
Unions et types numrs

Outre la construction de structures, le langage C offre deux mcanismes similaires : la construction dunions, et la construction de types numrs. 2.2.1.1 numrations

On a parfois bien envie de reprsenter des donnes de nos programmes par des noms symboliques : lexemple le plus trivial est celui du type boolen, dont nous avons dj indiqu quil nexistait pas dans le langage C. Il existe donc un constructeur particulier du langage permettant de dnir un type compos de valeurs prises dans un ensemble, dont les lments sont dnis par un symbole. La dclaration dun type numr fait par la construction : enum nom_de_type { liste de symboles }; Les symboles doivent vrier les mmes proprits que les noms de : succession de caractres alphabtiques (minuscules et majuscules), numriques, ou caractre _, ne commenant pas par un chiffre. Comme pour les structures, le mot-cl enum fait partie intgrante du nom de type. Voici un exemple de dnition de type boolen : enum booleen { vrai, faux }; ... enum booleen condition; ... condition=vrai; for (;condition != faux ;) { if (test()) condition=faux; } Il faut savoir quen interne, tout type numr est reprsent par un entier : le compilateur se charge de convertir les symboles en valeurs et rciproquement. On peut dailleurs imposer les valeurs certains des symboles en le prcisant par une initialisation des symboles dans la dclaration du type numr, comme dans lexemple ci-dessous : enum couleur {rouge,vert=2,bleu=4,orange,jaune=2};

2.2. COMPLMENTS DE COURS

53

Encore une fois, le compilateur se borne convertir les symboles en valeurs : il nest dit nulle part que ces symboles reprsentent forcment des valeurs distinctes, et lcriture ci-dessus est parfaitement licite ! 2.2.1.2 Unions

La construction de structures dnie prcdemment permet de modliser des objets dnis par une agrgation de donnes (les champs de la structure). Il est des cas o lon a besoin de reprsenter des donnes de type diffrents, un seul de ces types de donnes tant utile pour une instance dobjet donne. Prenons lexemple des variables dun langage de programmation. Pour chaque variable, on a besoin de stocker son nom (une chane de caractres vriant certaines contraintes), son type (on se limitera ici int, double et chane dau plus 15 caractres), ainsi que sa valeur. On voit que la donne reprsentant la valeur de la variable dpend du type. Une union est un mcanisme de construction permettant justement de dnir les diffrentes reprsentations dune donne, sachant que la place mmoire de chacune des reprsentations occupe un espace mmoire identique. Pour lexemple de la variable, on pourrait ainsi dnir : #include <stdio.h> #include <string.h> #define NOM_MAX 32 #define MAX_CAR 15 struct variable { char nom[NOM_MAX]; enum type_var { entier, flottant, chaine } type; union val_var { int i; double d; char str[MAX_CAR+1]; } val; }; Noter lutilisation dun type numr pour reprsenter le type de la variable. Bien distinguer NOM_MAX qui est le nombre maximal de caractres pour stocker le nom de la variable, et MAX_CAR qui est le nombre maximal de caractres dans le cas o la variable est du type chaine ! Voici maintenant une procdure qui afche les informations contenues dans une structure (noter que, bien que la procdure ne modie pas le contenu de la structure, on utilise un passage par adresse pour des questions de performance) : void imprime_variable (struct variable *v) { (void) printf("variable %s, type %d, valeur :",

54

CHAPITRE 2. STRUCTURES DE DONNES EN C v->nom,v->type); switch (v->type) { case entier: printf("%d\n",v->val.i); break; case flottant: printf("%f\n",v->val.d); break; case chaine: printf("%s\n",v->val.str); break; } }

Utilisons maintenant cette procdure dans le petit exemple suivant : int main (int argc,char *argv[]) { struct variable v1,v2,v3; strcpy(v1.nom,"i1"); v1.type=entier; v1.val.i=4; strcpy(v2.nom,"x0"); v2.type=flottant; v2.val.d=3.141596; strcpy(v3.nom,"str"); v3.type=chaine; strcpy(v3.val.str,"Hello world"); imprime_variable(&v1); imprime_variable(&v2); imprime_variable(&v3); } ce qui donne lexcution : variable i1, type 0, valeur : 4 variable x0, type 1, valeur : 3.141596 variable str, type 2, valeur : Hello world On accde aux champs dune union exactement comme aux champs dune structure. Note : il est, encore une fois, de la responsabilit du programmeur de garantir que laccs au champ utile dune union est fait bon escient. Le compilateur ne soccupe absolument pas de la cohrence des accs. Par exemple, si la variable v3 utilise ci-dessus est par erreur dclare de type entier, on obtient le rsultat suivant :

2.3. TRAVAUX PRATIQUES variable i1, type 0, valeur : 4 variable x0, type 1, valeur : 3.141596 variable str, type 0, valeur : 1214606444

55

Explication : les quatre premiers octets de la chane de caractres H, e, l et l ont t interprts comme codage sur 4 octets 0x48656c6c ce qui (sur machine gros-boutiste) correspond : 108 + 256 (108 + 256 (101 + 256 72)) = 1214606444

2.3
2.3.1

Travaux pratiques
Manipulation de chiers de voitures

On dispose dun chier contenant des informations sur des voitures doccasion : le modle et le prix. Plus prcisment : le chier est de type texte (caractres imprimables du code ASCII) ; chaque ligne du chier correspond une voiture ; chaque ligne du chier contient le nom du modle suivi du caractre ; (point-virgule), suivi du prix (exprim en euros par un nombre entier ou ottant). Voici un petit exemple dun tel chier : Peugeot Citroen Renault Citroen 206 CC;7200 Xsara HDi;2900 Laguna DCi;6300 Xsara Picasso HDi;7500

Le but de lexercice est dcrire un programme qui va permettre de trier ce chier par ordre de prix croissant. 2.3.1.1 Liminaire

Tlcharger sur le site Internet du cours le chier voitures.c. Ce chier contient la dnition dun type struct voiture, dni comme suit : /* taille maximale pour un nom de voiture #define MAXNOM 40 */

/* une structure de donnees pour stocker une voiture : */ /* le nom (tableau de MAXNOM caracteres), le prix (double) */ struct voiture { char nom[MAXNOM]; double prix; };

56

CHAPITRE 2. STRUCTURES DE DONNES EN C

Ce chier contient galement une procdure qui permet de trier par ordre de prix croissant un tableau de structures struct voiture (on ne cherchera pas forcment comprendre cette procdure). Il contient enn une procdure principale qui utilise la procdure prcdente sur un exemple dni en dur dans le programme : compilez ce programme, testez-le, puis comprenez lutilisation de la procdure de tri. 2.3.1.2 Procdure de lecture du chier

Ecrivez une procdure de lecture dun tableau de voitures partir dun chier. Cette procdure devra avoir le prototype suivant : int lit_fichier_voitures (struct voiture bagnoles[], int nb_voit, char *nom_fich); les trois paramtres reprsentant respectivement ladresse mmoire du tableau, le nombre maximal dlments de ce tableau, le nom du chier (une chane de caractres). La procdure devra rendre comme rsultat le nombre de voitures lues. Attention : la procdure doit bien entendu tester si ce qui est lu est correct (toute ligne qui contient des donnes incorrectes doit tre ignore). Faire attention aussi ne pas lire plus de donnes quil ny a de place dans le tableau ! Le rsultat devra tre gal -1 en cas derreur (chier inexistant ou non lisible). 2.3.1.3 Procdure dcriture dun chier de voitures

Ecrivez maintenant une procdure dcriture dun tableau de voitures sur la sortie standard (en utilisant un format identique celui du chier original). Cette procdure admet deux paramtres : ladresse mmoire du tableau et le nombre dlments crire. Puis modiez la procdure prcdente an dcrire dans un chier (le chier original ou un autre chier), dont le nom sera pass en troisime paramtre de la procdure. 2.3.1.4 Conclusion

Compltez votre programme pour quil ralise lensemble des traitements voulus. Pour tre plus prcis, le programme devra considrer que chaque argument qui lui est pass sur la ligne de commande est le nom dun chier que le programme doit trier (le rsultat du tri devra tre crit dans le chier original). Le status code du programme sera gal au nombre de chiers que le programme naura pas russi traiter (donc 0 en cas de succs).

2.3.2

Gestion de comptes bancaires

Il sagit maintenant de dvelopper une petite application de gestion simplie de comptes bancaires. On veut que cette application ait le comportement suivant :

2.3. TRAVAUX PRATIQUES

57

elle doit prsenter une interface utilisateur simple (de type ligne de commande) permettant de raliser les oprations de base qui sont : la cration dun nouveau compte ; on attribue un numro chaque compte, un titulaire, une adresse, un solde initial nul ; la consultation du solde dun compte ; un dpt en numraire sur un compte ; un retrait en numraire sur un compte (pas de dcouvert autoris !) ; la consultation des 4 dernires oprations ralises sur un compte ; la clture du compte, qui nest possible que si le solde du compte est nul (il faut ventuellement effectuer un retrait de ce solde auparavant). elle doit conserver les donnes saisies sous forme permanente, an de les retrouver lors dune prochaine excution ; ladministrateur de lagence doit pouvoir accder au total des soldes des comptes ouverts ; An de se consacrer au plus sur les aspects de modlisation des structures de donnes, un squelette du programme est fourni, intgrant tous les aspects dinterface utilisateur : menus de consultation, saisie des informations. . . Ce programme nomm basebank.c est disponible sur le site Internet du cours. 2.3.2.1 Liminaire

Rcuperer le programme dexemple, le compiler, le faire tourner an den comprendre le fonctionnement (les diffrents menus accessibles). Puis, lire attentivement le code et reprer les procdures quil faudra complter. 2.3.2.2 Analyse et conception

Rchir la modlisation dune opration, dun compte bancaire ; rchir galement la modlisation de lagence elle-mme. Rchir au problme du stockage permanent des donnes. Concevoir des mcanismes dinitialisation (rcupration des donnes) et de terminaison (sauvegarde de ces donnes). Rchir la faon de raliser les oprations demandes. Rchir de plus quelques fonctionnalits bien pratiques : retrouver un compte connu par son numro, retrouver un compte connu par le nom de son titulaire. . . 2.3.2.3 Implmentation

Implmenter les structures et procdures conues ci-dessus. Un grand soin doit tre apport, comme dhabitude, au traitement des ventuelles erreurs. 2.3.2.4 Pour conclure, sil reste du temps. . .

Rchir limplmentation en allouant dynamiquement les structures de donnes.

58

CHAPITRE 2. STRUCTURES DE DONNES EN C

Chapitre 3 Listes
3.1 Cours

La suite de ce cours va prsenter des structures de donnes classiques, dont on trouve lutilit dans de nombreux problmes.

3.1.1

Introduction

Les structures de donnes qui vont tre prsentes ne sont que trs rarement utilises telles quelles dans la rsolution des problmes rels. Par contre, les structures relles sont quasiment tout le temps composes dlments pouvant chacun se rapporter lune des structures dtailles ci-aprs. On retrouve donc une analyse classique : identier dans un problme des donnes ou des traitements utilisant des outils de base ; savoir assembler bon escient ces composants pour rsoudre le problme pos. Ce chapitre sintressera prsenter lune des structures de donnes fondamentale de linformatique, la liste, ainsi que quelques uns des algorithmes qui lui sont associs.

3.1.2
3.1.2.1

Listes chanes
Prsentation

On a vu que les tableaux permettaient de reprsenter en mmoire une collection de donnes de mme nature. Il a galement t dit que ces tableaux taient manipuls sous forme linaire : la donne lindice i + 1 est conscutive en mmoire la donne dindice i. Il est des cas o cette contigut nest pas satisfaisante. Par exemple, pour ajouter ou supprimer une donne un endroit quelconque dun tableau, on est oblig de dcaler la n du tableau (les indices suprieurs lindice o lon veut insrer ou dtruire la donne). Si lendroit de modication du tableau est totalement alatoire, on obtient une opration dont le cot est, en moyenne, proportionnel la taille du tableau. On a alors un problme rsoudre : la notion dlment suivant dans un tableau est une notion implicite. On va donc tre oblig de lexpliciter an de conserver les mmes techniques 59

60

CHAPITRE 3. LISTES

de parcours. Parmi les moyens dont on dispose en langage C, on va utiliser ladresse mmoire an de dsigner de faon unique un lment. On appelle donc liste chane ou plus simplement liste une structure de donne constitue dlments contenant chacun : des donnes utiles ou une rfrence (adresse, ou pointeur) sur des donnes utiles ; une rfrence (adresse, ou pointeur) de llment suivant. La liste est elle-mme totalement dtermine par la rfrence (ladresse) de son premier lment : on nomme souvent ce premier lment tte de liste. Pour indiquer le dernier lment de la liste, on se sert en gnral dune adresse dont on est sr quelle ne peut contenir aucune donne utile : cest le cas de ladresse dnie par la macro-dnition NULL dans le chier den-tte stdio.h. Lalgorithme 1 propose un exemple de dclaration en C dun type structur permettant de construire une liste dentiers. Algorithme 1 Exemple de ralisation dune structure de liste struct list_entier { int val; struct list_entier *suivant; }; ... struct list_entier *tete=NULL;

Insistons encore sur le fait quune telle structure ne nous sert que dexemple ! Limportant est de comprendre quun lment de liste est une association dune valeur (ou dun ensemble de valeurs), avec une dsignation (souvent, une adresse ou un pointeur) qui nous indique o se trouve llment suivant , alors que, dans un tableau, cet lment suivant est implicitement dni comme llment dont le numro est un plus le numro de llment courant. Nous reviendrons naturellement sur ces notions, en explicitant les diffrences entre ces deux types de structures de donnes, et leurs avantages et inconvnients respectifs. 3.1.2.2 Procdures de manipulation de listes

Les procdures de manipulation concernent : linitialisation dune liste (en gnral, liste vide) ; le test, permettant de dterminer si une liste est vide ; passage llment suivant ; lajout dun lment dans une liste ; la suppression dun lment dans une liste ; le parcours de la liste ; etc. Une liste, ou plus prcisment, la tte de la liste, sera, dans la suite du chapitre, reprsent par un pointeur sur le premier lment de la liste, et nous utiliserons par dfaut la convention dcrite dans lalgorithme 1, lobjet NULL permettant de reprsenter une liste vide.

3.1. COURS

61

Linitialisation dune liste est alors triviale : il suft daffecter la valeur NULL la variable reprsentant la tte de la liste. 3.1.2.3 Longueur dune liste

titre dexemple, voici un premier algorithme (algorithme 2) oprant sur nos listes, et qui permet de calculer le nombre dlments dune liste. Algorithme 2 Taille dune liste int list_length (struct list_entier *L) { int len=0; while (L != NULL) { L = L->suivant; len++; } return len; }

Cet algorithme est un parcours de liste typique, utilisant une boucle de type while, mettant en uvre deux oprations lmentaires : le test, dterminant si lon est arriv en n de liste ou non, et le passage au suivant. 3.1.2.4 Impression du contenu dune liste

Lalgorithme prsent ci-dessous (algorithme 3) est trivialement proche du prcdent : il sagit simplement dimprimer la valeur des lments rencontrs au cours du parcours de la liste. Algorithme 3 Impression de liste void list_print (struct list_entier *L) { int num; for (num = 0 ; L ; num++ , L = L->suivant) printf("Element %d = %d\n", num, L->val); }

Note : on a cette fois utilis une boucle for. On rappelle que cette boucle comprend trois parties : lexpression dinitialisation (ici num = 0) ; lexpression de test de boucle (ici L, donc valeur vraie tant que ladresse contenue dans L est diffrente de 0) ;

62

CHAPITRE 3. LISTES

lexpression de passage la boucle suivante ; ici, il y a deux expressions (num++ et L=L->suivant), que lon a runies en une expression unique en les sparant par des virgules (oprateur dit dabsorption ). Linstruction du corps de la boucle est alors rduit la seule impression. 3.1.2.5 Cration dlment

Nous navons pas encore abord les aspects de cration dlment de liste. Cette opration peut, a priori, se scinder en deux tapes : lobtention dune zone de mmoire pour la reprsentation de llment ; la mise en place de cet lment au sein dune liste particulire, lemplacement dsir. Comme pour toute ralisation informatique, diffrentes approches sont possibles pour lobtention dune zone de mmoire pour la reprsentation dun lment, par exemple : dclarer un tableau de structure de list_entier, et utiliser les lments successifs du tableau ; utiliser la procdure du systme malloc pour allouer dynamiquement cette zone. Cest cette dernire approche que nous utiliserons. Les algorithmes 4 nous permettent respectivement dallouer et de librer la mmoire servant reprsenter un lment. Algorithme 4 gestion des lments struct list_entier *elem_new (int val) { struct list_entier *new; new = (struct list_entier *) malloc (sizeof(struct list_entier)); new->val = val; new->suivant = NULL; return new; } void elem_free (struct list_entier *elem) { free(elem); }

Fort traditionnellement, nous utilisons comme paramtre de la procdure malloc le rsultat de lopration sizeof, qui seul nous garantit la portabilit du programme. Lalgorithme propose galement une opration de libration de la mmoire associe un lment. Nous verrons quil est possible de changer limplantation de ces deux oprations, elem_new et elem_free, sans modier en rien les autres algorithmes oprant sur des listes.

3.1. COURS

63

Important : Enn, gardons en mmoire le fait quune liste doit tre initialise, en gnral en affectant la variable qui la reprsente (de type pointeur sur lment) la valeur NULL. Llment tant cr, il convient maintenant de lintroduire dans la liste, lemplacement dsir. Cest l quil convient de noter lune des particularits des listes : tous les lments dune liste ne sont pas gaux en termes de manipulation et de temps daccs : le premier lment dune liste est directement accessible au travers du pointeur qui reprsente la liste ; le second lment nest accessible que depuis le pointeur qui se trouve dans le premier lment. Laccs llment de rang n ncessite de parcourir les n 1 premiers lments de la liste ; ce comportement est tout fait diffrent de celui des tableaux, dans lesquels le cot daccs un lment est indpendant de la taille du tableau et de la position de llment dans le tableau. Cet aspect est prendre en compte lors de lvaluation dalgorithmes mettant en uvre des tableaux ou des listes. Il est relativement simple dinsrer un lment en tte dune liste. Il suft que lancienne tte de liste devienne le suivant de llment insr, et que cet lment devienne la nouvelle tte de liste. Lalgorithme 5 ralise cette opration. Algorithme 5 Insertion en tte /* Inserer un element en tete */ struct list_entier * list_push (struct list_entier * L, struct list_entier *elt) { elt->suivant = L; return elt; } ... tete = list_push(tete,elem_new(12));

De mme, il est simple dinsrer un nouvel lment aprs un lment donn. Cette opration est dcrite par lalgorithme 6 page suivante. On peut proposer un algorithme gnral dinsertion dun lment en n de liste (on utilise souvent lexpression queue de liste ). Si la liste est vide, lalgorithme insrera llment en tte ; si la liste nest pas vide, lalgorithme insre llment aprs le dernier de la liste. On notera que cet algorithme impose toujours de raffecter la variable qui dsigne la tte de la liste, puisque cette variable est effectivement modie lorsque lon ajoute un lment une liste initialement vide. Lalgorithme 7 page suivante nous permet de crer une liste, contenant, dans lordre, les lments 10, 20 et 30, au travers dune suite dexpressions telle que : struct list_entier * tete;

64

CHAPITRE 3. LISTES

Algorithme 6 Insertion aprs un lment /* Inserer un element apres L */ void list_insert (struct list_entier *L,struct list_entier *elt) { elt->suivant = L->suivant; L->suivant = elt; }

Algorithme 7 Insertion en queue de liste /* Inserer en "queue" de liste */ struct list_entier *list_append (struct list_entier *L, struct list_entier * elt) { struct list_entier *cur; if (L == NULL) return list_push(L,elt); cur = L; while (cur->suivant != NULL) cur = cur->suivant; list_insert(cur,elt); return L; } ... tete = list_append(tete,elem_new(30));

3.1. COURS ... tete tete tete tete ...

65

= = = =

NULL; list_append(tete,elem_new(10)); list_append(tete,elem_new(20)); list_append(tete,elem_new(30));

Pourtant, cette criture nest pas optimale. La procdure list_append ncessite de traverser toute la liste pour aller dposer un nouvel lment son extrmit. Pour mettre en place n lments, il faut ainsi traverser 0, 1, puis 2, puis 3, etc, puis n 1 lments, soit au total (n1)n lments. Le temps (et donc le cot) de cration de la liste est donc proportionnel au carr 2 du nombre dlments. Si au contraire, nous utilisons le code suivant pour crer notre liste : struct ... tete = tete = tete = tete = ... list_entier *tete; NULL; list_push(tete, elem_new(30)); list_push(tete, elem_new(20)); list_push(tete, elem_new(10));

criture dans laquelle, notons-le bien, nous insrons en tte de la liste les lments dans lordre inverse de celui o nous voulons les voir apparatre dans la liste, nous pouvons acqurir la certitude que la mise en place de chaque lment est dune dure constante, indpendante de la taille de la liste, et que la cration de notre liste est ainsi proportionnelle au nombre dlments mettre en place. Ce code est donc, a priori, meilleur que le prcdent. Comment choisir le meilleur algorithme, comment valuer le cot dun programme ? Ces questions font lobjet dune branche particulire de lalgorithmique, qui est la mesure de la complexit. Nous allons dvelopper ci-aprs les grandes lignes de ce domaine.

3.1.3

Complexit

La complexit dun algorithme est lie son temps de calcul, son encombrement mmoire, etc. Il existe habituellement dans tout algorithme un ou plusieurs paramtres qui vont inuer directement sur ce temps de calcul ou cet encombrement mmoire. Lorsquil sagit par exemple de calculer la somme dun ensemble dlments, le nombre dlments de cet ensemble va clairement inuer sur le temps ncessaire pour effectuer ce calcul. La notation suivante, dite notation en grand O, introduite par D.E. Knuth ([3]), dnit des classes de fonctions : O(g) = {f : / c > 0, n0 , n n0 , f (n) c g(n)} Dire quune fonction f appartient la classe O(g), notation dans laquelle g reprsente une fonction au comportement connu, revient dire que le comportement de cette fonction f est born, un facteur prs, par celui de g.

66 Identication O(1) O(ln(n)) O(n) O(n ln(n)) O(n2 ) O(n3 ) O(2n ) O(n!)

CHAPITRE 3. LISTES Description Indpendant du nombre dlments Proportionnel au logarithme du nombre dlments Proportionnel au nombre dlments Proportionnel n ln(n) Proportionnel au carr du nombre dlments Proportionnel au cube du nombre dlments Exponentiel Factoriel

F IGURE 3.1 Table des complexits des algorithmes O(c f ) = O(f ) O(f + g) = O(f ) + O(g) O(f g) = O(f ) O(g) F IGURE 3.2 Oprations sur les complexits Ainsi, f (n) n O(n2 ), f (n) 431 n2 O(n2 ), mais f (n) n O(ln n). On / notera que la premire criture est exacte, mais quil est plus prcis dcrire : f (n) n O(n). En algorithmique, nous travaillons essentiellement avec des algorithmes dont le comportement dpend de la valeur ou du nombre dlments n de lune des entres. Si le temps dexcution dun algorithme est proportionnel cette valeur ou ce nombre dlments des donnes en entre, nous dirons que lalgorithme est en O(n). On pourra ds lors afrmer, par exemple, que si la taille des donnes traiter double, la dure de ce traitement sera galement double. Dans la pratique, nous nous intresserons aux classes de complexits dcrites dans le tableau 3.1, page 66. La complexit augmentant, et lalgorithme devenant donc plus mauvais , au fur et mesure que lon descend dans le tableau. Un peu de terminologie : on parlera dalgorithme logarithmique pour dsigner un algorithme dont la complexit est en O(ln n), dalgorithmes linaires, quadratiques ou exponentiels pour ceux dont la complexit est en O(n), O(n2 ) et O(2n ) respectivement.

3.1.4

valuation des complexits

Un algorithme se dcompose souvent en parties, juxtaposes ou imbriques, dont il est possible de calculer sparment les complexits. On peut tablir aisment les rsultats suivants, pour deux fonctions f et g, et une constante c, rsums dans la table 3.2, page 66. Enn, il convient en gnral dans une somme de ngliger la plus petite des complexits ; nous dirons quun algorithme est en O(n2 ) si le calcul nous apprend quil est en O(n) + O(n2 ). Pour nous donner une ide de linuence de la complexit sur le temps dun algorithme, le tableau 3.3, page 67, nous donne les dures dexcution de divers algorithmes, pour diverses tailles des donnes (de

3.1. COURS Algo. O(n) O(n ln(n)) O(n2 ) O(n3 ) O(n5 ) O(2n ) O(3n ) 10 0.0001 0.0002 0.001 0.01 1 0.0102 0.5905 20 50 80 100 0.0002 0.0005 0.0008 0.001 0.00060 0.00196 0.00351 0.00461 0.004 0.025 0.064 0.1 0.08 1.25 5.12 10 32 52 m 9h 27 h 10.4858 348 ans 4E+09 sic. 4E+15 sic. 9h 2E+09 sic. 5E+23 sic. 2E+33 sic.

67

F IGURE 3.3 Dures dexcution de certains algorithmes 10 100 lments), la dure dune opration lmentaire tant la mme dans tous les cas (105 secondes). Ces dures sont exprimes en secondes, sauf indication contraire (m pour minutes, h pour heure, ans et sic. pour sicles si ncessaire). On notera que certaines classes dalgorithmes deviennent vite inutilisables (cest un euphmisme) lorsque la taille des donnes augmente.

3.1.5

Complexit des oprations usuelles

Toutes les instructions dune machine (et, a fortiori, toutes les instructions dun langage de programmation de haut niveau) ne ncessitent pas la mme dure pour sexcuter. Une lvation la puissance, par exemple, est plus lente quune addition. Les diffrences entre les temps dexcution individuels peuvent tre considrables. Nous considrerons cependant que toutes ces oprations oprent en un temps born, indpendant des valeurs qui entrent en jeu dans lopration. Cest ainsi que toutes les instructions suivantes : a=2+3; delta=a*x*x+b*x+c; r=sqrt(eps)*(2-3*abs(p)); if (a>b) a=a-b; else b=a+b/2; sont considres comme des oprations en O(1), car toutes les variables intervenant dans les expressions sont des scalaires. En revanche, chaque instruction rptitive devra tre analyse avec soin, pour dterminer sa complexit. Ainsi : k=1; while (k<n) k=k*2; s=0; for (k=1; k<n; k++) s=s+k; s=0; for (k=1; k<n; k++) for (l=1; l<n;l++) s=s+1/(k+l-1); les trois boucles ci-dessus sont respectivement en O(log n), O(n) et O(n2 ) respectivement. La complexit du programme propos par lalgorithme 7 page 64, qui dcrit la liste pour ajouter un lment son extrmit, est clairement O(n). De mme, les deux exemples de cration de listes qui suivent cet algorithme savrent-ils en O(n2 ) et O(n).

68

CHAPITRE 3. LISTES

3.2
3.2.1

Travaux pratiques
Cration de liste

crire une procdure list_alea() permettant de construire une liste de P lments, chaque lment tant un nombre entier choisi alatoirement dans lintervalle [0 ; N [ (N et P sont les deux paramtres de la procdure, le rsultat tant ladresse de la liste construite). crire galement une procdure de destruction (libration de toutes les zones mmoires des lments) dune liste. crire un programme principal testant votre procdure avec comme paramtres N = 20 et P = 10000. Puis (aprs avoir vri que le programme fonctionne), modier le programme en utilisant la procdure clock(3) an de mesurer le temps dexcution du programme. Tester le temps de calcul pour de grandes valeurs de P (ne pas oublier de supprimer lafchage de la liste !). Quelle est la complexit de votre algorithme ? Note : on pourra rutiliser la procdure crite au dbut du cours permettant de gnrer un nombre entier alatoire selon une loi uniforme entre deux bornes A et B.

3.2.2

Recherche de nombre

crire une procdure elem_search() permettant de tester si une valeur (entire) est prsente dans une liste ; la valeur et ladresse de la liste sont les deux paramtres de la procdure, qui fournit un rsultat boolen (valeur numrique 1 ou 0) selon que la valeur cherche existe ou non dans la liste. crire un programme qui construit une liste alatoire de 100 valeurs entre 0 et 9999, puis qui tire des nombres au hasard entre 0 et 9999 jusqu en trouver un prsent dans la liste. Quelle est la complexit de votre algorithme ?

3.2.3

Suppression dun lment

crire une procdure elem_kill() permettant de supprimer un lment dans une liste (la valeur de llment et ladresse de la liste sont les deux paramtres de la procdure). Si llment nest pas prsent, la liste est inchange ; si llment est prsent plusieurs fois, on ne supprime que la premire occurrence. Le rsultat de la procdure est ladresse de la liste modie. Reprendre le programme de lexercice prcdent, et supprimer llment de la liste lorsquil a t trouv. Tester le programme en afchant la liste avant la suppression et la liste aprs la suppression. Quelle est la complexit de votre algorithme ?

3.2.4

Cration dune liste trie

crire maintenant une procdure qui construit une liste ordonne (par ordre croissant) de P lments pris alatoirement dans lintervalle [0 ; N [.

3.2. TRAVAUX PRATIQUES

69

NB 1 : la liste doit rester ordonne chaque lment ajout, il ne sagit pas ici de construire une liste non ordonne de P lments que lon ordonne ensuite ! NB 2 : on ne doit pas ajouter un lment qui est dj prsent dans la liste. Tester la procdure en crivant un programme qui construit une liste de 100 valeurs ordonnes entre 0 et 9999, puis qui lafche. Quelle est la complexit de votre algorithme ?

3.2.5

Gestion densemble

On se propose maintenant dcrire des procdures de manipulation densembles de nombres entiers, que lon reprsente par des listes ordonnes. crire les procdures suivantes : une procdure de cration dun ensemble de P lments, comportant des entiers alatoires choisis entre 0 et N 1 ; une procdure qui teste si un lment appartient un ensemble ; une procdure qui teste si deux ensembles sont gaux (contiennent les mmes lments). tudier pour chaque procdure sa complexit ! Essayer, le cas chant, dobtenir une complexit optimale. crire ensuite les procdures suivantes : une procdure de cration de lunion de deux ensembles ; au choix, cette procdure, an de construire son rsultat, pourra ou non dtruire les deux ensembles initiaux ; une procdure de cration de lintersection de deux ensembles ; au choix, cette procdure, an de construire son rsultat, pourra ou non dtruire les deux ensemble initiaux. Tester chaque procdure en lappliquant deux ensembles alatoires (voir procdures prcdentes) de 5 lments entre 0 et 9. Pour chaque procdure, indiquer la complexit de votre algorithme.

70

CHAPITRE 3. LISTES

Chapitre 4 Listes, les et piles


4.1
4.1.1

Cours
Algorithmique des listes

Les listes, nous lavons vu, prsentent nombre de particularits. Le tableau 4.1 compare quelques complexits doprations sur des tableaux et des listes. Ces diffrences sont sensibles lorsquil faut concevoir des algorithmes bien adapts aux structures de donnes quils doivent manipuler. Pour cette raison, nous insisterons chaque fois sur les complexits des solutions mises en uvre dans nos exemples. 4.1.1.1 Copie de liste

Recopier une liste consiste crer une liste similaire la liste de dpart (mme taille, mmes valeurs des lments), dont les lments soient physiquement diffrents de ceux de la liste originelle. Puisquil faut ainsi crer une copie dlment, voici, dcrite dans lalgorithme 8 page suivante, une procdure permettant de copier un lment dune liste. On notera dans cette procdure lexpression : * new = * elem; TABLE 4.1 Tableaux et listes Caractristique Tableau Liste Accs un lment O(1) O(n) Insertion en tte O(n) O(1) Balayage O(n) O(n) Insertion dlment O(n) O(n) ou O(1)

71

72

CHAPITRE 4. LISTES, FILES ET PILES

Algorithme 8 Copie dlment struct list_entier * elem_copy (struct list_entier * elem) { struct list_entier *new; new = (struct list_entier *) malloc (sizeof(struct list_entier)); new = * elem; * return new; }

dont le but est de recopier le contenu de la structure pointe par la variable elem dans la structure pointe par la variable new. Cette criture permet de recopier lensemble des champs de la structure. Cette procdure de recopie dlment tant dnie, on peut proposer des algorithmes de copie de liste . Lalgorithme dcrit en 9 dcrit une premire version de copie de liste. Cet Algorithme 9 Copie(1) de liste struct list_entier * list_copy1 (struct list_entier * li) { struct list_entier * cur, * res; cur = res = NULL; while (li != NULL) { cur = li; li = li->suivant; res = list_append(res, elem_copy(cur)); } return res; }

algorithme est n de lide quil sufsait de partir dune liste rsultat vide (ici, res), et de lui ajouter, en bout, les lments successifs de la liste initiale. Cependant, lon sait que lajout en bout de liste est une opration dont le temps dexcution est proportionnel la taille de la liste (ici, n lments au nal), et lon se rend compte que cette opration va tre rpte autant de fois quil y a dlments, n galement. La complexit de lalgorithme sera donc O(n2 ), ce qui semble, intuitivement, trop coteux pour une simple opration de recopie ! Lalgorithme 10 page ci-contre propose une version dont la programmation est moins vidente, mais dont la complexit est simplement O(n).

4.1. COURS Algorithme 10 Copie(2) de liste struct list_entier * list_copy2 (struct list_entier *li) { struct list_entier * cur, * res, * elt, * prev; cur = res = NULL; while (li != NULL) { cur = li; li = li->suivant; elt = elem_copy(cur); elt->suivant = NULL; if (res == NULL) res = elt; else prev->suivant = elt; prev = elt; } return res; }

73

F IGURE 4.1 Liste initiale 4.1.1.2 Rversion dune liste

Lalgorithme 11 page suivante dcrit une procdure de rversion physique dune liste . Les lments de la liste sont rarrangs pour constituer une nouvelle liste, dans laquelle ils sont rangs dans lordre inverse. On notera quaprs appel de la procdure, la tte de lancienne liste pointe sur le dernier lment de la nouvelle liste. Les gures 4.1 et 4.2 page suivante montrent la rversion dune liste de quatre lments. 4.1.1.3 Tri de listes

Lune des oprations importantes sappliquant des listes (contenant des lments sur lesquels existe une relation dordre) est le tri. On imaginera ici que dans la suite du cours cette notion de tri reprsente (sauf indication contraire) un tri numrique dentiers par valeur croissante, mais ceci nest en rien une restriction sur la gnralit des oprations dcrites ici.

74

CHAPITRE 4. LISTES, FILES ET PILES

Algorithme 11 Rversion dune liste struct list_entier *list_reverse (struct list_entier * li) { struct list_entier * cur, * prev; cur = prev = NULL; while (li != NULL) { cur = li; li = li->suivant; cur->suivant = prev; prev = cur; } return prev; }

F IGURE 4.2 Liste retourne

4.1. COURS

75

F IGURE 4.3 Listes avant fusion

F IGURE 4.4 Listes aprs fusion Nous allons dcrire ici un algorithme classique, qui est le tri par fusion. Les tapes sont les suivantes : lalgorithme prend une liste dlments en entre ; si la liste dentre est vide, ou comporte un lment unique, elle est donc, de fait, trie, et cette liste est fournie comme rsultat ; si la liste comporte plus dun lment, elle est partage en deux sous-listes ; lalgorithme de partage est lui-mme peu important ; il suft que ( 1 prs), les deux sous-listes rsultantes aient le mme nombre dlments ; chacune de ces sous-listes est trie (par un appel rcursif de la procdure) ; les deux sous-listes rsultantes (qui sont donc tries) sont fusionnes pour obtenir une liste unique, trie, qui est le rsultat de la procdure. Lopration de fusion consiste fusionner deux listes tries, pour obtenir une liste rsultante qui contiendra les lments de ces deux listes. Les gures 4.3 et 4.4 donnent un exemple de la fusion de deux listes telle que dcrite ici. Si lon raisonne en terme de profondeur au niveau de la rcursion, lappel initial de la procdure est lappel, unique, de niveau 1, sur une liste de taille n. Il effectue lui-mme deux appels de niveau 2, sur des listes de taille n/2. Il y aura 4 appels de niveau 3, sur des listes de taille n/4, huit appels de niveau 4, sur des listes de taille n/8, et ainsi de suite. Puisque le

76

CHAPITRE 4. LISTES, FILES ET PILES

processus sinterrompt ds quune liste est de taille infrieur ou gale 1, il y a, au maximum, ln(n) niveaux dappel. Au niveau 1, la procdure coupe la liste initiale en 2, ce qui fait manipuler n lments, effectue les deux appels rcursifs, puis fusionne les rsultats pour obtenir la liste trie nale. Il y a galement n lment fusionner. Ce niveau 1 a donc une complexit en O(n). Le mme raisonnement permet de voir que le second niveau consiste en 2 appels de procdures, qui vont elles-mmes manipuler n/2 lments chacune. La complexit de ce second niveau est galement O(n). On peut poursuivre ce mme raisonnement jusquau dernier niveau, o n procdures sexcutent, manipulant chacune un seul lment. Pour chaque niveau, la complexit est donc O(n). Puisquil y a ln(n) niveaux, la complexit de lalgorithme est donc O(n ln(n)). Pour le coin de la culture : on vient dexhiber un algorithme de tri dont la complexit est O(n ln(n)), ce qui est bien meilleur que le tri par insertion dont la complexit est O(n2 ). Peuton encore faire mieux ? Eh bien non ! Si lon suppose que la seule opration que lon peut effectuer est la comparaison de deux lments (savoir si le premier lment est infrieur, gal ou suprieur au second), alors tout algorithme de tri a une complexit au moins gale O(n ln(n)). Cest donc un algorithme optimal (ce nest pas le seul, il existe dautres algorithmes ayant la mme complexit).

4.1.2

Pile

Une pile est une structure de donnes, permettant de grer (ajouter et retirer) dynamiquement des objets de nature homogne, avec les proprits suivantes : il est possible de tester si une pile est vide ; il est possible dajouter un objet ; il est possible dextraire un objet dune pile non vide ; lobjet extrait est celui ajout le plus rcemment la pile. Limage dune srie de pices de monnaies empiles les unes sur les autres donne une bonne ide des proprits dune pile. Les piles sont souvent ralises au moyen de listes chanes : empiler un lment consiste lajouter en tte de la liste, dpiler le retirer de la tte de la liste. Ces deux oprations ont une complexit en O(1), et lallocation dynamique des lments de la liste satisfait le critre de dynamisme dune pile. On dit souvent quune pile travaille en LIFO (last in, rst out), autrement dit dernier entr, premier sorti .

4.1.3

File

Une le est une structure de donnes qui permet de grer dynamiquement (ajouter et retirer) des objets de nature homogne, avec les proprits suivantes : il est possible de tester si une le est vide ; il est possible dajouter un objet ;

4.2. TRAVAUX PRATIQUES

77

F IGURE 4.5 File il est possible dextraire un objet dune le non vide ; lobjet extrait est celui ajout le plus anciennement la le. Les les sont souvent utilises pour modliser des les dattentes, dans lesquelles le premier arriv est le premier servi. On dit souvent quune le travaille en FIFO (rst in, rst out), autrement dit premier entr, premier sorti . Une le est souvent modlise au moyen dune liste ; selon la convention choisie : on ajoute en tte et lon retire en queue ; on ajoute en queue et lon retire en tte. Cependant, ces deux ralisations prsentent le dfaut que la complexit de lune des oprations (celle qui fait intervenir la queue de la liste) est en O(n). Pour cette raison, on associe souvent la reprsentation de la liste un pointeur supplmentaire, qui dsigne la queue de la liste. La gure 4.5 symbolise la reprsentation dune telle le. Grce ce pointeur, dont le rle est de toujours dsigner le dernier lment, il est possible dajouter un nouvel lment en O(1). La reprsentation prfre pour les les est donc une liste, avec un pointeur supplmentaire sur la queue de la liste, dans laquelle on ajoute en queue et lon retire en tte.

4.2
4.2.1

Travaux pratiques
Tri de liste

On se propose dimplanter lopration de tri par fusion sur des listes, telle que dcrite en 4.1.1.3. crire et tester une procdure list_sort, prenant comme paramtre une liste (en fait, ladresse du premier lment de cette liste) et rendant comme rsultat (ladresse de) la liste trie. La procdure sera probablement complte dune autre procdure, list_merge, qui prendra comme paramtres deux listes tries, et fournira comme rsultat la fusion des deux listes. Il est vraisemblable quil soit aussi ncessaire dcrire une procdure list_split qui permet de couper une liste en deux sous-listes de tailles identiques ( 1 prs).

4.2.2

Ralisation de les

On se propose de raliser une implantation de les de nombres entiers. Pour ceci, on utilisera une liste, dont on dcrira les lments de la faon habituelle. La le elle-mme sera dcrite au moyen dune nouvelle structure, que lon dnira, et qui contiendra deux pointeurs sur des

78

CHAPITRE 4. LISTES, FILES ET PILES

lments de liste, pointeurs qui dsigneront la tte et la queue de la liste. Les lments sont reprsents par des pointeurs sur des structures, et sont ceux qui sont manipuls par les opration de cration et de libration dlments (cf. algorithme 4 page 62). On implantera les quatre oprations suivantes : queue_new cration dune le dentiers ; procdure sans paramtres, dont le rsultat sera un pointeur sur la structure reprsentant la le. queue_empty opration indiquant si une le est vide ou non ; opration prenant comme paramtre un pointeur sur une le (tel que rendu par queue_new), et rendant un boolen (cest--dire un entier 0 ou 1). queue_insert opration ajoutant une le (premier paramtre de la procdure) un lment (dni par son pointeur, second paramtre de la procdure). queue_extract opration retirant dune le (premier paramtre de la procdure) llment le plus anciennement introduit. Le rsultat est un pointeur sur llment. On testera ces oprations (en vriant le contenu des les au moyen des oprations de manipulation de listes dnies au chapitre 3).

Chapitre 5 Graphes
5.1
5.1.1

Cours
Introduction

Un graphe est une structure combinatoire qui se compose dun ensemble de points, dits sommets, et dun ensemble darcs, dits aussi artes, un arc reliant deux sommets. On peut dnir mathmatiquement un graphe par un ensemble X et une partie A X X. Les lments de X sont les sommets, et lon dit que larte (ou larc) (x, y), pour x et y dans X, appartient au graphe si (x, y) A. On peut galement dire quil sagit dune relation binaire sur X. On notera G = (X, A) le graphe ainsi dni. En gnral, les graphes traits informatiquement sont nis, cest--dire que lensemble X est ni (ce qui implique que lensemble A soit galement ni). Les graphes permettent de reprsenter nombre de situations fort diverses. Il en rsulte que les problmes de reprsentation et de recherche en graphes sont nombreux et complexes. En logistique, par exemple, on peut reprsenter un rseau de transport (routier, ferroviaire, arien, uvial, mixte) par un graphe : les sommets sont les points de chargement ou de dchargement (ainsi que les points de connexion ou dinterconnexion des rseaux), les artes reprsentent les voies de transport. En thorie des jeux, on peut reprsenter les positions possibles du jeu (les congurations des pions dun jeu de solitaire, par exemple) par un sommet dun graphe, lexistence dun arc reliant un sommet A du graphe un sommet B exprimant le fait que lon passe de la conguration A la conguration B en jouant un coup sur le solitaire. Rsoudre un problme de solitaire revient trouver dans ce graphe un chemin passant de la conguration initiale la conguration nale. Le problme, sachant que le solitaire (anglais) comporte 33 cases pouvant ou non tre occupes par un pion est que ce graphe est susceptible davoir 233 sommets 1 , ainsi quun trs grand nombre dartes, et nest pas reprsentable dans une machine actuelle ; il est encore moins question de le parcourir en des temps raisonnables. . . Les graphes auxquels nous nous intresserons dans ce chapitre sont les graphes :
1. En ralit, beaucoup moins dans le cas o la conguration initiale est celle o lon a retir le pion central et o lon ne reprsente que les tats susceptibles dtre effectivement atteints au cours du jeu.

79

80
3 4 5

CHAPITRE 5. GRAPHES

F IGURE 5.1 Exemple de Graphe non orients, o lon confond larte (x, y) avec larte (y, x) (on parle alors darc (x, y)) ; on parle galement de graphe symtrique ; ou graphes orients, o les artes (x, y) et (y, x) sont considres distinctes ; simples, ce qui veut dire quil existe au plus une arte dun sommet A un sommet B 2 . La gure 5.1 donne une reprsentation typique dun graphe orient. Enn, notons le fait, important lors du choix des algorithmes et de la reprsentation, quun graphe peut tre dense (le nombre dartes issues dun sommet nest pas petit devant le nombre n de sommets) ou clairsem (sparse en anglais), le cas inverse. Lorsque lon raisonne en terme de complexit, on peut imaginer que le nombre dartes dun graphe dense est proportionnel n2 et celui dun graphe clairsem n.

5.1.2

Concepts associs

Voici quelques concepts lis aux graphes, et la terminologie associe. arc liaison oriente (x, y) dun sommet x un sommet y dun graphe orient. arte liaison non oriente entre un couple de sommets dun graphe non orient. ordre lordre dun graphe est le nombre de sommets du graphe. adjacent deux sommets sont adjacents sil existe un arc joignant ces deux sommets (graphes non orients). Pour des graphes orients, on utilise de prfrence les termes de prdcesseur et de successeur. chemin cest une suite oriente darcs tous diffrents liant un sommet un autre sommet dun graphe orient. chane suite dartes toutes diffrentes liant un sommet un autre sommet dun graphe non orient. distance cest le nombre minimum darcs (ou artes) que lon doit traverser pour passer dun certain sommet un autre sommet ; autrement dit, cest la longueur du plus court chemin liant le sommet de dpart au sommet darrive. circuit cest un chemin de longueur non nulle allant dun sommet vers lui-mme (dans un graphe orient).
2. Il peut naturellement exister, en outre, une arte allant du sommet B vers le sommet A.

5.1. COURS

81

cycle cest une chane de longueur non nulle allant dun sommet vers lui-mme (dans un graphe non orient). Le fait quil existe une arte entre les sommets x et y, et donc que lon puisse aller de x lui-mme en passant par y ne constitue pas un cycle, puisque cette chane utiliserait deux fois la mme arte. acyclique un graphe acyclique ne contient pas de cycle. connexit un graphe est connexe si pour tout couple (A, B) de sommets distincts, il existe au moins un chemin de longueur nie ayant A pour origine et B pour extrmit. diamtre le diamtre dun graphe est la plus longue des distances entre tous les couples possibles de sommets. degr le degr dun sommet est le nombre darcs arrivant ce sommet (degr entrant) ou partant de ce sommet (degr sortant). Dans un graphe non orient, le degr dun sommet est le nombre dartes issues de ce sommet. valu un graphe est dit valu si des valeurs numriques sont associes aux artes. Ces valeurs sont en principe positives. Le cot dun chemin peut ainsi tre calcul comme la somme des valeurs des arcs le composant.

5.1.3

Reprsentation des graphes

Il existe deux reprsentations classiques pour les graphes, dites par matrice dadjacence, et par liste dadjacence. 5.1.3.1 Matrice dadjacence

Dans cette reprsentation, le graphe est reprsent par une matrice boolenne (i.e. ne contenant que des 0 et des 1) M de taille N N , N tant le nombre de sommets du graphe. Les sommets sont numrots (par exemple de 0 N 1), et llment M [i, j] indique, par sa valeur (1 ou 0) sil existe ou non un arc du sommet i vers le sommet j. Quel que soit le nombre darcs, la reprsentation par matrice boolenne occupe, en choisis2 sant la reprsentation la plus compacte, N 2 bits, soit N octets. 8 5.1.3.2 Liste dadjacence

Dans cette reprsentation, on conserve pour chaque sommet une liste des sommets qui lui sont adjacents. En C, on peut choisir de reprsenter les sommets, numrots de 0 N 1, par un tableau de N pointeurs sur les listes dadjacence. Dans une telle liste, on peut utiliser une structure pour chaque arc, dans laquelle on reprsente le sommet vers lequel mne larc, par exemple par un entier reprsentant le numro de ce sommet, et bien sr un pointeur vers le sommet adjacent suivant. Une telle reprsentation ncessite typiquement 8 octets par arc (4 octets pour reprsenter le numro du sommet, 4 octets pour reprsenter le pointeur vers le suivant). Si N est le nombre de sommets, et A le nombre darcs, la taille en mmoire est au minimum de 4N + 8A octets. Si le graphe reprsenter est xe (na pas voluer au cours du calcul), on peut reprsenter les sommets adjacents un sommet donn par les lments successifs dun tableau, suivi dun

82

CHAPITRE 5. GRAPHES

marqueur de n. Si le graphe a moins de 216 sommets, ces lments peuvent tre des entiers 16 bits, soit 2 octets par arc, plus le marqueur de n, 2 octets par sommet. La taille mmoire est alors 6N + 2A octets. 5.1.3.3 Comparaison des reprsentations

Tailles des reprsentations : La reprsentation par liste, pour des graphes de petites tailles ou denses, est coteuse devant la reprsentation par matrice. Au contraire, si le graphe a un nombre de sommets important, et est relativement clairsem, la reprsentation par liste dadjacence est plus intressante. Cot de lutilisation : Il est un peu coteux ( cause des manipulations dextraction de bits) daller retrouver llment M [i, j] dans une matrice boolenne ; cependant un tel accs est en O(1). Savoir sil existe un chemin entre i et j avec une reprsentation par listes ncessite de parcourir la liste dadjacence du sommet i. Si le graphe est clairsem, le nombre darcs issus dun sommet est trs faible, et lon peut considrer que cette recherche est en O(1). Si ce nest pas le cas (graphe dense), la longueur de la liste peut tre considre comme proportionnelle au nombre de sommets, et la complexit de cette opration sera value en O(n). Inversement, si un algorithme a besoin de trouver tous les arcs issus dun sommet, cette recherche est en O(n) pour la reprsentation par matrice (il faut balayer toute la ligne de la matrice), mais en O(1) dans le cas de la reprsentation par listes pour un graphe trs clairsem. Le problme inverse, trouver les chemins qui mnent un sommet donn est simple pour la reprsentation par matrice (O(n)), mais plus coteux pour la reprsentation par listes pour des graphes denses (O(n2 )). Autres aspects : Si le graphe doit voluer au cours du temps, il est clair que la reprsentation par matrice permet dajouter ou de supprimer aisment des arcs. En revanche, ajouter ou supprimer des sommets ncessite une recopie de la matrice, opration qui est en O(n2 ). La reprsentation par listes se prte mieux lajout ou la suppression de sommets, bien quil soit alors ncessaire de choisir des reprsentations plus complexes, donc plus coteuses, si lon veut pouvoir effectuer de manire performante ces oprations. Choix : Le choix de la meilleure reprsentation dpend donc de nombre de paramtres, qui sont lis certes laspect du graphe qui doit tre manipul, mais galement nombre dautres considrations qui peuvent tre lies aux algorithmes appliquer, ou encore aux procdures dont on dispose.

5.1.4

Problmes sur graphes

La thorie des graphes sintresse nombre de problmes abstraits, qui leur tour reprsentent des situations et des problmes tout--fait concrets. Voici quelques exemples de problmes sur graphes.

5.1. COURS 5.1.4.1 Parcours de graphe

83

On sintresse numrer tous les sommets accessibles depuis un sommet donn. Ce parcours peut seffectuer en profondeur (on va le plus vite possible le plus loin possible du sommet de dpart) ou en largeur (il convient dnumrer en premier tous les sommets une distance 1 du sommet de dpart, puis ceux qui sont situs une distance deux, etc). Ce problme dnumration est extrmement frquent : recherche de chemin, de plus court chemin, jeux, etc. 5.1.4.2 Fermeture transitive

La fermeture transitive dun graphe G = (X, A) est la relation transitive minimale contenant la relation (X, A). Il sagit dun graphe G = (X, A), tel que (x, y) A si et seulement sil existe un chemin dans G dorigine x et dextrmit y. Ce problme se rsout typiquement au moyen dun produit boolen de matrices. Le calcul de G seffectue par itration dune opration qui ajoute A les arcs (x, y) tels quil existe un z qui a x pour prdcesseur et y pour successeur. 5.1.4.3 Arbre de recouvrement

Un arbre de recouvrement, ou arbre recouvrant est un graphe partiel, sous-ensemble dun graphe non orient connexe, possdant lune des proprits suivantes (quivalentes) : le graphe est connexe, mais cesse de ltre ds que lon supprime lune des artes ; le graphe est connexe et possde n sommets et n 1 artes ; il existe une chane et une seule joignant deux sommets quelconques ; le graphe possde n sommets et n 1 arcs, et aucun cycle. Les gures 5.2 et 5.3 page suivante montrent un graphe connexe et lun des arbres de recouvrement associs. Un exemple typique dutilisation dun arbre de recouvrement est celui de lalimentation en eau dun btiment, ou dun quartier. Les sommets du graphe reprsentent les points deau alimenter, les arcs les endroits o il est possible de faire passer des tuyaux. Calculer larbre de recouvrement associ au graphe permet de trouver une solution au problme de lalimentation des points deau. Dans ce type de problme, on utilise souvent des graphes valus, cest--dire que lon associe des cots aux arcs (par exemple, la longueur physique de larc), et on cherche un arbre de recouvrement de cot minimal. 5.1.4.4 Circuits

On recherche la prsence de circuits (cycles) dans un graphe, cest--dire de chemins de longueur non nulle liant un sommet lui-mme. Une application pratique est celle de la planication de la ralisation dun projet complexe, par exemple la construction dun btiment. Cette ralisation fait appel nombre doprations diffrentes (ex : construction des murs, pose de la plomberie, de llectricit), oprations qui sont ventuellement dpendantes les unes des autres par des contraintes dantriorit : ainsi, on ne peut gure envisager de poser la plomberie avant davoir achev les murs. Chaque opration peut

84

CHAPITRE 5. GRAPHES

F E A B C D

F IGURE 5.2 Exemple de Graphe connexe


G

F E A B C D

F IGURE 5.3 Un arbre de recouvrement du graphe 5.2

5.1. COURS
1

85

10

11

12

F IGURE 5.4 Exemple darbre tre reprsente par le sommet dun graphe, un arc allant de A vers B indiquant que lopration A doit tre acheve avant que lopration B puisse dbuter. Le graphe obtenu doit tre acyclique ; dans le cas contraire, cest quil existe un problme grave dans la ralisation. . . Rappel : sauf dans certains algorithmes (lis par exemple aux chanes de Markov), on considre quil nexiste pas darc joignant un sommet lui-mme.

5.1.5

Arbres

Les arborescences, souvent appeles improprement arbres constituent un sous-ensemble des graphes 3 . Un arbre est un graphe orient connexe, ayant les proprits suivantes : il existe un sommet unique, dit racine de larbre, nayant aucun prdcesseur ; chaque sommet autre que la racine a un prdcesseur unique ; pour chaque sommet, il existe un chemin unique menant de la racine ce sommet. Les arbres sont souvent reprsents par un schma dans lequel on place la racine en haut ou gauche (cf. gure 5.4). Bien que les algorithme relatifs aux graphes puissent sappliquer aux arbres, il existe souvent, compte tenu des proprits particulires des arbres, des algorithmes qui leur sont spciques 4 . Ainsi, chaque sommet ayant au plus un prdcesseur, un arbre n sommets peut tre reprsent par un vecteur de n lments, associant chaque sommet son prdcesseur.
3. On considre quun arbre est un graphe non orient, comprenant des sommets et des artes (cf. par exemple les arbres de recouvrement , 5.1.4.3), et quune arborescence est un graphe orient, constitu de sommets et darcs. 4. On peut de mme considrer que les listes constituent un sous-ensemble des arbres, donc des graphes, mais algorithmes sur listes sont naturellement mieux adapts que des algorithmes plus gnraux sappliquant aux arbres !

86
4 5

CHAPITRE 5. GRAPHES

F IGURE 5.5 Exemple de graphe

5.2
5.2.1

Travaux pratiques
Reprsentation de graphes

On se proposer de construire un ensemble de procdures permettant la reprsentation et la manipulation de graphes. On se limitera des graphes de 256 sommets au maximum. La reprsentation adopte sera celle dune matrice dadjacence, avec les conventions suivantes : un graphe sera reprsent par une structure (struct graph) contenant lordre du graphe (un entier), et une matrice de caractres 5 m de taille 256 par 256. on utilisera chaque caractre pour reprsenter un arc, la valeur 1 de m[i][j] indiquant quil existe un arc reliant i j, la valeur 0 quil nen existe pas. les sommets seront numrots de 1 N . On crira les procdures suivantes : graph_new procdure qui rend un pointeur vers une structure nouvellement alloue. struct graph * graph_new (int ordre); Le graphe ainsi cr ne contiendra aucun arc. graph_print procdure qui imprime une vision (avec des 0 et des 1) de la matrice dadjacence du graphe pass en paramtre (un pointeur). void graph_print (struct graph *); graph_arc procdure indiquant sil existe un arc menant de i j. Les nombre i et j sont des entiers compris entre 1 et n, ordre du graphe. int graph_arc (struct graph *g, int i, int j); graph_arc_set procdure crant ou dtruisant larc menant de i j selon que k vaut 1 ou 0. void graph_arc (struct graph *g, int i, int j, int k); partir de ce premier ensemble de procdures, on crira une procdure permettant de lire la reprsentation dun graphe depuis un chier texte. Cette reprsentation se composera dune premire entre indiquant lordre du graphe, puis dune entre par sommet [numrots de 1 N ] indiquant les numros de sommets vers lesquels il existe un arc depuis le sommet courant.
5. Cette convention est moins efcace en termes dencombrement de la mmoire, puisque lon utilise 8 bits pour reprsenter lexistence dun chemin, alors quen principe un bit suft ; elle simplie cependant grandement la programmation, et rend un peu plus efcaces les procdures de manipulation du graphe.

5.2. TRAVAUX PRATIQUES

87

Les entres seront spares les unes des autres par le caractre ; . Des blancs et passages la ligne pourront tre insrs si dsir. Ainsi, le graphe de la gure 5.5 page ci-contre sera reprsent par un chier ayant le contenu suivant : 5 2 4;3 4;;5;2 3 On notera quune entre vide dans un tel chier indique quil nexiste aucun arc issu du sommet correspondant.

5.2.2

Recherche de circuit

On se propose dimplanter un algorithme de recherche de circuit dans un graphe orient. Pour ceci, on peut utiliser lalgorithme suivant : partir de lensemble X des sommets et A des arcs ; rpter lopration suivante tant quil reste des sommets dans X ; rechercher les sommets qui nont pas de prdcesseurs, les retirer de X. retirer de A les arcs issus de ces sommets Sil reste des sommets dans X, mais quil ny a plus de sommets sans prdcesseur 6 , cest quil existe au moins un circuit dans G. crire la procdure qui dtermine sil existe des circuits dans un graphe donn en paramtre.

5.2.3

Calcul de connexit

On se propose de concevoir un algorithme permettant de dcouvrir, partir de la matrice dadjacence dun graphe, sil existe des chemins de longueurs 2, puis 3, 4, 5, etc dans le graphe. En dduire le calcul de la fermeture transitive du graphe. Quel est la complexit des algorithmes ainsi raliss ?

6. De tels sommets sans prdcesseur sont dits sources ; de mme, les sommets sans successeurs sont dits puits (anglais sink).

88

CHAPITRE 5. GRAPHES

Chapitre 6 Algorithmes sur graphes


6.1
6.1.1

Cours
Parcours de graphe

Dans nombre de problmes, le parcours de graphe revt une importance essentielle. On peut par exemple dsirer trouver lensemble des sommets accessibles depuis un sommet donn (lexercice 5.2.3 page 87 fournit un algorithme permettant deffectuer ce calcul, mais sa complexit est leve), ou encore numrer lensemble des sommets accessibles depuis un sommet donn, etc. 6.1.1.1 Parcours en profondeur

Nous allons dcrire ici un parcours de graphe en profondeur. Cet algorithme sert de support la rsolution dune grande classe de problmes, qui peuvent tre lis aussi bien aux arbres quaux graphes. Un parcours en profondeur consiste partir dun sommet donn dun graphe (ou de la racine, dans le cas dun arbre), et numrer ses successeurs, puis, choisissant lun de ces successeurs, numrer les successeurs de celui-ci, etc. Cest lopration de descente dans le graphe. Lorsquil nest pas possible daller de lavant, on revient en arrire, on choisit un autre successeur, et lon effectue une nouvelle descente, jusqu ce que tous les chemins possibles issus du sommet de dpart aient t numrs. On effectuera la slection des successeurs ou le test darrt en fonction des besoins. En particulier, si le graphe comporte des circuits, certains chemins peuvent tre de longueur innie ; il convient alors, en fonction des besoins de lalgorithme, de prendre les prcautions ncessaires pour viter ce cas de gure, par exemple en veillant ce que chaque sommet soit utilis une fois et une seule au cours de la descente. Ce problme ne se pose pas dans le cas des arbres, o il ny a pas de circuit et o chaque noeud est accessible par un chemin et un seul depuis la racine. Notons enn que dans nos descriptions dalgorithmes, nous utilisons un ensemble de procdures dont les spcications sont dcrites dans lexercice 5.2.1 page 86, et qui sont accessibles sur le site : http://kiwi.emse.fr/POLE/courssda.html 89

90 6.1.1.2 Choix des structures de donnes

CHAPITRE 6. ALGORITHMES SUR GRAPHES

Si lon part du principe quun sommet ne sera explor quune fois au cours de la descente, un tableau pil de taille n, n tant lordre du graphe, suft conserver la liste des sommets explors. chaque sommet, il convient dassocier la liste des successeurs quil reste explorer, soit, au maximum, n entres pour chaque sommet. Une matrice pos de taille n n convient pour reprsenter cette structure. Une variable pt indique le niveau atteint. Enn, s est le sommet de dpart de notre recherche. 6.1.1.3 Algorithme gnral de parcours

Voici une premire bauche de lalgorithme. Commenons par quelques structures de donnes : int int int int pos[NMAX][NMAX]; pil[NMAX]; pt,succ; i, j, c;

Lalgorithme dmarre avec le sommet de dpart, s. On le place dans la pile. On utilise un indicateur, succ (comme succs , ou encore il existe un successeur ), initialement positionn 1 puisque lon va explorer les successeurs du sommet s. pt = 0; pil[pt] = s; succ = 1; Lalgorithme consister boucler sur la descente. Au cours de celle-ci, on empile les sommets rencontrs. Si lon ne peut pas aller plus loin, on va dpiler, pour explorer dautres successeurs du sommet parent. Une fois tous les trajets essays depuis s, lalgorithme va dcrmenter pt, le rendant ngatif ; do le test de poursuite de la boucle. while (pt>=0) { if (succ != 0) { La pile contient un sommet courant c, dont on va chercher les successeurs. Tel que programm ci-dessous, lalgorithme conserve tous les successeurs du sommet c. Il faut naturellement tre plus restrictif, en fonction du but recherch, pour dcider si tous les successeurs conviennent ou non ; en particulier, comme expliqu ci-dessus, on peut dcider de ne retenir que les sommets qui nont pas encore t rencontrs au cours de cette descente. c = pil[pt]; i = 0; for (j=1; j<=n; j++) { if (graph_arc(g,c,j)) { /* Decider ou non si le sommet convient */ pos[pt][i] = j; i++;

6.1. COURS } } for ( ; i<n; i++) pos[pt][i] = 0; }

91

On va maintenant choisir le premier successeur disponible au niveau courant. Si lon en trouve un (succ est alors diffrent de 0), on va lempiler, puis continuer la boucle pour chercher ses propres successeurs, non sans lavoir retir de la liste des successeurs explorer. for (i=0; i<n && ((succ=pos[pt][i])==0); i++) { } if (succ != 0) { pos[pt][i] = 0; /* eliminer succ */ pt++; pil[pt]=succ; } Si lon a puis tous les successeurs de ce niveau, on revient au parent en dcrmentant le pointeur pt. Comme on la signal, le test de la boucle gnrale permet darrter lalgorithme lorsque pt devient ngatif. else { pt--; } } /* Fin */ 6.1.1.4 Exemple : recherche de chemin

Imaginons que notre algorithme consiste chercher les chemins entre s et d, sommet de destination. Nous considrerons que pour un sommet intermdiaire k, un successeur est tudier sil est diffrent de d, et sil na pas dj t atteint au cours de la descente, autrement dit sil nest pas dans les pt + 1 premiers lments de pil. Si lun des successeurs est d prcisment, nous avons trouv un chemin de s d, qui passe par les sommets dont les numros sont dans les pt + 1 premiers lments de pil. Voici donc la version approprie du choix des successeurs : if (graph_arc(g,c,j)) { int p; if (j == d) { /* on a trouve un chemin ; on limprime */ for (p=0; p<=pt; p++) printf("%d ", pil[p]); printf("%d\n", d); }

92

CHAPITRE 6. ALGORITHMES SUR GRAPHES else { int incl = 1; /* Sommet deja trouve ? */ for (p=0; p<=pt; p++) if (pil[p] == j) incl = 0; if (incl) { pos[pt][i] = j; i++; } } }

Lalgorithme ci-dessus imprime tous les chemins trouvs entre s et d. Il serait possible, lorsquun chemin est rencontr, de ne conserver que le plus court, et de limprimer la n. 6.1.1.5 Exemple : sommets accessibles depuis un sommet donn

Si notre but est de construire la liste des sommets accessibles depuis un sommet donn, nous pouvons utiliser un tableau n entres, vus, qui indiquera pour chaque sommet sil a dj t rencontr dans lexploration. int vus[NMAX+1]; for (i=0; i<=n; i++) vus[i] = 0; vus[s] = 1; La procdure de slection des successeurs consiste simplement vrier que le sommet na encore t jamais rencontr au cours de toute lexploration de larbre ; si le sommet a dj t rencontr, ses descendants ont t explors (ou vont ltre), et il nest pas utile de le faire nouveau. Lopration de slection des descendants scrit alors : if (graph_arc(g,c,j) && (!vus[j])) { pos[pt][i] = j; vus[j]=1; i++; } Lorsque lexploration du graphe est achev, on peut par exemple imprimer la liste des sommets rencontrs, et donc accessibles depuis le sommet s : /* Imprimer la liste des sommets accessibles */ for (i=1; i<=n; i++) if (vus[i]) printf("%d ", i); print("\n"); Comme le montre ce programme, la diffrence avec lalgorithme prcdent rside uniquement dans le choix des successeurs explorer ; ici un sommet est conserv seulement sil na encore jamais t rencontr au cours de lalgorithme. Ce mme algorithme pourrait tre modi aisment an de construire un arbre de recouvrement du graphe.

6.2. TRAVAUX PRATIQUES

93

6.2
6.2.1

Travaux pratiques
Fermeture transitive

On se propose de construire la fermeture transitive dun graphe G, cest--dire le graphe G dans lequel un arc entre i et j indique quil existe dans G un chemin entre i et j. Lexercice 5.2.3 page 87 proposait une premire mthode de calcul de la fermeture transitive dun graphe, travers une squence de produit boolen de matrice (cf. 5.1.4.2 page 83). Cet algorithme prsente linconvnient dtre en O(n4 ). On se propose dimplmenter lalgorithme de Roy-Warshall pour la recherche de la fermeture transitive dun graphe reprsent par sa matrice dincidence M , qui consiste , pour tout k tel que M [i, k] et M [k, j], ajouter larc (i, j) la matrice. On implmentera lalgorithme, et on calculera sa complexit.

6.2.2

Recherche de circuit

On se propose de rechercher la prsence de circuits dans un graphe G. Lopration de fermeture transitive permet de dtecter lexistence de tels circuits, puisque sil existe de 1 sur la diagonale de la matrice reprsentant le graphe de la fermeture transitive de G, ceci indique quil existe un chemin allant dun sommet lui-mme, et donc un circuit. On peut cependant proposer un algorithme en gnral plus performant, qui est le suivant : partir de lensemble X des sommets et A des arcs ; rpter lopration suivante tant quil reste des sommets dans X ; rechercher les sommets qui nont pas de prdcesseurs, les retirer de X. retirer de A les arcs issus de ces sommets Sil reste des sommets dans X, mais quil ny a plus de sommets sans prdcesseur 1 , cest quil existe au moins un circuit dans G. crire la procdure qui dtermine sil existe des circuits dans un graphe donn en paramtre.

1. De tels sommets sans prdcesseur sont dits sources ; de mme, les sommets sans successeurs sont dits puits (anglais sink).

94

CHAPITRE 6. ALGORITHMES SUR GRAPHES

Deuxime partie Annexes

95

Annexe A Modularit des programmes en C


Avant-propos
Le but de ce document est de prsenter comment on peut dvelopper des programmes volumineux en C (plusieurs milliers de lignes) en utilisant un principe de dcoupage (fonctionnel) en modules indpendants du point de vue de limplmentation. La premire partie dtaille les diffrentes tapes de la construction dun programme excutable partir dun source en C. La seconde partie indiquera comment on peut utiliser ce mcanisme pour dcomposer un programme en modules.

A.1

Construction dun programme C

Il nest pas inutile de revenir la faon dont nous construisons nos programmes simples. Construire un programme C, cest, a minima, crire un chier source (nommons-le toto.c) qui contient une procdure nomme main. Voici un exemple dun tel chier : #include <stdio.h> int main (int argc,char *argv[]) { puts("Hello world"); return 0; } Considrons la premire ligne, qui contient la directive dinclusion du chier den-tte stdio.h : on a expliqu que cette directive permettait au programme (au compilateur, plus prcisment) dtre inform de lexistence dune procdure nomme puts, admettant un premier paramtre de type char* (adresse mmoire dun caractre, ou plus exactement, chane de caractres), admettant ventuellement des paramtres supplmentaires, et retournant un rsultat de type entier. Dtaillons un peu ce processus. 97

98

ANNEXE A. MODULARIT DES PROGRAMMES EN C

A.1.1

Le prprocesseur

Le compilateur gcc comme tous les programmes du mme type est en fait un enchaneur de passes : son rle est, partir dun chier source (qui peut tre un source C mais aussi Fortran, C++. . .) de produire un programme excutable en appelant plusieurs programmes successifs (lenchaneur de passes stoppe son traitement si lune des phases choue). La premire de ces passes est le prprocesseur : comme son nom le suggre, cest en fait une phase prliminaire considre comme ne faisant pas partie du langage lui-mme. Cest ce prprocesseur qui est charg de traiter les lignes du code source dont le premier caractre est #. Il travaille uniquement au niveau du chier en mode texte, et il ne connat rien la syntaxe de C, pas plus qu celle des autres langages ! Son rle est de construire un chier intermdiaire prprocess , cest--dire un chier o toutes les lignes du chier initial qui commencent par # ont disparu et ont t remplaces par le rsultat de la directive demande. Les autres lignes du source peuvent avoir t modies, notamment celle qui utilisent les chanes de caractres qui ont t dnies pour le prprocesseur. Par exemple, avec la macro-dnition de NULL (trouve dans stdio.h) suivante : # define NULL ((void*) 0) la ligne de source suivante : ptr=malloc(10); if (ptr==NULL) sera remplace par la ligne : ptr=malloc(10); if (ptr==((void*) 0) Attention : cest bien un remplacement de texte qui est fait ! Pour sen convaincre, analyser le bout de code 1 suivant : #define UN 1 #define DEUX UN+UN #define QUATRE DEUX*DEUX ... if (QUATRE==3) printf("BUG\n"); sera remplace par le prprocesseur par : if (1+1*1+1==3) printf("BUG\n"); ce qui, comme la multiplication est prioritaire par rapport laddition, donnera une condition vraie ! Il aurait fallu crire : #define UN 1 #define DEUX (UN+UN) #define QUATRE (DEUX*DEUX)
1. exemple aimablement fourni par Michel Beigbeder.

A.1. CONSTRUCTION DUN PROGRAMME C ce qui aurait donn aprs traitement : if (((1+1)*(1+1))==3) printf("BUG\n"); et donc, une excution correcte !

99

Revenons notre ligne #include <stdio.h> ; celle-ci est interprte par le prprocesseur qui recherche dans des rpertoires standard (il existe une autre forme pour la directive include sous la forme #include "stdio.h" : sous cette forme, le prprocesseur recherche dabord le chier demand dans le rpertoire courant avant de le rechercher dans les rpertoires standard) le chier dont le nom est indiqu. Sil le trouve, il inclut le chier, en lui faisant subir le mme traitement que le chier initial (traitement des lignes commenant par #, recherche des macros). On peut gnrer ce chier prtrait en utilisant la commande : $ gcc -o toto.i -E toto.c loption -E du compilateur indique que lon ne doit effectuer que le prtraitement, loption -o toto.i indique que le rsultat doit tre stock dans le chier toto.i (sinon, le rsultat est afch sur la sortie standard). Au total, le chier initial, une fois trait, devient assez volumineux (7 lignes de code source, plus de 1000 lignes une fois trait). Notons que ce nouveau chier contient une ligne qui nous intresse : extern int puts(const char *); Cest une dclaration de prototype de la procdure puts, qui indique le nombre et le type des paramtres de cette procdure, ainsi que le type de son rsultat. Le mot-cl extern indique au compilateur que cette procdure est dnie ailleurs (pas dans le chier en cours).

A.1.2

Le compilateur, lassembleur

Une fois le chier prtrait, cest maintenant rellement le compilateur C qui va effectuer la phase suivante : il va traduire le chier source C en un langage intermdiaire, dit langage dassemblage, qui est spcique au processeur sur lequel est effectue la compilation, et qui est encore lisible avec un diteur de texte (on peut crire directement ses programmes en assembleur !). Cest durant cette phase que la vrication de lutilisation bon escient des procdures est effectue : toujours dans notre cas, le compilateur peut (grce au prototype prsent dans le chier prtrait) vrier que lutilisation de puts est cohrente : il y a bien un paramtre de type char*. On peut l encore demander lenchaneur de passes de stopper cette phase de compilation par la commande : $ gcc -S toto.c loption -S indiquant de sarrter aprs la compilation. Cette commande construit (sauf en cas derreur de syntaxe. . .) un chier de nom toto.s, de type texte, contenant la traduction en langage dassemblage du chier source.

100

ANNEXE A. MODULARIT DES PROGRAMMES EN C

Enn, une fois cette compilation effectue, lassembleur effectue la traduction en langage machine (non lisible avec un diteur de texte) : le chier gnr (toto.o dans notre exemple, le sufxe .o signiant object code ou code objet) contient le code machine de la procdure unique dclare dans notre programme : la procdure main. On peut demander gcc de stopper aprs cette phase dassemblage par loption -c : $ gcc -c toto.c Ce code objet nest pas lisible avec un diteur de texte, mais on dispose dun outil appel nm qui permet danalyser le contenu dun code objet. Comme toute commande, elle dispose de nombreuses options, mais on peut se contenter ici de sa fonctionnalit de base : $ nm toto.o 00000000 T main U puts Cette commande nous apprend les choses suivantes : la commande a reconnu le format du code objet ! il y a dans ce code un symbole main qui est dans la zone T comme Text, cest--dire la zone de code excutable (ne pas oublier que la mmoire contient des donnes et du code !) ; la valeur indique 00000000 donne ladresse mmoire (relative la zone de texte) o commence ce code excutable ; il y a dans ce code un symbole puts qui est dans la zone U comme Undefined, cest-dire quil est fait rfrence ce symbole, mais lassembleur na pas trouv le code correspondant (cest normal, notre chier source ne contient pas le code source de puts). noter que, bien que de trs nombreuses procdures soient dclares dans notre chier (toutes les procdures dnies dans stdio.h), seule puts est indique dans le chier objet, car cest la seule rellement utilise.

A.1.3

Lditeur de liens

La dernire phase permettant la production dun programme excutable est ldition de liens. Cela va consister, dans notre exemple simple : trouver quelque part le code de la procdure puts ; cette procdure peut elle-mme faire rfrence dautres procdures, quil faudra galement trouver ; ajouter notre programme le code de dmarrage (ce qui se passe avant la procdure main) et le code darrt (ce qui se passe aprs la procdure main) ; La premire opration est faite en consultant un ensemble de codes objets fournis avec lenvironnement de dveloppement. An dviter de consulter de nombreux chiers, on regroupe ces codes objets en un seul chier appel bibliothque (library en anglais, et parfois improprement librairie en franais. . .). Dans le cas des procdures standard du langage C, toutes les procdures sont dans la bibliothque dite libc ou glibc dans le cas de gcc : la localisation exacte de ce chier dpend de lenvironnement de dveloppement. La deuxime opration est faite en consultant un autre ensemble de codes objets : sous Linux, ces codes se trouvent dans plusieurs chiers, nomms crt1.o 2 , crti.o et crtbegin.o
2. le prxe crt signie C run time

A.1. CONSTRUCTION DUN PROGRAMME C

101

(avant la procdure main), crtend.o et crtn.o (aprs la procdure main). Leur localisation exacte dpend, encore une fois, de lenvironnement de dveloppement.

A.1.4

Le run-time

On appelle run-time le code fourni par le systme dexploitation au moment de lexcution. En effet, nos programmes vont utiliser les ressources du systme (la mmoire, le processeur, les chiers, les priphriques), et vont donc utiliser les fonctionnalits du systme dexploitation : ces fonctionnalits sont rendues par du code toujours prsent en mmoire, qui est le code spcique du systme dexploitation. Mais ce nest pas tout. En effet, si lon relance la commande nm sur lexcutable obtenu aprs dition de liens (toujours le petit exemple toto.c), on a la surprise de voir que de nombreux symboles sont dsormais prsents dans le chier, mais que la procdure puts nest toujours pas dnie ! En effet, nm afche quelque chose ressemblant : $ nm a.out .. 0804833c T main U puts@@GLIBC_2.0 .. Ceci sexplique par la motivation suivante : de trs nombreux programmes vont utiliser les procdures de la bibliothque C (tous ceux dvelopps en C, ce qui est le cas de presque tous les utilitaires fournis avec Linux. . .), et il serait catastrophique aussi bien au niveau de lencombrement disque (pour stocker les chiers des programmes excutables) que de lencombrement mmoire (lorsque les programmes sont excuts) de dupliquer ces codes ! Le systme Linux, comme la plupart des Unix, a pour cela mis en place un dispositif qui permet dviter ces deux inconvnients. A.1.4.1 Chargement dynamique des bibliothques

Au moment de ldition de liens, le code des bibliothques nest en fait pas ajout au programme : lditeur de liens vrie simplement que les procdures utilises dans le programme existent quelque part, cest--dire dans une bibliothque accessible au moment de la compilation, et il mmorise dans le chier excutable, pour chaque procdure, la bibliothque o cette procdure a t trouve. Cest ce quindique lutilitaire nm : le programme utilise la procdure puts, qui a t trouve dans la bibliothque glibc (le 2.0 qui suit indique en plus la version de la bibliothque). Cest au tout dbut de lexcution du programme que le code des bibliothques va tre recherch et install en mmoire : on appelle ce mcanisme chargement dynamique de bibliothques, ou encore dynamic loading of librairies (DLL). Ce code fait partie de ce qui est excut avant la procdure main. Sur certains systmes, cette recherche se fait mme de faon paresseuse (cest le vrai terme utilis, traduction de langlais lazy resolution) : un minimum de travail est fait au dmarrage du programme, et cest la premire utilisation de la procdure que lon recherche rellement o

102

ANNEXE A. MODULARIT DES PROGRAMMES EN C

elle se trouve (grosso modo, la philosophie est de se dire : pourquoi faire systmatiquement un travail alors que lon peut peut-tre sen passer si la procdure nest pas utilise ? do le nom de paresseux). Les appels ultrieurs ne posent eux pas de problme. A.1.4.2 Bibliothques partages

Le chargement dynamique des bibliothques dcrit prcdemment permet dviter de dupliquer le code dans les programmes excutables (chiers), ce qui conomise de la place disque. Pour conomiser la place mmoire, on utilise un mcanisme complmentaire : les bibliothques sont de plus partages entre les diffrents programmes qui les utilisent. Ceci revient dire que chaque bibliothque est au plus prsente une fois en mmoire (ds quau moins un programme lutilise). Pour cette raison, on nomme parfois ces bibliothques des objets dynamiques partags ou dynamic shared objects ou encore DSO. On trouve notamment dans le programme excutable, toujours grce la commande nm : $ nm a.out .. 08049440 D __dso_handle .. un symbole appel __dso_handle, qui est en fait un pointeur sur une liste chane des bibliothques partages dynamiques que le programme utilise.

A.2
A.2.1

Modularit en C
Principe gnral

Dans le schma simple, il ny a quun seul chier source : ce chier est compil (prtrait, compil, assembl) et ldition de liens ajoute les procdures de la bibliothque C, le C run time. Il ny a aucun problme pour tendre ce processus plusieurs chiers sources. Chaque chier source est, totalement indpendamment des autres, prtrait, compil, assembl. Simplement, ldition de liens traite la collection des chiers objets gnrs, et ajoute ensuite la libc et le C run time !

A.2.2

Un premier exemple

Voici par exemple la construction dun programme partir de 2 sources, toto.c qui contient : #include <stdio.h> void toto (int i) { printf("i = %d\n",i); }

A.2. MODULARIT EN C et titi.c qui contient : extern void toto (int); int main (int argc,char *argv) { toto(argc); toto(3); }

103

On peut noter : la prsence dans le premier chier de linclusion de stdio.h, ncessaire pour lutilisation de la procdure printf ; labsence dans le deuxime chier de cette inclusion (aucune procdure de la bibliothque C nest utilise) ; la dclaration en extern du prototype de la procdure foo, utilise ensuite. La construction de lexcutable correspondant peut se faire par la succession des commandes : $ gcc -Wall -c toto.c $ gcc -Wall -c titi.c $ gcc toto.o titi.o Bien noter que, pour chaque chier source, on prcise loption -c qui indique de ne pas faire ldition de liens (chaque chier ne gnre pas lui tout seul le programme complet). Pour la dernire commande, comme les chiers ont le sufxe .o, gcc en dduit que ce sont des chiers objet et ne fait donc que ldition de liens. On aurait galement pu construire le programme en une seule commande : $ gcc -Wall toto.c titi.c la diffrence tant simplement que les codes objet temporaires sont effacs aprs ldition de liens.

A.2.3

Utilisation du prprocesseur

On peut galement utiliser le prprocesseur : il est ainsi possible de crer ses propres chiers den-tte (les .h). Dans le petit exemple prcdent, voici une nouvelle version du chier titi.c : #include "toto.h" int main (int argc,char *argv) { toto(argc); toto(3); }

104

ANNEXE A. MODULARIT DES PROGRAMMES EN C

le chier toto.h contenant son tour : extern int toto (int); Quest-ce que lon a gagn ? Pas grand chose sur cet exemple prcis, mais si lon a de nombreuses procdures dnies dans un mme chier, et que ces procdures sont leur tour utilises dans plusieurs autres chiers sources (penser stdio.h qui contient de trs nombreuses procdures, utilises dans de trs nombreux sources. . .), on conomise de nombreuses lignes de dclarations remplaces par la seule ligne dinclusion, et on y gagne en lisibilit ! NB : on a utilis la seconde forme de la macro dinclusion o le nom du chier est entre doubles quotes, on rappelle que dans ce cas, le chier est dabord recherch dans le rpertoire courant.

A.2.4

Comment dcouper un programme source ?

Avec le schma de construction des programmes tel quil a t prsent, il nest pas possible de rpartir une procdure C entre deux chiers sources, et on peut donc avoir deux situations extrmes : le source est intgralement contenu dans un seul chier quel que soit le nombre de procdures le composant ; le source est dcoup en autant de chiers quil y a de procdures le composant. Toute solution intermdiaire entre ces deux extrmes est techniquement ralisable, et un choix particulier relvera plutt des bonnes pratiques de programmation que dune contrainte lie au langage ou lenvironnement de dveloppement.

A.2.5

Construction automatique des programmes

Lorsque le nombre de chiers sources pour un mme excutable devient important, il devient assez ardu de construire lexcutable. Si un chier est modi, il est ncessaire de le recompiler, mais il nest peut-tre pas ncessaire de tout recompiler. Cest pour remplir cette tche particulire que lenvironnement Linux propose un outil de construction automatique de programmes appel make. Cet outil utilise un chier de conguration appel Makefile ou encore makefile : ce chier contient, avec une syntaxe un peu particulire, les rgles de construction. Toujours dans notre exemple simple ci-dessus, voici une faon possible dcrire ce chier Makefile prog:toto.o titi.o gcc -o prog toto.o titi.o toto.o:toto.c gcc -Wall -c toto.c titi.o:titi.c toto.h gcc -Wall -c titi.c

A.2. MODULARIT EN C

105

Chacune des lignes 1, 3 et 5 dnit une rgle de dpendance : cette rgle sera applique si le programme make se rend compte que lun des chiers dont le nom est situ aprs le caractre : a t modi postrieurement celui dont le nom est indiqu avant ce caractre. Ainsi, prog dpend de toto.o et de titi.o, toto.o dpendant son tour de toto.c et titi.o dpendant de titi.c et de toto.h. Le fait davoir ajout toto.h dans la liste des dpendances de titi.o provient de linclusion de toto.h dans titi.c : si lon modie toto.h, il faut bien recompiler titi.c ! Noter enn que lespace avant le mot gcc sur chacune des trois lignes 2, 4 et 6 doit tre une tabulation, et non une succession despaces. Une fois ce chier crit, il suft de taper la commande make prog et le programme make reconstruira si besoin le chier prog : $ make prog gcc -Wall -c toto.c gcc -Wall -c titi.c gcc -o prog toto.o titi.o $ make prog make: prog is up to date. $ gedit toto.c $ make prog gcc -Wall -c toto.c gcc -o prog toto.o titi.o

106

ANNEXE A. MODULARIT DES PROGRAMMES EN C

Annexe B La saisie de donnes au clavier


Avant-propos
Le but de ce petit document est dindiquer quelques fonctionnalits utilisables pour le traitement des saisies dinformation au clavier. Ceci concerne lenvironnement Unix de faon gnrale, et Linux en particulier. Le rdacteur de cette note avoue sa mconnaissance du fonctionnement prcis sous Windows. . .

B.1

Rappel

Chaque programme C dispose dun ot dit entre standard connu sous le sobriquet de stdin (macro-dnition prsente dans le chier den-tte stdio.h). Cest sur ce ot accessible en lecture seulement que la procdure scanf (ainsi que la macro/procdure getchar) lit ses donnes. Lorsque le programme C, une fois compil, est lanc depuis un mulateur de terminal, cette entre standard est connecte au clavier (de la mme faon que la sortie standard est connecte vers lcran) : lors dune lecture sur stdin, lutilisateur tape sur les touches du clavier, et lorsquil appuie sur la touche Return ou Entre, ce quil a frapp est disponible pour le programme.

B.2

Fonctionnement par dfaut du terminal

Vous avez srement constat que les donnes transmises vers le programme ntaient pas exactement la succession des caractres taps au clavier : lutilisation de la touche Backspace ou Ctrl-H (cette notation signie : appuyer dabord sur la touche Ctrl et tout en la maintenant enfonce, appuyer sur la touche H) a pour effet deffacer le dernier caractre saisi ; la touche Delete ou Suppr efface totalement la ligne ; lutilisation de la combinaison Ctrl-C interrompt le droulement du programme. 107

108

ANNEXE B. LA SAISIE DE DONNES AU CLAVIER

Vous avez aussi constat que lorsque vous tapez une touche normale , vous voyez apparatre le caractre frapp lcran. Tout ceci est luvre dun composant du systme dexploitation que lon nomme driver tty 1 ou pilote de terminal. Ce pilote de terminal a un rle triple (au moins) : cest lui qui est charg dafcher les caractres taps (fonction dcho) ; cest lui qui est charg de ldition de la ligne en cours de saisie (possibilit deffacer un caractre, un mot, la ligne complte) ; cette fonctionnalit particulire est souvent dsigne dans la documentation sous le vocable de traitement canonique de lentre ou input canonical processing ; cest lui qui est charg de traiter les caractres de contrle (le Ctrl-C qui interrompt le programme, mais il en existe dautres). On parle ici de traitement des signaux ou de signal processing.

B.2.1

Traitement canonique

Le traitement canonique du pilote de terminal fonctionne en mode ligne : les caractres taps au clavier sont utiliss pour construire une ligne complte de donnes, et cest lors de la frappe de la touche Return que la ligne ainsi constitue devient disponible pour le programme. Rappelons que le caractre n de ligne est prsent dans les donnes envoyes au programme. Si lon veut forcer lenvoi vers le programme dune ligne incomplte (non termine par une n de ligne), on peut le faire en tapant la squence Ctrl-D (ce caractre nest pas ajout la ligne). Attention : si le pilote clavier reoit une ligne vide, cest--dire en tapant Ctrl-D en dbut de ligne, il gnrera un indicateur de n de chier pour le programme qui tente la lecture. Dans ce cas, scanf (ou getchar) retourne la valeur EOF (dnie dans le chier den-tte stdio.h comme tant la valeur numrique -1). Certains interprteurs de commandes considrent cette n de chier comme une volont de lutilisateur de terminer le traitement, et terminent alors leur fonctionnement ! Notons enn que cest toute la ligne qui est envoye en une seule fois au programme : ainsi, si lon effectue une boucle de lecture par getchar, le programme, lors du premier appel, va attendre que la ligne ait t saisie et termine par la touche Entre ; tous les appels successifs getchar seront immdiatement satisfaits jusqu ce que le caractre n de ligne ait t lu (pas dattente) ; le getchar suivant provoquera une nouvelle attente, etc. Les diverses fonctionnalits du traitement canonique sont accessibles par certains caractres taps au clavier, dits caractres spciaux . Ces caractres spciaux sont indiqus ci-dessous, avec leur nom symbolique : erase caractre spcial effaant le dernier caractre tap ; en gnral, Ctrl-H ou Backspace ; kill caractre spcial effaant la totalit de la ligne ; en gnral, Ctrl-U ; eof (end of le) caractre spcial forant lenvoi de la ligne en cours ddition ; en gnral, Ctrl-D ;
1. le nom tty est le diminutif de teletype dsignant une imprimante connecte lordinateur par une ligne srie ; ce nom est rest pour dsigner le pilote de la ligne srie, sur lesquelles on a connect ensuite des terminaux alphanumriques ; ces terminaux sont maintenant remplacs par des mulateurs de terminal en environnement multi-fentr, mais le nom initial est rest !

B.2. FONCTIONNEMENT PAR DFAUT DU TERMINAL

109

eol (end of line) caractre spcial marquant la n de ligne ; en gnral, Ctrl-J ou Return ou Entre ; eol2 autre caractre spcial marquant la n de ligne ; en gnral, non dni ; werase (word erase) caractre spcial effaant le dernier mot (efface les caractres prcdemment saisi jusquau premier blanc) ; en gnral, Ctrl-W ; rprnt (reprint) caractre spcial permettant de rafcher la totalit des caractres dj saisis dans la ligne ddition ; en gnral, Ctrl-R ; lnext caractre spcial permettant de banaliser le caractre suivant (utilise pour saisir les caractres spciaux dans la ligne !) ; en gnral, Ctrl-V ; par exemple, si lon souhaite ajouter le caractre Ctrl-H dans la ligne, on tape dabord Ctrl-V puis Ctrl-H, si lon souhaite ajouter le caractre Ctrl-V, on tape deux fois Ctrl-V ;

B.2.2

Traitement des signaux

Le pilote de terminal offre la possibilit lutilisateur de gnrer des signaux pour les programmes en cours dexcution. Ce sont galement des caractres spciaux qui dclenchent ces signaux. On dispose ainsi : dun signal dinterruption dit SIGINT ; en gnral, cest la combinaison Ctrl-C qui dclenche lenvoi de ce signal ; la plupart des programmes, lorsquils recoivent ce signal, terminent prmaturment et immdiatement leur fonctionnement ; dun signal dit SIGQUIT ; en gnral, cest la combinaison Ctrl-\ qui dclenche lenvoi de ce signal ; la plupart des programmes, lorsquils recoivent ce signal, terminent prmaturment et immdiatement leur fonctionnement (comme pour SIGINT) et gnrent une copie de leur espace mmoire dans un chier core ; dun signal de suspension dit SIGTSTP ; en gnral, cest la combinaison Ctrl-Z qui dclenche lenvoi de ce signal ; ceci permet de suspendre lexcution dun programme, et redonne la main linterprteur de commandes ; on peut poursuivre ultrieurement lexcution du programme stopp (commande bg et fg de linterprteur) ; On peut insrer ces caractres spciaux dans la ligne de saisie en les prcdant du caractre dchappement (lnext) dcrit dans le paragraphe prcdent : par exemple, la squence Ctrl-V Ctrl-C permet dajouter le caractre Ctrl-C (code ASCII 3) dans la ligne de saisie.

B.2.3

Traitement de lcho

Par dfaut, tout caractre tap au clavier est afch (en cours de saisie) sur lcran. Attention : cet cho ne doit pas tre confondu avec lafchage des caractres crits par le programme sur la sortie standard, galement connecte lcran ! Dautres fonctions sont offertes par cette fonctionnalit dcho : echoe lors dune frappe du caractre deffacement, lcho gnre la squence : espace arrire, espace, espace arrire (permet deffacer physiquement le caractre du terminal) ; echok effectue un cho dun saut de ligne aprs la frappe dun caractre deffacement de la ligne ;

110

ANNEXE B. LA SAISIE DE DONNES AU CLAVIER

echoctl effectue un cho des caractres de contrle (les combinaisons Ctrl-X) sous la forme ^X.

B.3
B.3.1

Quelques incidences sur la lecture de donnes


Un petit problme

Il faut bien avoir lesprit le mode de fonctionnement par dfaut du pilote de terminal lorsque lon souhaite demander interactivement des informations lutilisateur. Prenons lexemple de code suivant : int n; char c; ... printf("Donner la valeur de n ?"); scanf("%d",&n); printf("Taper un caractere pour continuer :"); scanf("%c",&c); ... lexcution, le programme attend que lutilisateur donne la valeur de n, puis afche le message Taper.. et continue son excution sans attendre ! Explication : en fait, cest le traitement par ligne qui est responsable de ce petit problme. Lors de la saisie de la valeur numrique n, lutilisateur tape au clavier les chiffres dcimaux qui composent cette valeur, et valide sa ligne en tapant sur la touche Entre : la ligne, y compris le caractre de n de ligne, est donc transmise au programme. Lorsque le premier scanf analyse les donnes saisies, il interprte correctement les chiffres dcimaux pour dterminer la valeur de n : cette analyse se stoppe sur le caractre n de ligne, qui est remis dans les donnes en entre. Lors du second scanf, comme il reste des donnes de la lecture prcdente, on commence par analyser celles-ci : on na besoin que dun seul caractre (format %c) et cest donc le caractre n de ligne qui est immdiatement lu, do la poursuite sans attente de lexcution du programme !

B.3.2
B.3.2.1

Comment viter cela ?


La procdure fflush

La procdure fflush permet de forcer le vidage dun ot : par exemple, lorsque lon crit des donnes sur le ot stdout, on ne voit rien apparatre lcran tant que lon ncrit pas une n de ligne (ou que lon neffectue pas une lecture au clavier). Lutilisation de linstruction fflush(stdout) permet de forcer ce vidage. Sur certains systmes, on peut galement utiliser fflush sur un ot ouvert en lecture : leffet est alors de vider les donnes transmises au programme mais non encore lues. Cest

B.4. PROGRAMMATION DU PILOTE DU TERMINAL

111

prcisment ce que lon cherche faire. Hlas : ce fonctionnement de fflush ne fait pas partie de la norme ANSI-C, qui prcise simplement que sur les ots ouverts en lecture, leffet de fflush est indtermin. Linux sen tient la stricte implmentation ANSI, et un appel fflush sur un ot ouvert en criture retourne un code derreur. On dispose sous Linux dune procdure quivalente, nomme __fpurge : bien quutilisable, le manuel prcise quelle nest ni standard ni portable . utiliser avec modration, donc. . . B.3.2.2 Lecture en deux temps

On peut pallier ces inconvnients par la mthode dite de lecture en deux temps : toute lecture au clavier saisit une ligne complte dans un tableau de caractres ; lanalyse se fait ensuite sur le tableau lu ; La premire phase peut seffectuer par la procdure fgets (lutilisation de gets est totalement viter car source de nombreuses failles de scurit). Pour le seconde, on peut avantageusement remplacer tout usage de scanf par un appel sscanf, procdure de comportement identique fscanf sauf quau lieu dindiquer en premier paramtre un ot, on indique un tableau de caractres. Le petit exemple ci-dessus devient, avec cette mthode : #define LINEMAX 1024 int n; char c,line[LINEMAX]; ... printf("Donner la valeur de n ?"); fgets(line,LINEMAX,stdin); sscanf("%d",&n); printf("Taper un caractere pour continuer :"); fgets(line,LINEMAX,stdin); sscanf(line,"%c",&c); Lors du deuxime appel fgets, les donnes prsentes dans le tableau line et non encore lues sont tout simplement crases !

B.4

Programmation du pilote du terminal

Le pilote de terminal, dont le nombre de fonctionnalits est trs important (la page de manuel de ce pilote termios(3) est souvent la plus longue du manuel Unix !), est totalement congurable. Ce qui a t prsent ci-dessus est le fonctionnement par dfaut, et on peut donc loisir le modier.

B.4.1

Traitement non-canonique

On peut ainsi notamment supprimer le traitement canonique. Dans ce cas, le mode de fonctionnement par ligne nest plus actif, et les caractres spciaux associs (effacement dun ca-

112

ANNEXE B. LA SAISIE DE DONNES AU CLAVIER

ractre, de la ligne) sont interprts comme des caractres standard. Attention cependant : les caractres permettant de gnrer les signaux sont conservs ! En mode non-canonique, les caractres saisis au clavier sont transmis au programme selon le mode suivant : le pilote satisfait (transmet les donnes) les demandes de lecture ds quun nombre minimal de caractres a t saisi ; attention : le nombre de caractres transmis au programme peut tre infrieur au nombre de caractres demands ; le pilote dispose dun timer ou intervalle de temps : si cet intervalle de temps est dpass aprs la lecture dau moins un caractre, le pilote nattend pas que le nombre minimal de caractres soit reu pour transmettre les donnes au programme (ceci permet dviter les attentes trop longues). Ces deux paramtres sont rglables : le timer a une prcision du dixime de seconde et peut aller jusqu 25.5 secondes. Pour le pilote, ils sont considrs comme des pseudo-caractres spciaux, dnomms min et time.

B.4.1.1

Exemple important

Il est frquent de vouloir lire les caractres 1 par 1, ds quils sont taps au clavier, et sans attente de la touche Entre : il suft pour cela de placer le pilote de terminal en mode noncanonique, en rglant le nombre minimal de caractres 1 et le timer 0.

B.4.2

cho, signaux

On peut galement invalider la fonctionnalit dcho : ceci invalide presque toutes les fonctionnalits associes, mais on conserve la possibilit de faire un cho du seul caractre Entre. Voir la documentation pour cela. De la mme faon, on peut invalider la fonctionnalit de traitement des signaux.

B.5

Dans la pratique

La conguration du pilote de terminal peut se faire de deux faons : lutilisation de la commande stty(1), lance partir de linterprteur de commandes, modie les paramtres du terminal associ linterprteur ; ces paramtres modis le sont galement pour tout programme lanc par ce dernier ; lutilisation des procdures de bibliothque termios(3), directement partir du programme. Il est noter que certains interprteurs (tcsh notamment), sauvegardent les paramtres du terminal avant toute commande, et les rtablissent ensuite : la premire mthode (par la commande stty) est donc totalement inoprante, et cest donc vers la deuxime quil faut se tourner.

B.5. DANS LA PRATIQUE

113

B.5.1

Quelques utilisations de stty

Attention : la syntaxe indique ici est celle disponible sous Linux. Sous dautres environnements Unix (Solaris, IRIX, HP-UX), il peut y avoir quelques diffrences. Toujours se reporte au manuel en ligne. La suppression du traitement canonique se fait par la commande : $ stty -icanon ou la commande quivalente $ stty cbreak Pour revenir en mode canonique, on utilise lune des deux versions : $ stty icanon $ stty -cbreak Si lon souhaite, en mode non-canonique, prciser les paramtres : $ stty -icanon min 1 time 0 La suppression (et le rtablissement) du traitement des signaux se font par : $ stty -isig $ stty isig La suppression (et le rtablissement) de lcho se font par : $ stty -echo $ stty echo Enn, on dispose des modes combins raw (brut) et cooked (cuisin). Si lon a mis son terminal dans un tat lamentable, on peut restaurer une conguration valable par la commande $ stty sane

B.5.2

Conguration en C du pilote de terminal

On a besoin dune structure de donnes spcique, nomme struct termios pour effectuer cette conguration. Notre but nest pas dexpliquer en dtail le contenu de cette structure, ni comment on lutilise. Les personnes intresses peuvent consulter le manuel en ligne, ou consulter les enseignants. On trouvera nanmoins ci-dessous deux procdures : lune sauvegarde la conguration du terminal, et le recongure de faon passer en mode non-canonique, sans cho et sans gestion des signaux. Le nombre minimal de caractres est positionn 1, le timer 0. La seconde procdure permet de restaurer la conguration initiale (utile pour ne pas laisser le terminal dans un tat peu utilisable. . .). Ces procdures sont utilises dans un petit programme qui afche tous les caractres taps : cest la touche Delete ou Suppr qui permet de terminer le fonctionnement. Vous pouvez le compiler, le lancer, et essayer de taper Ctrl-C, Backspace, Entre. . .

114 #include <stdio.h> #include <termio.h> #include <unistd.h>

ANNEXE B. LA SAISIE DE DONNES AU CLAVIER

/* cette procedure reconfigure le terminal, et stocke */ /* la configuration initiale a ladresse prev */ int reconfigure_terminal (struct termios *prev) { struct termios new; if (tcgetattr(fileno(stdin),prev)==-1) { perror("tcgetattr"); return -1; } new.c_iflag=prev->c_iflag; new.c_oflag=prev->c_oflag; new.c_cflag=prev->c_cflag; new.c_lflag=0; new.c_cc[VMIN]=1; new.c_cc[VTIME]=0; if (tcsetattr(fileno(stdin),TCSANOW,&new)==-1) { perror("tcsetattr"); return -1; } return 0; } /* cette procedure restaure le terminal avec la */ /* configuration stockee a ladresse prev */ int restaure_terminal (struct termios *prev) { return tcsetattr(fileno(stdin),TCSANOW,prev); } /* exemple dutilisation */ int main (int argc,char *argv[]) { struct termios prev; int nb,c; if (reconfigure_terminal(&prev)==-1) return 1; for (nb=0;;) { c=getchar();

B.6. MODIFICATION DE LENTRE STANDARD nb++; (void) printf("carac[%d]=(%d,%o,%x)",nb,c,c,c); if (c==127) { printf(" char=DEL\n%d caracteres\n",nb); break; } if (c>=32) printf(" char=%c\n",c); else printf("\n"); } if (restaure_terminal(&prev)==-1) return 1; return 0; }

115

B.6

Modication de lentre standard

Ainsi que cela a t indiqu, tout programme lanc partir dun interprteur de commandes se retrouve avec son entre standard connecte sur le terminal de linterprteur. Il est toutefois possible de modier ce fonctionnement.

B.6.1

Redirection partir dun chier

On peut utiliser en lieu et place du clavier un chier dans lequel on a crit toutes les rponses/donnes ncessaires pour le programme. Par exemple, imaginons un programme qui demande lutilisateur un nombre de donnes, puis saisit le nombre indiqu de valeurs numriques. On peut alors crer un chier prog.in (le nom na aucune importance !) qui contient : 3 123 345 206 et lancer le programme en indiquant que la saisie des paramtres se fait dans le chier prog.in, en lanant le programme par : $ ./prog < prog.in NB : si ce sont les rsultats afchs par le programme sur la sortie standard que lon souhaite conserver dans un chier prog.out (encore une fois, le nom na aucune importance) au lieu de les crire sur le terminal, on utilise la syntaxe suivante : $ ./prog > prog.out

116

ANNEXE B. LA SAISIE DE DONNES AU CLAVIER

les deux oprations tant combinables : $ ./prog < prog.in > prog.out $ ./prog > prog.out < prog.in

B.6.2

Redirection partir des rsultats dun programme

Il peut y avoir des cas o le fonctionnement suivant est attendu : un premier programme afche des rsultats (sur sa sortie standard), ces rsultats devant tre saisi sous la mme forme par un second programme pour obtenir le rsultat nal. On peut penser stocker les rsultats intermdiaires dans un chier, sur lequel on redirige lentre standard du second programme, comme dans lexemple suivant : $ ./prog1 > resultats $ ./prog2 < resultats $ rm resultats On peut en fait viter de crer ce chier intermdiaire, et tout effectuer en une seule commande : $ ./prog1 | ./prog2 Attention bien respecter lordre ! On peut mme gnraliser ceci n programmes : $ ./prog1 | ./prog2 | ./prog3 | ./prog4 o pour tout i, la sortie standard de progi est connecte sur lentre standard de progi+1. En terminologie Unix, une telle succession de programmes porte le nom de tube (pipe en anglais), les programmes intermdiaires (ni le premier, ni le dernier) prenant le nom de ltres. Il existe de trs nombreux ltres, qui, en les combinant astucieusement, permettent dobtenir une grande varit de rsultats.

Index
acyclique, 77 adjacent, 76 adresse, 58 allocation dynamique (mmoire), 44 append, 10 arborescence, 81 arbre, 81 arbre de recouvrement, 79 arbre recouvrant, 79 arc, 75, 76 arte, 75, 76 big-endian (gros-boutiste), 31 binaire (chier), 28 buffer, 32 calloc, 46 chane, 76 champ (de structure), 38 chemin, 76 circuit, 76 clearerr, 22 codes derreur, 12 complexit, 63 connexe, 77 connexit, 77 copie de liste, 68 cycle, 77 dnition de type (typedef), 49 degr, 77 dpiler, 72 diamtre, 77 distance, 76 empiler, 72 End Of File, 10 enregistrement, 37 entre standard, 9 entres-sorties, 7 enum (numration), 50 numration (enum), 50 EOF, 10 errno, 21 exit, 11 exponentiel (algorithme), 64 fclose, 10 feof, 22 fermeture transitive, 79, 89 ferror, 22 fgetc, 16 fgets, 16 chier, 8 chier (binaire), 28 chier (rgulier), 28 chier (texte), 28 chiers, 7 FIFO, 72 FILE, 9 le, 72 n de liste, 61 ot, 9, 12 fopen, 10, 11 format (dcriture), 14 format (de lecture), 18 fprintf, 13 fputc, 13 fputs, 13 fread, 30 free, 48 freopen, 11 fscanf, 16 fseek, 32 ftell, 32 117

118 fwrite, 29 getc, 16 getchar, 16 gets, 16 grand O, 63 graphe, 75 graphe clairsem, 76 graphe dense, 76 graphe orient, 76 graphe partiel, 79 gros-boutiste (big-endian), 31 head, 20 LIFO, 72 linaire (algorithme), 64 liste, 58 liste (copie), 68 liste (parcours), 59 liste (rversion), 69 liste chane, 58 liste dadjacence, 77 little-endian (petit-boutiste), 31 logarithmique (algorithme), 64 malloc, 44 man, 9 Markov, 81 matrice dadjacence, 77 mmoire (allocation dynamique), 44 NULL, 9, 61 O (notation), 63 ordre, 76 parcours (de graphe), 85 parcours (de liste), 59 perror, 21 petit-boutiste (little-endian), 31 pile, 72 point, 75 pointeur, 58 pointeur (sur structure), 42 prdcesseur, 76 printf, 13 producteur-consommateur, 12 profondeur, 85 puits, 83, 89 putc, 13 putchar, 13 puts, 13 quadratique (algorithme), 64 queue de liste, 61 racine, 81 record, 37 rgulier (chier), 28 remove, 22 rename, 22 rversion de liste, 69 rewind, 33 Roy-Warshall (algorithme), 89 scanf, 16, 18 sink, 83, 89 sizeof, 45 solitaire, 75 sommet, 75 sortie standard, 9 source, 83, 89 sparse, 76 sprintf, 13 sscanf, 16 stderr, 9 stdin, 9 stdio.h, 9 stdout, 9 struct, 38 structure de donnes, 37 successeur, 76 tampon mmoire, 32 tte de liste, 58 texte (chier), 28 tmple, 22 tri par fusion, 71 typedef (dnition de type), 49 ungetc, 16

INDEX

INDEX union, 51 valu, 77

119

120

INDEX

Bibliographie
[1] Jean-Jacques Girardot and Marc Roelens. Introduction http://kiwi.emse.fr/INTROINFO/, Septembre 2010. linformatique.

[2] Jean-Jacques Girardot and Marc Roelens. Structures de donnes et Algorithmes en C. http://kiwi.emse.fr/POLE/SDA/, Octobre 2010. [3] Donald E. Knuth. Big omicron and big omega and big theta. SIGACT News, April-June 1976.

121

122

BIBLIOGRAPHIE

Table des matires


I Cours 3
5 7 7 7 7 8 9 9 9 10 11 12 12 12 12 13 14 14 16 17 17 18 20 21 21 22 22 22 23 24 24 24

Introduction 1 Entres-sorties en C 1.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 1.1.2 Reprsentation des donnes dans un chier . . . . . . . 1.1.3 Bibliothque standard des entres-sorties . . . . . . . . 1.1.4 Dnition des ots . . . . . . . . . . . . . . . . . . . . 1.1.5 Flots et chiers . . . . . . . . . . . . . . . . . . . . . . 1.1.5.1 Cration dun ot partir dun chier . . . . . 1.1.5.2 Destruction dun ot . . . . . . . . . . . . . . 1.1.5.3 Rouverture dun ot . . . . . . . . . . . . . 1.1.6 Quelques proprits des ots . . . . . . . . . . . . . . . 1.1.6.1 Unit dchange avec un ot . . . . . . . . . 1.1.6.2 Accs squentiel . . . . . . . . . . . . . . . . 1.1.7 Oprations dcriture dans un ot . . . . . . . . . . . . 1.1.7.1 criture de caractres . . . . . . . . . . . . . 1.1.7.2 criture de chanes . . . . . . . . . . . . . . 1.1.7.3 criture formate . . . . . . . . . . . . . . . 1.1.8 Oprations de lecture dans un ot . . . . . . . . . . . . 1.1.8.1 Lecture en mode caractre . . . . . . . . . . . 1.1.8.2 Lectures de chanes . . . . . . . . . . . . . . 1.1.8.3 Lecture formate . . . . . . . . . . . . . . . . 1.1.9 Un exemple : head . . . . . . . . . . . . . . . . . . 1.1.10 Autres procdures . . . . . . . . . . . . . . . . . . . . 1.1.10.1 Gestion des erreurs . . . . . . . . . . . . . . 1.1.10.2 Autres procdures de manipulation de ots . . 1.1.10.3 Autres procdures de manipulations de chiers 1.1.11 Un exemple : high scores . . . . . . . . . . . . . . . 1.1.12 Un exemple, head, 2 . . . . . . . . . . . . . . . . . 1.2 Complments de cours . . . . . . . . . . . . . . . . . . . . . . 1.2.1 Caractres et chanes de caractres en C . . . . . . . . . 1.2.1.1 Le type char . . . . . . . . . . . . . . . . . 123

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

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

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

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

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

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

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

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

124 1.2.1.2 Les constantes caractres . . . . . . . Chanes de caractres . . . . . . . . . . . . . . . 1.2.2.1 Le type chane de caractres . . . . . . 1.2.2.2 Constantes chanes de caractres . . . 1.2.2.3 Particularits . . . . . . . . . . . . . . 1.2.3 Fichiers texte et chiers binaires . . . . . . . . . 1.2.3.1 criture en format binaire . . . . . . . 1.2.3.2 Lecture en format binaire . . . . . . . 1.2.4 Gros-boutistes et petits-boutistes . . . . . . . . . 1.2.5 Tampons en mmoire . . . . . . . . . . . . . . . 1.2.6 Positionnement dans un ot . . . . . . . . . . . Travaux pratiques . . . . . . . . . . . . . . . . . . . . . 1.3.1 Arguments de la procdure main . . . . . . . . 1.3.2 Consulter le manuel . . . . . . . . . . . . . . . 1.3.3 Conversion de nombres . . . . . . . . . . . . . . 1.3.4 criture dentiers . . . . . . . . . . . . . . . . . 1.3.5 Nombre de caractres et de lignes dans un chier Exercices faire. . . sil reste du temps . . . . . . . . . . 1.4.1 Conversion hexadcimale . . . . . . . . . . . . 1.4.2 Fichiers : format texte ou binaire . . . . . . . . . 1.4.3 Format de chier et manipulation de matrices . . 1.2.2 . . . . . . . . . . . . . . . . . . . . .

TABLE DES MATIRES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 26 26 26 27 28 29 30 31 32 32 33 33 33 34 34 35 35 35 36 36 39 39 39 39 39 40 42 43 43 45 46 48 50 51 52 52 52 53 55 55 55 56

1.3

1.4

Structures de donnes en C 2.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 2.1.2 Types structurs en C . . . . . . . . . . . . . . . . . . 2.1.2.1 Dnition dun type structur . . . . . . . . 2.1.2.2 Structures et tableaux, structures imbriques 2.1.2.3 Accs aux champs dune structure . . . . . 2.1.3 Structures et procdures . . . . . . . . . . . . . . . . 2.1.4 Structures rfrenant des structures . . . . . . . . . . 2.1.5 Allocation dynamique de structures et tableaux . . . . 2.1.5.1 La procdure malloc . . . . . . . . . . . . . 2.1.5.2 La procdure calloc . . . . . . . . . . . . . 2.1.5.3 La procdure free . . . . . . . . . . . . . . 2.1.6 Dnition de type nomm . . . . . . . . . . . . . . . 2.2 Complments de cours . . . . . . . . . . . . . . . . . . . . . 2.2.1 Unions et types numrs . . . . . . . . . . . . . . . 2.2.1.1 numrations . . . . . . . . . . . . . . . . 2.2.1.2 Unions . . . . . . . . . . . . . . . . . . . . 2.3 Travaux pratiques . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1 Manipulation de chiers de voitures . . . . . . . . . . 2.3.1.1 Liminaire . . . . . . . . . . . . . . . . . . 2.3.1.2 Procdure de lecture du chier . . . . . . .

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

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

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

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

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

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

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

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

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

TABLE DES MATIRES 2.3.1.3 Procdure dcriture dun chier de voitures 2.3.1.4 Conclusion . . . . . . . . . . . . . . . . . . Gestion de comptes bancaires . . . . . . . . . . . . . 2.3.2.1 Liminaire . . . . . . . . . . . . . . . . . . 2.3.2.2 Analyse et conception . . . . . . . . . . . . 2.3.2.3 Implmentation . . . . . . . . . . . . . . . 2.3.2.4 Pour conclure, sil reste du temps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

125 56 56 56 57 57 57 57 59 59 59 59 59 60 61 61 62 65 66 67 68 68 68 68 68 69 71 71 71 71 73 73 76 76 77 77 77 79 79 79 80 81

2.3.2

Listes 3.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Introduction . . . . . . . . . . . . . . . . . . . 3.1.2 Listes chanes . . . . . . . . . . . . . . . . . 3.1.2.1 Prsentation . . . . . . . . . . . . . 3.1.2.2 Procdures de manipulation de listes 3.1.2.3 Longueur dune liste . . . . . . . . . 3.1.2.4 Impression du contenu dune liste . . 3.1.2.5 Cration dlment . . . . . . . . . 3.1.3 Complexit . . . . . . . . . . . . . . . . . . . 3.1.4 valuation des complexits . . . . . . . . . . . 3.1.5 Complexit des oprations usuelles . . . . . . 3.2 Travaux pratiques . . . . . . . . . . . . . . . . . . . . 3.2.1 Cration de liste . . . . . . . . . . . . . . . . 3.2.2 Recherche de nombre . . . . . . . . . . . . . . 3.2.3 Suppression dun lment . . . . . . . . . . . 3.2.4 Cration dune liste trie . . . . . . . . . . . . 3.2.5 Gestion densemble . . . . . . . . . . . . . . . Listes, les et piles 4.1 Cours . . . . . . . . . . . . . . . . . . 4.1.1 Algorithmique des listes . . . . 4.1.1.1 Copie de liste . . . . 4.1.1.2 Rversion dune liste 4.1.1.3 Tri de listes . . . . . 4.1.2 Pile . . . . . . . . . . . . . . . 4.1.3 File . . . . . . . . . . . . . . . 4.2 Travaux pratiques . . . . . . . . . . . . 4.2.1 Tri de liste . . . . . . . . . . . 4.2.2 Ralisation de les . . . . . . . Graphes 5.1 Cours 5.1.1 5.1.2 5.1.3

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

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

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

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

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

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

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

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

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

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

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

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

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

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . . . . . . . Introduction . . . . . . . . Concepts associs . . . . . Reprsentation des graphes

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

126 5.1.3.1 Matrice dadjacence . . . . . . . 5.1.3.2 Liste dadjacence . . . . . . . . 5.1.3.3 Comparaison des reprsentations 5.1.4 Problmes sur graphes . . . . . . . . . . . 5.1.4.1 Parcours de graphe . . . . . . . . 5.1.4.2 Fermeture transitive . . . . . . . 5.1.4.3 Arbre de recouvrement . . . . . 5.1.4.4 Circuits . . . . . . . . . . . . . 5.1.5 Arbres . . . . . . . . . . . . . . . . . . . . Travaux pratiques . . . . . . . . . . . . . . . . . . 5.2.1 Reprsentation de graphes . . . . . . . . . 5.2.2 Recherche de circuit . . . . . . . . . . . . 5.2.3 Calcul de connexit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

TABLE DES MATIRES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 81 82 82 83 83 83 83 85 86 86 87 87 89 89 89 89 90 90 91 92 93 93 93

5.2

Algorithmes sur graphes 6.1 Cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Parcours de graphe . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1.1 Parcours en profondeur . . . . . . . . . . . . . . . . . . 6.1.1.2 Choix des structures de donnes . . . . . . . . . . . . . . 6.1.1.3 Algorithme gnral de parcours . . . . . . . . . . . . . . 6.1.1.4 Exemple : recherche de chemin . . . . . . . . . . . . . . 6.1.1.5 Exemple : sommets accessibles depuis un sommet donn 6.2 Travaux pratiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Fermeture transitive . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.2 Recherche de circuit . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

II

Annexes
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

95
97 97 98 99 100 101 101 102 102 102 102 103 104 104

A Modularit des programmes en C A.1 Construction dun programme C . . . . . . . . . . . . . . . A.1.1 Le prprocesseur . . . . . . . . . . . . . . . . . . . A.1.2 Le compilateur, lassembleur . . . . . . . . . . . . . A.1.3 Lditeur de liens . . . . . . . . . . . . . . . . . . . A.1.4 Le run-time . . . . . . . . . . . . . . . . . . . . . . A.1.4.1 Chargement dynamique des bibliothques A.1.4.2 Bibliothques partages . . . . . . . . . . A.2 Modularit en C . . . . . . . . . . . . . . . . . . . . . . . . A.2.1 Principe gnral . . . . . . . . . . . . . . . . . . . A.2.2 Un premier exemple . . . . . . . . . . . . . . . . . A.2.3 Utilisation du prprocesseur . . . . . . . . . . . . . A.2.4 Comment dcouper un programme source ? . . . . . A.2.5 Construction automatique des programmes . . . . .

TABLE DES MATIRES B La saisie de donnes au clavier B.1 Rappel . . . . . . . . . . . . . . . . . . . . . . . . . . . B.2 Fonctionnement par dfaut du terminal . . . . . . . . . . B.2.1 Traitement canonique . . . . . . . . . . . . . . . B.2.2 Traitement des signaux . . . . . . . . . . . . . . B.2.3 Traitement de lcho . . . . . . . . . . . . . . . B.3 Quelques incidences sur la lecture de donnes . . . . . . B.3.1 Un petit problme . . . . . . . . . . . . . . . . B.3.2 Comment viter cela ? . . . . . . . . . . . . . . B.3.2.1 La procdure fflush . . . . . . . . B.3.2.2 Lecture en deux temps . . . . . . . . . B.4 Programmation du pilote du terminal . . . . . . . . . . . B.4.1 Traitement non-canonique . . . . . . . . . . . . B.4.1.1 Exemple important . . . . . . . . . . B.4.2 cho, signaux . . . . . . . . . . . . . . . . . . . B.5 Dans la pratique . . . . . . . . . . . . . . . . . . . . . . B.5.1 Quelques utilisations de stty . . . . . . . . . . B.5.2 Conguration en C du pilote de terminal . . . . . B.6 Modication de lentre standard . . . . . . . . . . . . . B.6.1 Redirection partir dun chier . . . . . . . . . B.6.2 Redirection partir des rsultats dun programme Index Bibliographie Table des matires

127 107 107 107 108 109 109 110 110 110 110 111 111 111 112 112 112 113 113 115 115 116 117 121 123

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

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

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

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

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

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

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

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

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

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

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

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

Vous aimerez peut-être aussi