Programmation impérative en C - LIPN - Université Paris 13
3.3.3 Exemple de tableau ... A ce stade, il faut respecter la syntaxe du langage de
programmation choisi et préciser tout ce qui était ... Le programme doit
également encore ensuite être corrigé pour être simplement maintenu à son
niveau de ...
part of the document
6
1.3.2 Adresse PAGEREF _Toc525037008 \h 7
1.4 Instructions et programme PAGEREF _Toc525037009 \h 7
1.4.1 Laffectation PAGEREF _Toc525037010 \h 8
1.4.2 Les instructions dentrée/sortie PAGEREF _Toc525037011 \h 8
1.4.3 Les instructions de contrôle PAGEREF _Toc525037012 \h 9
1.5 Système dexploitation, fichiers PAGEREF _Toc525037013 \h 9
1.6 Compilation PAGEREF _Toc525037014 \h 10
2 Eléments de base du langage C PAGEREF _Toc525037015 \h 13
2.1 Premiers programmes PAGEREF _Toc525037016 \h 13
2.1.1 « Bonjour tout le monde ! » PAGEREF _Toc525037017 \h 13
2.1.2 Conversion de degrés Fahrenheit en degrés Celsius PAGEREF _Toc525037018 \h 16
2.1.3 Constantes symboliques PAGEREF _Toc525037019 \h 20
2.1.4 Lecture dentrées PAGEREF _Toc525037020 \h 22
2.2 Lalphabet et les mots du langage PAGEREF _Toc525037021 \h 24
2.2.1 Identificateur PAGEREF _Toc525037022 \h 24
2.2.2 Mots réservés PAGEREF _Toc525037023 \h 25
2.2.3 Constantes PAGEREF _Toc525037024 \h 25
2.2.4 Opérateurs, délimiteurs et séparateurs PAGEREF _Toc525037025 \h 26
2.3 Notation syntaxique PAGEREF _Toc525037026 \h 27
3 Types, opérateurs et expressions PAGEREF _Toc525037027 \h 28
3.1 Types de base PAGEREF _Toc525037028 \h 28
3.1.1 Types entiers PAGEREF _Toc525037029 \h 28
3.1.2 Types réels PAGEREF _Toc525037030 \h 30
3.1.3 Type caractère PAGEREF _Toc525037031 \h 32
3.1.4 Autres types PAGEREF _Toc525037032 \h 33
a. Type booléen PAGEREF _Toc525037033 \h 33
b. Type chaine de caractères PAGEREF _Toc525037034 \h 34
c. Type void PAGEREF _Toc525037035 \h 34
3.2 Opérateurs et expressions PAGEREF _Toc525037036 \h 34
3.2.1 Opérateurs numériques et logiques PAGEREF _Toc525037037 \h 35
3.2.2 Conversions de types PAGEREF _Toc525037038 \h 37
a. Conversion implicite PAGEREF _Toc525037039 \h 37
b. Conversion forcée (casting) PAGEREF _Toc525037040 \h 38
3.2.3 Opérateurs de modification de variables : les affectations PAGEREF _Toc525037041 \h 38
a. Affectations de variables PAGEREF _Toc525037042 \h 39
b. Incrémentation et décrémentation PAGEREF _Toc525037043 \h 40
3.3 Types dérivés des types de base PAGEREF _Toc525037044 \h 40
3.3.1 Types définis par lutilisateur PAGEREF _Toc525037045 \h 41
3.3.2 Structures PAGEREF _Toc525037046 \h 41
3.3.3 Exemple de tableau PAGEREF _Toc525037047 \h 43
3.4 Autres opérateurs PAGEREF _Toc525037048 \h 46
3.4.1 Opérateurs binaires (bits à bits) PAGEREF _Toc525037049 \h 46
3.4.2 Opérateur sizeof PAGEREF _Toc525037050 \h 46
3.4.3 Opérateur conditionnel PAGEREF _Toc525037051 \h 46
3.4.4 Opérateur de séquence PAGEREF _Toc525037052 \h 47
3.4.5 Divers PAGEREF _Toc525037053 \h 47
3.4.6 Précédence et associativité des opérateurs PAGEREF _Toc525037054 \h 47
4 Les instructions du langage PAGEREF _Toc525037055 \h 49
4.1 Instruction et blocs dinstructions PAGEREF _Toc525037056 \h 49
4.2 Instructions de contrôle PAGEREF _Toc525037057 \h 50
4.2.1 Linstruction conditionnelle PAGEREF _Toc525037058 \h 50
4.2.2 Linstruction de branchement (switch) PAGEREF _Toc525037059 \h 53
4.2.3 Boucle while et boucle for PAGEREF _Toc525037060 \h 54
4.2.4 Boucle do-while PAGEREF _Toc525037061 \h 56
4.2.5 Instructions de rupture de séquence PAGEREF _Toc525037062 \h 56
4.3 Instructions dentrée/sortie PAGEREF _Toc525037063 \h 57
4.3.1 Entrée/sortie de caractères : getchar et putchar PAGEREF _Toc525037064 \h 57
4.3.2 Entrée/Sortie « formatées » : Printf et scanf PAGEREF _Toc525037065 \h 57
Premières notions de programmation
Généralités
La programmation recouvre lensemble des techniques permettant de résoudre des problèmes à laide de programmes sexécutant sur un ordinateur. Une étape essentielle consiste en lécriture dun texte de programme dans un langage particulier (un langage de programmation). Mais lécriture du programme, bien que fondamentale, nest quune étape du processus de programmation que lon peut décomposer de la manière suivante :
Lanalyse et la spécification du problème, qui permettent de préciser le problème à résoudre et/ou disoler les fonctionnalités du logiciel à mettre en oeuvre. Dans cette phase, on détermine quelles sont les données et leurs propriétés, quels sont les résultats attendus, ainsi que les relations exactes entre les données et les résultats.
La conception (ou modélisation) : il sagit généralement de déterminer la méthode de résolution du problème (un algorithme) et den identifier les principales étapes. Cest également dans cette phase que lon conçoit la manière dont on va mettre en uvre les fonctionnalités dun logiciel (celles qui ont été identifiées dans la phase danalyse).
Limplantation (ou codage) dans un ou plusieurs langages de programmation particuliers. Il sagit de traduire la méthode et les algorithmes préconisés en un ou plusieurs textes de programmes. A ce stade, il faut respecter la syntaxe du langage de programmation choisi et préciser tout ce qui était resté dans lombre.
La syntaxe est lensemble des mots et des règles décriture (la « grammaire ») qui détermine la structure dun texte de programme correct, cest-à-dire, que la machine peut transformer en code exécutable grâce à un compilateur prévu pour ce langage de programmation. Cette syntaxe doit être rigoureusement suivie. Un seul mot ou caractère mal placé et le texte du programme ne peut être traduit en code exécutable. Franchir cette étape nest pas très difficile car on bénéficie de laide du compilateur qui signale une à une les erreurs quil rencontre. Mais on peut malheureusement aussi commettre des erreurs de type sémantique (erreurs relatives au « sens » du programme) : dans ce cas, le texte du programme peut être transformé en code exécutable par le compilateur, mais ce code peut provoquer des interruptions brutales avec certaines données, ou pire, fournir des résultats erronés.
Le risque derreurs de codage étant en fait relativement élevé, limplantation doit toujours être suivie dune phase de mise au point dans laquelle on effectue des tests permettant de vérifier la robustesse du programme obtenu. Le programme doit également encore ensuite être corrigé pour être simplement maintenu à son niveau de performances dans un environnement en constante évolution - ce qui fait que la phase de mise au point est de durée a priori indéterminée.
Notons que lécriture du programme proprement dit ne vient quen troisième étape du processus de programmation, et que les deux premières étapes sont souvent longues. Il faut aussi être conscient du fait quil y a un travail considérable entre la fin de la seconde étape et lentrée du texte du programme dans lordinateur. En particulier, on aura parfois à se poser des questions darchitecture de programmes pour faire fonctionner ensemble des parties développées avec des éléments préexistants (récupération de données dans des bases de données ou sur Internet, etc.), ou pour articuler des modules de programmes écrits dans des langages de programmation différents.
De même, la phase de tests et de mise au point dun programme reste souvent négligée par les débutants en programmation, mais les problèmes apparaissant alors peuvent être fort complexes et coûteux de sorte que si cette étape nest pas réellement menée à bien, il est souvent plus simple de repartir de zéro, même dans le cas de programmes industriels ayant coûté plusieurs hommes/années.
La qualité du travail accompli dans les deux premières étapes reste bien entendu primordiale pour la qualité du programme produit. Différentes branches de compétences et de techniques basées sur des modélisations mathématiques se sont dailleurs développées avec succès pour résoudre des problèmes de structuration de données, donnant ainsi naissance à une branche de linformatique : lalgorithmique. (On en verra une introduction au second semestre via la présentation de quelques algorithmes de tri et de recherche délements stockés dans des structures de données classiques).
Programmation impérative
Les structures de données et la nature des algorithmes étudiés dans le cadre classique de lalgorithmique sont intimement liées à la nature des langages de programmation utilisés. Ces langages dits « impératifs », sont basés sur la notion daction exécutée, laction la plus typique étant laffectation de variable. Le concept de variable dans ces langages de programmation est très différent de la notion mathématique. Grossièrement, une variable est une case mémoire de lordinateur dans laquelle on peut stocker une valeur de donnée, numérique ou autre. Lopération dattribution de valeur à une variable sappelle laffectation. La valeur dune variable peut être récupérée et modifiée à volonté, et la programmation dans ces langages est basée principalement sur lutilisation et la modification judicieuse de variables. Ces langages sont qualifiés d impératifs, car un programme consiste en une série dordres (ou instructions) donnés à exécuter par la machine. Parmi ces ordres, laffectation est une opération fondamentale.
Le langage C que nous allons utiliser dans ce cours est un représentant typique des langages impératifs. Ce type de langages est particulièrement adapté à la structure des ordinateurs classiques, car, bien que qualifiés de langages de programmation « évolués » - par opposition aux langages machine - ils restent très proches dans leur conception de ces langages de bas niveaux. Cest une des raisons qui les rend incontournables, car cette proximité de structure leur assure une supériorité de performances indéniable dans le contexte des architectures de machines actuelles. Une autre raison importante est que, ayant été historiquement inventé les premiers, la plupart des langages utilisés aujourdhui (comme les langages à objet), comportent eux aussi un noyau « impératif » et concervent la possibilité décrire des modules de programmes dans ce style de programmation.
Dautres types de langages sécartant plus de cette structure existent, comme les langages dits « fonctionnels » ou « déclaratifs ». Ces langages permettent de résoudre et spécifier plus facilement certains problèmes, en offrant dautres moyen de structurations de données (et en ne manipulant pas que des nombres ou des caractères). Mais nous naurons guère le temps daborder cette année la programmation quils mettent en oeuvre.
Bien quun cours de programmation ne puisse se réduire à un cours sur un ou des langages de programmation (cf. les premières étapes dun processus de programmation), leur apprentissage et leur pratique restent une nécessité absolue, car cest avec lécriture effective de programmes et leur passage en machine que le processus de programmation peut finalement aboutir à la résolution de problèmes. Il faut donc bien avoir malgré tout une petite expérience et une certaine idée de ce en quoi consistera le programme, pour franchir correctement les premières étapes danalyse et de spécification.
Nous allons donc au cours de ce premier semestre, nous consacrer dabord à lapprentissage et à la pratique dun langage impératif, le langage C, en nous exerçant sur des problèmes simples pour lesquels les deux premières phases ne sont pas trop problématiques.
Le langage C souffre de la réputation dêtre un langage de bas niveau, car il manipule le même type dobjets que la plupart des machines, cest-à-dire des caractères, des nombres et des adresses demplacement mémoire. Il na guère dopérations qui manipulent directement des chaînes de caractères, ou qui simplifient les accès en lecture/écriture sur des fichiers, ou encore permettant de synchroniser des opérations en parallèle. Mais sa taille modeste lui assure finalement lavantage de laccessibilité, et un programmeur peut raisonnablement espérer connaître et comprendre assez rapidement tout le langage et lutiliser régulièrement.
Le langage C a été conçu à lorigine aux Laboratoires Bell (en même temps que le système UNIX) pour faire de la programmation système. Il est très efficace et très utilisé dans lindustrie, car cest un langage généraliste et il reste indépendant de toute architecture de machine particulière.
Son apprentissage permet en outre dapprendre ensuite facilement dautres langages du même type (Pascal, Fortran, Cobol, Basic), et un même dautres types de langages, comme des langages à objet, car un certain nombre dentre eux en dérivent (C++, Perl, ou Java) et présentent avec lui des similitudes syntaxiques.
Au second semestre, nous complèterons ce premier apprentissage de la programmation par deux volets dapprofondissement : un premier volet introduira quelques premières notions dalgorithmique, base théorique de la programmation impérative, et un deuxième volet permettra de comprendre la manière dont les programmes peuvent finalement être exécutés, en précisant quelques particularités de larchitecture des machines actuelles.
Variables : identificateur, valeur et type, adresse
Une variable est une zone de la mémoire centrale à laquelle on donne un nom, ou identificateur, qui permet de la désigner dans le programme, et qui contient une valeur. Cette valeur reste stockée dans cette zone et ne change que si lon affecte une autre valeur à la variable.
Une variable ne contient quune seule valeur à la fois, et cette valeur est la dernière quon lui a affectée. Tant quune première valeur (valeur initiale) na pas été affectée à une variable, la valeur de cette variable est (a priori) indéterminée. Il est souvent important de préciser la valeur initiale des variables, et lon appelle cette première opération daffectation linitialisation.
Identificateur, valeur et type
Le codage des valeurs en machine consiste en une suite de 0 et de 1. Pour pouvoir déterminer la valeur correspondant à une telle représentation, il est nécessaire de connaître le type de cette valeur (le codage sen déduit et la valeur est alors déterminée, mais le type ne fait pas partie du codage). Ainsi par exemple, le code 01001111 représente aussi bien lentier 79 (codage dun entier sur 8 bits en binaire) que le caractère O (un caractère est codé ici par un entier : son code Ascii).
A chaque variable est donc associé un type qui indique lensemble abstrait des valeurs quelle peut prendre, et qui permet de récupérer sa valeur. Le type dune variable détermine également les opérations que lon peut effectuer avec elle. Il existe plusieurs types classiques dans les langages de programmation : des types numériques (entiers ou réels), le type booléen (avec seulement deux valeurs : le VRAI et le FAUX), et le type caractère. Un des rôles du compilateur est de vérifier la cohérence des types utilisés dans les opérations utilisant les variables.
Lidentificateur dune variable sert à référencer une variable, mais selon la manière dont on lemploie, il peut aussi désigner la valeur contenue à ladresse correspondante. La valeur étant interprétée en fonction du type de la variable, une variable doit toujours être déclarée avant dêtre utilisée dans le programme pour faciliter le travail du compilateur.
La déclaration dune variable dans le programme introduit son identificateur (son nom), et son type (celui de sa valeur). La déclaration du type des variables permet au compilateur deffectuer les tests de cohérence, en particulier dans le calcul dexpression et/ou de fonctions qui les utilisent.
Ainsi par exemple en C, avant dutiliser une variable entière appelée degre, on lintroduira par la déclaration :
int degre ;
qui définit degre comme variable de type int dont les valeurs possibles font partie dun sous-ensemble des entiers.
Pour des raisons de performance, la taille de la zone mémoire allouée à une variable pour stocker sa valeur est fixe. Les langages de programmation distinguent donc plusieurs types numériques, catégorisés par la taille de la zone mémoire utilisée. Ainsi, la déclaration dune variable indique finalement à la fois lensemble des valeurs quelle peut prendre, et la taille de la zone mémoire nécessaire pour stocker cette valeur.
Les valeurs manipulées sont exactes losrquon utilise des entiers, mais elles correspondent à des intervalles limités. Un domaine fréquent (entier signé codé sur 32 bits) est [ -231, +231 1 ], soit un intervalle dune taille de lordre de 2*109. Si les valeurs obtenues par calcul dépassent la capacité de représentation, elles sont tronquées (on parle de débordement) et les résultats obtenus sont faux.
Pour les « réels », lensemble correspondant au type est plus vaste (de lordre de 1O37 pour des machines 32 bits) mais ne contient quun nombre fini de réels représentables (à peu près équirépartis logarithmiquement), les autres étant représentés par le réel le plus proche. Lerreur maximale ainsi introduite dans des calculs (en valeur relative) est en général 2-24, cest-à-dire de lordre de 10-7.
Nous nous pencherons sur ces questions de représentations plus tard au second semestre, et présenterons simplement plus loin les types de base existants en C.
Adresse
La position de la zone dans laquelle est stockée la valeur dune variable en mémoire centrale sappelle ladresse de la variable. Deux variables définies simultanément ont toujours des adresses différentes. Mais cette adresse étant déterminée dynamiquement au moment de lexécution du programme, cest une donnée qui reste généralement inconnue. (Certains langages de programmation ne font dailleurs jamais référence aux adresses).
Il arrive cependant quon veuille mentionner une variable pour la désigner en tant quemplacement mémoire, et non pas en tant que valeur (cest le cas en C). Cette situation se produit en particulier lorsque lon définit des modules de traitement prenant en argument des variables dont on souhaite modifier les valeurs par ce traitement. Dans ce cas, on ne peut indiquer directement le code de ladresse, puisquil varie dune exécution à lautre, mais on peut utiliser lopérateur dadresse, noté &, qui, placé devant un identificateur de variable, désigne ladresse de cette variable.
Instructions et programme
Le texte dun programme écrit dans un langage de programmation impératif est constitué dune série dordres ou instructions donnés à exécuter à la machine. Ces instructions sont regroupées par séries (ou blocs) en un ou plusieurs modules (des fonctions) utilisant des variables. De manière générale, quand une instruction est terminée, on passe à linstruction suivante dans le texte du module de traitement.
La puissance dune instruction étant très faible, un programme est constitué de nombreuses instructions dont lexécution est, par contre, très rapide (plusieurs centaines de millions dinstructions par seconde). Certaines instructions permettent heureusement de décrire des répétitions de séries dinstructions, ce qui fait que le texte dun programme peut rester relativement court. Tout lart sera de trouver des traitements itératifs atteignant leur but via la répétition de traitements élémentaires simples.
On classe habituellement les instructions en trois catégories :
Les instructions daffectation de variables
Les instructions dentrée/sortie
Les instructions de contrôle
Laffectation
Linstruction daffectation est linstruction de base des langages impératifs. Elle a pour effet daffecter une valeur à une variable. Elle comporte deux parties : la désignation de la variable qui va recevoir une valeur, et une expression qui décrit comment calculer cette valeur (à partir de valeurs dautres variables ou de résultats de calcul de fonctions). Par exemple,
rayon * sin (angle 0.5)
est une expression dans laquelle 0.5 est une constante, rayon et angle deux variables, sin une fonction et * et - des opérateurs. Ainsi, les expressions acceptées par un langage de programmation sont assez analogues aux expressions algébriques que lon rencontre habituellement en mathématiques, mais lon verra quil existe aussi des expressions de type booléen ou caractère.
En C laffectation est notée par le signe dégalité =. La syntaxe générale suivie par une instruction daffectation est très simple : à gauche du symbole dégalité figure lidentificateur de la variable à affecter ; à droite figure une expression (qui peut être complexe) et qui décrit le calcul à effectuer pour calculer la valeur à affecter à la variable. Un point-virgule termine cette expression pour indiquer quil sagit dune instruction. Le calcul de la valeur décrite par lexpression est effectué en premier, puis cette valeur est affectée à la variable, cest-à-dire rangée (i.e. écrite) à ladresse mémoire correspondante.
Ainsi, une variable entière degre introduite par sa déclaration, peut ensuite être initialisée par laffectation dune première valeur (ici 10), de la manière suivante :
int degre ;
degre = 2*5 ;
Les instructions dentrée/sortie
Les instructions dentrée/sortie (ou de lecture/écriture) sont des opérations réalisées en C au moyen de fonctions prédéfinies (dans une librairie standard), et qui permettent au programme dinteragir avec lextérieur. Sans ce type dinstructions, un programme ne serait guère exploitable par des utilisateurs. Les instructions dentrée permettent de fournir des données de départ à un traitement, ou dorienter le déroulement du programme en cours dexécution. On peut lire des entrées en provenance de différents supports périphériques comme le clavier, ou un disque (grâce à des fichiers). Les instructions de sortie permettent inversement dimprimer des résultats ou des données sur lécran, une imprimante, ou de les sauvegarder sur disque dans un fichier.
Ainsi, linstruction C
printf("Bonjour tout le monde !") ;
permettra décrire les caractères « bonjour tout le monde ! » sur la console de lécran. On verra plus loin que la fonction printf permet aussi décrire les valeurs des variables du programme dans différents formats.
Les instructions de contrôle
Les instructions de contrôle sont des instructions destinées à rompre le déroulement en série dinstructions écrites les unes derrière les autres. Ces instructions agencent des blocs dinstructions et peuvent être imbriquées. Elles « contrôlent » littéralement le déroulement du programme, doù leur nom. Lune des instructions de base des instructions de contrôle est linstruction conditionnelle, qui soumet lexécution dun bloc dinstructions à un test préliminaire, permettant ainsi de sauter, sous certaines conditions, une portion de programme.
Ainsi, linstruction C
if (angle > 0)
resultat = mesure / angle ;
nenchainera sur linstruction daffectation de la variable resultat que si la condition sur la valeur de angle (angle >0) est réalisée. Cela permet dempêcher laffectation de la variable resultat dans le cas malencontreux où angle serait nul. Ce cas, en effet, provoquerait une erreur à lexécution du calcul, du fait de la division par zéro de la valeur de la variable mesure.
Dautres instructions de contrôle permettent de mettre en oeuvre des mécanismes de répétition appelés boucles. Les boucles effectuent de manière répétée une même suite dinstructions pourvu quune certaine condition soit réalisée (comme par exemple, que la valeur dune variable reste positive). La condition est alors testée avant chaque nouvelle exécution des instructions figurant dans la boucle jusquà ce quelle soit invalidée. Nous en verrons un exemple plus loin.
Système dexploitation, fichiers
Pour pouvoir interagir avec un ordinateur, il faut que celui-ci soit en train dexécuter un programme spécial, le système dexploitation. Il existe diverses variétés de systèmes, par exemple UNIX, WINDOWS ou VMS. Le système dexploitation est ce qui permet aux utilisateurs dexploiter lordinateur. Il permet dinterpréter des commandes (éditer un texte, créer ou détruire un fichier). Une commande est lexécution dun programme et na donc rien à voir avec une instruction (un programme est constitué de nombreuses instructions). Pour être exécuté, le code dun programme doit être chargé dans la mémoire centrale de lordinateur, et la tâche principale dun système dexploitation est précisément de charger en mémoire centrale le programme qui exécute la commande (ou le logiciel) en cours.
Les informations nécessaires au fonctionnement des programmes et du système sont stockées en mémoire permanente dans des collections de données appelées fichiers. Le système dexploitation gère aussi les fichiers, car ce sont eux qui permettent daccéder aux données et aux programmes. Un ensemble ordinateur + système dexploitation sappelle une plateforme.
Les contenus des fichiers sont de natures très diverses : modules de code de programme exécutables, modules de code partiel (ou code objet), représentations internes de nombres, de dessins, etc., et de nombreux fichiers sont essentiellement constitués de caractères texte (en particulier le texte source des programmes).
Les fichiers portent un nom qui peut être suivi (généralement après un point) dun suffixe nommé lextension. Le rôle dune extension est de donner une indication sur la nature des données contenues dans le fichier (fichier exécutable, fichier texte, etc.). Le mécanisme dorganisation des fichiers permet de les regrouper en répertoires (directories en anglais) et sous-répertoires dans une hiérarchie de noms indiquant leur emplacement et permettant de les classer en fonction de besoins particuliers (divers modules pour un logiciel, des librairies de programmes, ou encore, des boîtes de fichiers de courrier électronique, etc.).
Compilation
Un compilateur est un logiciel qui traduit un texte écrit dans un langage de programmation donné dans un autre langage : le langage machine. Un ordinateur particulier ne comprend en effet quun langage codé qui lui est spécifique : son langage machine.
Le compilateur du langage C est un programme complexe qui effectue cette traduction en plusieurs étapes. Ces étapes sont exécutées par plusieurs programmes, mais lutilisateur naura pas besoin de les lancer et utilisera une commande générale (ou bien il bénéficiera dun environnement intégré de programmation, dans lequel il pourra éditer le texte de son programme et lancer la compilation ou lexécution). Il est cependant utile den avoir connaissance pour une meilleure compréhension du langage.
Il existe en particulier une première phase de compilation qui est exécuté par un préprocesseur. Un préprocesseur est un programme de prétraitement qui passe avant un autre programme pour en modifier la source (ici le texte) dentrée. Tous les compilateurs C sont munis dun préprocesseur, et certaines directives du texte dun programme sadressent à lui.
Le compilateur permet en outre de produire des modules de codes en partie incomplets. Ce type de code sappelle un code objet par opposition au code exécutable (entièrement binaire). Un module de code objet est un module dont le code nest pas directement exécutable, car certains éléments (des adresses de variables) ne sont pas encore connus.
Pour produire un module exécutable, il faut lier ensemble différents modules objet comblant les lacunes des uns ou des autres au sein dun même code. Léditeur de liens est un programme, qui fait partie du logiciel de compilation, et qui permet de rendre exécutable la liaison de plusieurs modules objet, en vérifiant que toutes les variables sont correctement définies.
Ce mécanisme permet de mettre à la disposition des programmeurs des bibliothèques (ou librairies) de fonctions précompilées en code objet, et, plus généralement, décrire un programme à partir de plusieurs fichiers source qui pourront être mis au point et compilés séparément. En effet, à partir du texte dun module de programme, on peut obtenir un module de code objet qui pourra être utilisé ailleurs (cas des fonctions définies dans une librairie de fonctions), ou symétriquement, faire référence à des variables ou des fonctions définies ailleurs dans un module de programme (cas des programmes utilisant une fonction de librairie, ou définissant eux-mêmes plusieurs modules de fonctions compilés séparémment).
La figure suivante montre comment seffectue la compilation dun programme utilisant les fonctions dune librairie.
Figure 1
Compilation dun programme utilisant une librairie de fonctions
Pour pouvoir utiliser les fonctions de la librairie, le module source du programme devra simplement en faire la déclaration en tête de fichier. Une procédure analogue sapplique aux variables utilisées par plusieurs modules dans le cas de la compilation en modules séparés. Les variables définies ailleurs devront être déclarées (avec la mention spéciale extern en C) en tête du fichier de module source avant dêtre utilisées.
Eléments de base du langage C
Premiers programmes
La meilleure façon dapprendre un nouveau langage est de lire des programmes commentés. Nous allons donc dans cette section introductive, parcourir les quelques notions introduites dans le chapitre précédent sur quelques exemples très simples.
« Bonjour tout le monde ! »
Un premier exemple servant toujours dintroduction dans lapprentissage dun langage de programmation, est le programme qui affiche à lécran les mots « hello, world ». En voici une version française :
#include
int main ( )
{
printf("Bonjour tout le monde !\n") ;
return 0 ;
}
Les numéros figurant à gauche sur chaque ligne nous permettront de les commenter une à une, mais ils ne font pas partie du texte du programme.
La première ligne figure dans la plupart des programmes C. Elle indique au compilateur quil faut inclure la déclaration des fonctions dentrée/sortie de la librairie standard, car ce programme utilise lune dentre elles : la fonction printf.
Cette ligne commence par le symbole # car elle ne fait pas tout à fait partie du texte du programme : cest en fait une directive adressée au préprocesseur. Elle commande linclusion automatique dun fichier de librairie intitulé stdio.h qui contient la déclaration des fonctions de la librairie dentrée/sortie. Tout le texte de ce fichier sera littéralement inclus ici, à la place de cette première ligne. Linclusion est effectuée automatiquement, avant compilation, par le préprocesseur, modifiant ainsi le texte du programme qui sera effectivement compilé. Les déclarations contenues dans le fichier stdio.h sont nécessaires au compilateur pour vérifier que la fonction printf est correctement utilisée par le programme.
Ce fichier sappelle stdio.h pour STandarD Input/Output, et son nom se termine par lextension .h qui indique quil sagit dun fichier de déclarations. La seconde ligne est vide, mais permet de séparer le reste du programme de linclusion initiale des fichiers de déclarations, appelés pour cette raison, fichiers den-tête, parce quils sont toujours inclus en tête du programme.
La troisième ligne introduit la définition de la fonction principale du programme. Cette définition sétend jusquà laccolade fermante (ligne7), et les instructions figurant entre les deux accolades (ligne 4 et ligne 7) sont les instructions qui seront exécutées par le programme.
Un programme consiste en un nombre arbitraire de variables et de fonctions (petits modules de traitement). Lune dentre elles est primordiale et obligatoire pour lobtention dun code exécutable : la fonction principale. Cest elle qui décrit ce que fait le programme - les autres nétant définies que par commodité, pour être utilisées par elle. En C, on ne peut modifier le nom de la fonction principale qui sintitule toujours main.
La définition dune fonction comporte un bloc dinstructions figurant entre accolades, qui indiquent les opérations quelle va effectuer (plus éventuellement, on le verra plus loin, un ensemble de variables lui permettant de stocker des résultats intermédiaires). Le rôle dune fonction mathématique est traditionnellement de fournir un résultat à partir des valeurs initiales de paramètres. Les fonctions dun programme C sont conformes à ce schéma, puisquelles peuvent prendre en considération des paramètres de départ, et quelles retournent, après exécution de leurs instructions, une valeur. Mais elles sont susceptibles aussi davoir dautres effets, comme ici, limpression dun message sur la console.
Dans le cas de la fonction main, des paramètres initiaux peuvent être fournis au lancement de la commande correspondant au programme compilé. Nous y reviendrons plus loin. Dans cet exemple, le programme lancé ne pourra pas prendre en compte de paramètres. En effet, les paramètres doivent, dans la définition dune fonction, être introduits entre les deux parenthèses figurant après le nom de la fonction, et ici, ligne 3, le mot main est suivi simplement dune parenthèse ouvrante et dune fermante, sans paramètres interieurs.
De manière générale, la définition dune fonction débute par le type de la valeur quelle retourne, et indique entre parenthèses la liste et le type de chacun des paramètres quelle prend en considération. Ici, la ligne 3 précise que la fonction main retourne un entier de type int, et quelle ne prend aucun paramètre. Mais nous ignorerons pour linstant la valeur retournée par la fonction main. Dans le cas de toute autre fonction, la valeur retournée par une fonction est très importante, car elle sera utilisée ailleurs dans le programme (par exemple, dans une affectation de variable, ou pour fournir une valeur de départ à une autre fonction). Mais la fonction main est une fonction particulière, et la valeur finale quelle retourne ne peut être utilisée quau niveau de linterprète de commande du système dexploitation - niveau doù on lance lexécution du programme. Cette valeur doit nécessairement être un entier, mais la plupart du temps, elle sera ignorée du système.
Les lignes 5 et 6 situées entre les deux accolades constituent ce que lon appelle généralement le corps de la fonction, et ici, parce quil sagit de la fonction main, le corps du programme. Le corps dune fonction contient le bloc dinstructions qui est exécutée par cette fonction. Les instructions qui y figurent finissent par un point-virgule et sont exécutées lune après lautre. Cest ce bloc dinstructions qui définit ce que fait la fonction, la ligne 3 ne déclarant que les types associés aux paramètres qui lui seront fournis en entrée, et le type de la valeur quelle retourne.
Ici, le corps du programme ne contient que deux instructions. La première,
printf("Bonjour tout le monde !\n") ;
est un appel à la fonction printf de la librairie dentrée/sortie standard. Cette fonction prend ici un argument, "Bonjour tout le monde !\n", qui est une chaîne de caractères (reconnaissable à cause des guillemets), et cette fonction a pour effet dimprimer cette chaîne de caractères à la console. Les guillemets servent à délimiter les caractères de la chaîne. Le premier caractère imprimé à la console par cette fonction sera donc le caractère B.
Un caractère dans la séquence entre guillemets se représente lui-même, mais il arrive quon doive représenter des caractères « non imprimables » comme lindication dun passage à la ligne suivante sur la console, ou lavancement jusquà une marque de tabulation.
Ces cas particuliers sont décrits par des séquences de caractères spéciaux. Ainsi, la séquence \n dune chaîne de caractères indique un passage à la ligne suivante (newline character) et non pas la suite des deux caractères \ et n. Leffet de la fonction précédente sera donc dimprimer la suite Bonjour tout le monde ! à la console, puis de passer à la ligne suivante. Le dernier caractère visible sera donc le point dexclamation et le prompteur ou limpression suivante se trouveront sur une autre ligne.
Il est obligatoire dutiliser la séquence \n pour inclure un caractère newline dans largument passé à la fonction printf. Si vous tentez décrire dans votre programme
printf("Bonjour tout le monde !
") ;
le compilateur signalera un message derreur et refusera de poursuivre son traitement. Par contre, on peut utiliser les trois instructions suivantes pour produire exactement le même effet :
printf("Bonjour ") ;
printf("tout le monde !") ;
printf("\n") ;
Notez que les deux caractères \n nen représentent quun seul (le newline) dans la chaîne entre guillemets. Le caractère \ est en effet interprété comme un caractère qui permet déchapper au traitement standard (on parle de caractère déchappement). Ainsi, le caractère \ est en réalité ignoré, et a pour effet de modifier linterprétation donné au caractère suivant. Ce mécanisme déchappement permet de désigner des caractères invisibles. On en verra plus loin dautres exemples, comme \t pour le caractère tab de tabulation,\b pour le caractère backspace qui permet de reculer, \" pour le guillemet (qui sans cela serait le signe que la chaîne à imprimer est terminée), et \\ pour le backslash lui-même.
La dernière instruction est une instruction de retour,
return 0;
qui indique la fin de lexécution de la fonction, et la valeur quelle retourne. Pour une fonction quelconque, ce résultat est rapporté dans le calcul où figurait lappel de la fonction. Dans ce cas particulier, puisquil sagit de la fonction principale, la terminaison de la fonction entraîne aussi celle du programme, et la valeur zéro est rapportée au système dexploitation (là où a été lancé le programme).
Par convention, sous UNIX et sous WINDOWS, une valeur de retour nulle indique une terminaison correcte du programme. Cependant, dans le système VMS, la valeur de retour utilisée par convention pour signaler que tout sest bien passé nest pas 0, mais 1. On indiquera dans le troisième exemple comment écrire un programme général de ce point de vue.
Conversion de degrés Fahrenheit en degrés Celsius
Ce deuxième programme utilise la formule °Celsius = (5/9)(°Fahrenheit-32) pour afficher une table de correspondance entre degrés Fahrenheit et degrés Celsius :
0 -17
20 -6
40 4
60 15
80 26
100 37
120 48
140 60
Ce programme consiste encore en la définition dune seule fonction (la fonction main), mais elle est un peu plus compliquée :
#include
/* imprime une table Fahrenheit-Celsius
pour fahr = 0, 20, ..., 140 */
int main ( )
{
int fahr, celsius;
int inf, max, ecart;
inf = 0 ; /* temperature la plus basse */
max = 140 ; /* temperature la plus haute */
ecart = 20 ; /* ecart entre températures */
fahr = inf ;
while (fahr