Cours langage C

Introduction à la Programmation en C


Télécharger Introduction à la Programmation en C

★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Télécharger aussi :


Introduction à la Programmation en C ressource de formation

...

Chapitre 1

Historique et présentation

Ce chapitre essaye de placer le langage C [BK78] dans son contexte historique et technique, de manière à approcher l'état d'esprit du langage qui nous permet de deviner les règles sous-jacentes au langage sans pour ce la avoir à les mémoriser.

1.1 Historique

Le langage C [DR78] est lié à la conception du système UNIX✶ par les Bell-Labs. Les langages

ayant influencé son développement sont :

— lelangageBCPLdeM.Richards1967;

— le langage B développé aux Bell-Labs 1970.

Ces deux langages partagent avec le langage C

— les structures de contrôle;

— l'usage des pointeurs;

— la récursivité.

Ces deux langages prédécesseurs du C avaient la particularité d'être sans type. Ils ne travaillaient que sur des données décrites par un mot machine ce qui leur donnait un degré de portabilité nul. Le langage C comble ces lacunes en introduisant des types de données tels que l'entier, ou le caractère.

Les dates marquantes de l'histoire du langage C sont les suivantes

— 1970 diffusion de la famille PDP 11.

— 1971 début du travail sur le langage C, car le PDP 11 peut manipuler un octet alors que son mot mémoire est de 2 octets, il est nécessaire pour utiliser les fonctionnalités du PDP11 introduire un type de donnée char et un type int. Ces notions de type n'étant pas prises en compte par le langage B, elles le seront dans son successeur le C.

— 1972 la première version de C est écrite en assembleur par Brian W. Kernighan et Dennis M. Ritchie.

— 1973 Alan Snyder écrit un compilateur C portable (thèse MIT).

— 1975 Steve C. Johnson écrit et présente le PCC (Portable C Compiler). C'était à l'époque lecompilateurleplusrépandu.LePCCalongtempsassurésaproprenormepuisqu'ilétait plus simple de porter le PCC que de réécrire un compilateur C (25 % du code du PCC à modifier).

— 1987 début de la normalisation du langage par l'IEEE avec constitution d'un comité appelé X3J-11.

— 1989sortiedupremierdocumentnormaliséappelénormeANSIX3-159.

— 1990réalisationdudocumentfinalnormaliséauprèsdel'ISO:ISO/IEC9899[ISO89];

— 1999premièrerévisiondelanormeISO/IEC9899[ISO99].

Le livre de Philippe Dax [Dax92] est lui aussi une bonne introduction pour l'apprentissage de ce langage.

1.2 Présentation du langage

Le langage C est un langage de bas niveau dans le sens où il permet l'accès à des données que manipulent les ordinateurs (bits, octets, adresses) et qui ne sont pas souvent disponibles à partir delangagesévoluéstelsqueFortran,PascalouADA.

LelangageCaétéconçupourl'écrituredesystèmesd'exploitationetdulogicieldebase.Plus de 90% du noyau du système UNIX est écrit en langage C. Le compilateur C lui-même est écrit en grande partie en langage C ou à partir d'outils générant du langage C [SJ78]. Il en est de même pour les autres outils de la chaîne de compilation (assembleur, éditeur de liens, pré-processeur). Deplus,touslesutilitairesdusystèmesontécritsenC(shell,outils).

Il est cependant suffisamment général pour permettre de développer des applications variées de type scientifique ou encore pour l'accès aux bases de données. Par le biais des bases de données, il est utilisé dans les applications de gestion. De nombreux logiciels du domaine des ordinateurs personnels,telsqueMicrosoftWordouMicrosoftExcel,sonteux-aussiécritsàpartirdelangage Coudesonsuccesseurorientéobjet:C++[Str86].

Bien que pouvant être considéré de bas niveau, le langage C supporte les structures de base nécessaires à la conception des applications structurées. Cette caractéristique le range dans la catégoriedeslangagesdehautniveau.Ilestaussiundespremierslangagesoffrantdespossibilités de programmation modulaire, c'est-à-dire qu'un programme de langage C peut être constitué de plusieurs modules. Chaque module est un fichier source que l'on peut compiler de manière autonomepourobtenirunfichierobjet.L'ensembledesfichiersobjetsparticipantsàunprogramme doiventêtreassociéspourconstituerunfichierexécutable.

LorsquenousparlonsdulangageC,danscettepremièrepartie,nousfaisonsréférenceàceque sait faire le compilateur lui-même. Comme nous le verrons plus loin dans la section 1.4, plusieurs outils interviennent dans la transformation d'un ensemble de fichiers sources, constituant un programme, en un fichier binaire exécutable, qui est le résultat de ce que l'on appelle communément lacompilation.Ilseraitplusjustededirelescompilationssuiviesparlestraductionsd'assembleur enobjet,suiviesdelaréuniondesfichiersobjets.

Le compilateur de langage C se limite aux fonctionnalités qui peuvent être traduites efficacement en instructions machine.

La règle de fonctionnement du compilateur C, édictée ci-dessus, permet de détecter ce qui est fait directement par le compilateur lui-même et ce qui ne peut l'être. Essayons d'illustrer cette règleparquelquesexemples:

— Le compilateur C est capable de générer des instructions machine qui permettent de manipuler des éléments binaires(bits) ou des groupes d'éléments binaires. Il peut donc réaliser des masques pour sélectionner des groupes de bits, ou encore faire des décalages de bits à l'intérieur de mots machine.

— Il permet les manipulations algébriques (addition, multiplication, etc.) de groupes d'octets quireprésententdesvaleursentièresoudesvaleursdécimales.

— Ilpermetdemanipulerdescaractèresenconsidérantqu'uncaractèreestreprésentéparun octet à partir du code ASCII.

— Il ne permet pas de manipuler directement des tableaux. En particulier, il ne permet par de manipuler directement les groupes de caractères utilisés pour stocker les chaînes de caractères. Pour cela, il faut faire appel à des fonctions de bibliothèques dont le nom commence par str comme strcat O.

— De manière contradictoire à la règle précédente, le compilateur accepte l'affectation d'une collectiondedonnéesgroupées(structure)parunecollectiondedonnéesdetypeidentique, depuislanormalisationdulangage.Cecis'expliqueparlefaitqu'unestructureestconsidérée dans le langage comme une donnée simple, elle doit donc pouvoir être affectée à une autre donnéedemêmetype.

Pour réaliser des opérations plus compliquées, le programmeur doit écrire ses propres fonctions ou faire appel aux fonctions prédéfinies de la bibliothèque du langage C (voir chapitres 16 et 17). Ces fonctions sont, elles aussi, standardisées. La rédaction de la norme a supprimé un petit problème qui venait de la référence sous-jacente aux opérations possibles sur le processeur du PDP 11 de Digital Equipment sur lequel ont été conçues les premières versions du compilateur C. Cette machine était cependant suffisamment générale pour que cela ne transparaisse pas.

1.3 Idées fondamentales

Il est difficile de présenter les différents concepts du langage indépendemment les uns des autres, c'est pourquoi, certains concepts, comme la visibilité des variables, sont abordés dans les premiers chapitrespourn'êtreapprofondisqueplustard.

Nous allons décrire les principales composantes d'un programme écrit en langage C. Comme il est montré dans la figure 1.1, un programme en C est constitué d'un ensemble de fichiers sources destinésàêtrecompilésséparémentetàsubiruneéditiondelienscommune.Cesfichierssources sont aussi appelés modules et ce type de programmation est appelé programmation modulaire. Les notions de visibilités associées à la programmation modulaire sont approfondies dans le chapitre 9.

Le fait de pouvoir compiler chaque fichier source de manière autonome amène à concevoir des programmes de manière modulaire en regroupant, dans chaque fichier source, des fonctions qui manipulentlesmêmesvariablesouquiparticipentauxmêmesalgorithmes.

Chacune des parties peut être regroupée dans un ou plusieurs fichiers de langage C que l'on appelle aussi module.

Prenons pour exemple un programme qui enregistre une série de noms et notes d'élèves. Chaque nom est associé à une note. Une fois la saisie terminée le programme trie la liste des élèves par rangdenoteetaffichecettelisteainsitriée.Puisiltrielalistedesélèvesparordrealphabétique àpartirdeleursnomsetstockecettelistedansunfichiersurdisque.Cetyped'applicationpeut sedécouperentroispartiescorrespondantàlafigure1.1:

— lapartieinteractivequiéchangelesinformationsavecl'utilisateur(fichierl.c;

— lapartiedecalculquidanscecasestuntri(parnoteouparnom)(fichier2.c);

— lapartiestockagedesdonnéessurdisqueunefoiscesdonnéestriées(fichier3.c).

Chaque fichier (voir fig. 1.2) contient les éléments suivants dans un ordre quelconque

— des références à des variables ou des fonctions externes (sous forme de déclarations). Ces références décrivent les types des variables ou le prototype des fonctions utilisées dans ce module mais définies dans d'autres modules;

— des définitions de variables globales et de fonctions, qui peuvent être référencées3 dans les

autres fichiers ;

— des lignes de directives de compilation (pour le pré-processeur).

Une fonction (Fig. 1.3) est construite à partir :

— d'une interface constituée du type et du nom de la fonction suivis par les types et les noms

de ses paramètres;

— d'un bloc, appelé aussi corps de la fonction.

La fonction main() est particularisée, en ce sens que l'exécution du fichier binaire exécutable,

conçu à partir de l'ensemble des fichiers source, commence par elle.

Un bloc est constitué :

— d'une accolade ouvrante;

— desdéfinitionsdesvariableslocalesaubloc;

— desinstructions;

— d'uneaccoladefermante.

Une instruction peut être

— un bloc✹ ;

— ouuneexpression✺ suivied'unpointvirgule(❀);

— ou une instruction de contrôle de flot (test, boucle, rupture de séquence).

Nous parlerons en détail des possibilités du pré-processeur dans le chapitre 15.

1.4 En-chaîneur de passes

L'étudedes différentesactions entrepriseslorsdeceque nousappelonsdemanièreabusivela compilation, permet de mieux comprendre de quel outils proviennent les différentes caractéristiques de la programmation en langage C.

Nous prenons le cas des outils utilisés dans les systèmes de type UNIX car les actions sont facilement séparables. Dans un système de type UNIX, pour obtenir un fichier exécutable à partir d'unsourceC,lacommandeusuelleestcc✻ :

cc options nom_du_fichier.c

Ces compilateurs sont en fait des en-chaîneurs de passes; nous allons voir l'usage, le fonctionnement, et les options de cc. Cet outils sert à appeler les différents utilitaires nécessaires à latransformationd'unprogrammeCenunfichierexécutable.L'en-chaîneurdepassesccmeten oeuvrecinqutilitaires:

— lepré-processeur,quenousappelleronscpp;

— lecompilateurC,quenousappelleronsc0+c1carilpeutêtredécoupéendeuxparties;

— l'optimiseurdecode,appeléc2;

— l'assembleur,quenousnommeronsas;

— l'éditeurdeliens,dontlenomlepluscourantestld.

La figure1.4 donne un organigramme des différentes actions entreprises par un en-chaîneur de passes. Les fichiers temporaires de 1 à 3 décrits dans cette figure sont utilisés par les outils pour stockerlesdonnéesintermédiairesnécessairesàlatraductiondesdifférentscodes.

1.4.1 Pré-processeur

Le pré-processeur ou pré-compilateur est un utilitaire qui traite le fichier source avant le compilateur. C'est un manipulateur de chaînes de caractères. Il retire les parties de commentaires, qui sont comprises entre /* et */. Il prend aussi en compte les lignes du texte source ayant un # en première colonne pour créer le texte que le compilateur analysera. Ses possibilités sont de trois ordres(voirchap.15):

— inclusiondefichiers;

— définitiond'aliasetdemacro-expressions;

— sélectiondepartiesdetexte.

1.4.2 Compilateur

Le compilateur lit ce que génère le pré-processeur et crée les lignes d'assembleur correspondantes. Le compilateur lit le texte source une seule fois du début du fichier à la fin. Cette lecture conditionnelescontrôlesqu'ilpeutfaire,etexpliquepourquoitoutevariableoufonctiondoitêtre déclaréeavantd'êtreutilisée.

1.4.3 Optimiseur de code

L'optimiseurdecodeéliminelespartiesducodeassembleurquinesontpasutiles.Ilremplace des séquences d'instructions par des instructions plus sophistiquées et propres au processeur. Cette opération donne un code plus compact et plus rapide. Ensuite, il optimise les sauts. Dans les premières versions de compilateur, il est arrivé que sur certaines machines l'optimisation crée de petitsproblèmesquiserésumentpar:aprèsl'optimisation,leprogrammenemarcheplus.

1.4.4 Assembleur

L'assembleur prend le code généré par le compilateur, éventuellement modifié par l'optimiseur, et génère un fichier en format relogeable. Ce fichier possède des références insatisfaites qui seront résoluesparl'éditeurdeliens.SurlesmachinesutilisantunsystèmedetypeUNIX,cefichierest suffixépar.• 7.

1.4.5 Éditeur de liens

L'éditeur de liens prend le ou les fichiers en format relogeable et les associe pour créer un module chargeable. Il se sert de bibliothèques pour résoudre les références indéfinies, en particulier labibliothèquestandard(libc.a).Ilutiliseaussiunmodulespécial,crt0.o,quicontientlecode dedémarrageduprogramme.



Par défaut sur un système de type UNIX, l'éditeur de lien met le résultat de l'édition de liens dans un fichier qu'il appelle a. out.

7De mauvaises langues prétendent que sur d'autres types de système un mauvais esprit aurait osé les suffixer par .OBJ

1.4.6 Quelques options de cc

La figure1.4 donne un schéma de fonctionnement de cc, relativement aux options qui lui sont passées. Les options de l'enchaineur de passes sont précédées d'un tiret✽ (-). Voici les options lespluscourammentutilisées:

-c provoquelagénérationd'unmoduleobjetnonexécutable,ils'arrêteavantl'éditiondeliens. cc -c toto.c → toto.o

-E lance le pré-processeur seul, cpp, qui écrit sur la sortie standard ou génère un fichier suffixé par.i.

cc -E toto.c → stdout ou toto.i

-S génère le fichier assembleur après passage du pré-processeur et du compilateur. Le fichier est suffixépar.s.

cc -S toto.c → toto.s

-O optimiselecodegénéré(utilisationdec2).

-o nom donnelenomaufichierexécutableaulieudea.out. cc -o toto toto.c → toto

-v optionbavarde,ccannoncecequ'ilfait.

Deux options sont utiles sur le compilateur GNU gcc pour forcer la vérification d'une syntaxe correspondantàlanormeANSI:

-ansi avec cette option le compilateur se comporte comme un compilateur de langage C ANSI sansextensionsdelangagecorrespondantauCGNU;

-pedantic cette option demande au compilateur de refuser la compilation de programme non ansi;

-Wall cetteoptionaugmentelenombredemessagesd'alertegénérésparlecompilateurlorsqu'il rencontredesconstructionsdangereuses.

L'utilisation de ces options est recommandée lors de l'apprentissage du langage C en utilisant le compilateurgcc.

...

Chapitre 3 Types et variables

Ce chapitre traite des définitions de variables. Dans tous les langages, une définition de variable a les rôles suivants :

  1. définirledomainedevaleurdecettevariable(tailleenmémoireetreprésentationmachine);
  2. définirlesopérationspossiblessurcettevariable;
  3. définirledomainedevisibilitédecettevariable;
  4. permettre à l'environnement d'exécution du programme d'associer le nom de la variable à uneadressemémoire;
  5. initialiserlavariableavecunevaleurcompatibleavecledomainedevaleur.

En langage C, une variable se caractérise à partir de son type et de sa classe mémoire. Les points précédents numéros 1 et 2 sont associés au type de la variable; les points 3 et 4 sont associés àlaclassemémoiredelavariable.L'initialisationesttraitéedanslasection3.7.

3.1 Types de base

Cesontlestypesprédéfinisducompilateur.Ilssontaunombredesix:

void c'est le type vide. Il a été introduit par la norme ANSI. Il est surtout utilisé pour préciser les fonctions sans argument ou sans retour. Il joue un rôle particulier dans l'utilisation des pointeurs(voirchapitre10).

int c'est le type entier. Ce type se décline avec des qualificatifs pour préciser sa taille (long ou short), et le fait qu'il soit uniquement positif (unsigned) ou positif et négatif (signed) ✶. Le qualificatif signed est appliqué par défaut, ainsi il n'y a pas de différence entre une variable detypeintetunevariabledetypesigned int.

char cetypeesttrèsprochedel'octet.Ilreprésenteunentiersurhuitbits.Savaleurpeutévoluer entre-128et+127.Ilestlesupportdescaractèresausenscommunduterme.Cescaractères sont représentés par la table ASCII. Comme le type int le type char peut être qualifié de manière à être signé ou non. La norme ANSI introduit un type permettant de supporter des alphabets comprenant plus de 255 signes, ce type est appelé wchar_t. Il est défini dans le fichier<stddef . h>.

float cetypesertpourlescalculsavecdespartiesdécimales.

double c'estuntypequipermetdereprésenterdesvaleursayantunepartiedécimaleavecuneplus grandeprécisionqueletypefloat.Commenousleverronsdansl'expressiondesconstantes

(sec. 3.2.2) et dans les calculs(sec. 3.8), ce type est le plus courant pour représenter des valeurs avec parties décimales.

long double cetypeestrécent,ilpermetdereprésenterdesnombresavecpartiesdécimalesqui nécessitentunetrèsgrandeprécision.

3.1.1 Types entiers

Les mots short et long peuvent être utilisés seuls ou avec le mot int, donnant la possibilité d'avoir des définitions du type : short int ou long int. Ces définitions peuvent aussi s'écrire de manière abrégée : short ou long.

Le langage C considère les types char, short int, int et long int, comme des types entiers et permet de les mélanger lors des calculs (5.3).

A priori, les types entiers sont signés, c'est-à-dire qu'ils peuvent contenir des valeurs positives ou négatives. Par exemple, la valeur d'une variable du type char peut évoluer entre -128 et +127.

Les types entiers peuvent être qualifiés à l'aide du mot unsigned qui force les variables de ce type à être considérées comme uniquement positives. Par exemple, la valeur d'une variable du type unsigned char ne peut évoluer qu'entre 0 et 255.

Le qualificatif signed permet d'insister sur le fait que la variable peut prendre des valeurs positives ou négatives. Il fait pendant au qualificatif unsigned comme l'opérateur "+" unaire fait pendantàl'opérateur"-" unaire.

3.1.2 Types avec parties décimales

Commenousl'avonsdéjàdit,lestypesavecpartiesdécimalessontaunombredetrois:

float ce type sert pour les calculs avec des parties décimales. Il est souvent représenté selon la normeISO/IEEE754.

double cetypedeplusgrandeprécisionpermetdereprésenterdesvaleursavecpartiesdécimales. LuiaussiestsouventbasésurlanormeISO/IEEE754.

long double cetypeestrécentetpermetdereprésenterdesnombresavecpartiesdécimalessur unetrèsgrandeprécision,silamachinelepermet.

3.1.3 Tailles des types

L'espace qu'occupent les différents types en mémoire dépend de la machine sur laquelle est implanté le compilateur. Le choix est laissé aux concepteurs des compilateurs. Les seules contraintes sontdesinégalitésnonstrictes,àsavoir:

— sizeof(short) < sizeof(int) < sizeof(long)

— sizeo f (f loat) < sizeo f (double) < sizeo f (longdouble)

où sizeof est un opérateur qui donne la taille en nombre d'octets du type dont le nom est entre parenthèses.

La taille de l'entier est le plus souvent la taille des registres internes de la machine, c'est par exemple seize bits sur une machine de type ordinateur personnel✷ et trente-deux bits sur les machinesdutypestationdetravail.

La taille des variables ayant une partie décimale est le plus souvent cadrée sur la norme ISO/IEEE 754. Les machines supportant un type long double différent du type double sont assezrares.

2Pour des problèmes de compatibilité avec les anciennes versions, les compilateurs pour PC génèrent le plus souvent du code compatible 386.

Le tableau 3.1 donne quelques exemples de tailles pour des machines dont les registres ont les tailles suivantes en nombre de bits: 16 (DEC PDP11, Intel 486), 32 (SUN Sparc, Intel Pentium) et 64 (DEC Alpha). Vous remarquerez que malgré son architecture interne de 64 bits, le compilateur pour alpha utilise des entiers sur 32 bits. Il est aussi le seul processeur capable de différencier les doubledeslong doubles.

3.2 Constantes associées aux types de base

Les constantes sont reconnues par le compilateur grâce à l'utilisation de caractères qui ne participent pas à la construction d'un identifiant.

3.2.1 Constantes de type entier

Les constantes de type entier sont construites à partir de chiffres. Elles sont naturellement exprimées en base dix mais peuvent être exprimées en base huit (octal) lorsque le premier caractère estun0ouenbaseseizelorsquelesdeuxpremierscaractèressont0X(hexadécimal).

Une constante est a priori du type int si le nombre qu'elle représente est plus petit que le plus grand entier représentable. Si la valeur de la constante est supérieure au plus grand entier représentable,laconstantedevientdutypelong.

Les constantes peuvent être suffixées par un "l" ou "L" pour préciser que leur type associé est long int.

Lesconstantespeuventêtreprécédéesparunsigne"-" ou"+".

Elles peuvent être suffixées par un "u" ou "U" pour préciser qu'elles expriment une valeur sans signe (qualifiées unsigned).

Voiciquelquesexemplesdeconstantesdetypeentier: — constantesansprécisiondetype:

0377 octal

0X0FF hexadécimal

10 décimal

-20 décimal

— constantelongueentière:

  1. 120L,0364L,0x1faL
  2. 120l,0364l,0x1fal — constanteentièrenonsignée: 1. 120U,0364U,0x1faU
  3. 120u,0364u,0x1fau

 constantelongueentièrenonsignée:

  1. 120UL,0364UL,0x1faUL,120uL,0364uL,0x1fauL
  2. 120Ul,0364Ul,0x1faUl,120ul,0364ul,0x1faul

3.2.2 Constantes avec partie décimale

Les constantes avec partie décimale ont le type double par défaut. Elles peuvent être exprimées à partir d'une notation utilisant le point décimal ou à partir d'une notation exponentielle.

Ces constantes peuvent être suffixées par un "f" ou "F" pour préciser qu'elles représentent une valeur de type float.

Elles peuvent de même être suffixées par un "l" ou un "L" pour exprimer des valeurs de type long double.

Voiciquelquesconstantesavecpartiedécimale:

121.34 constante exprimée avec la notation utilisant le point décimal, son type implicite est double.

12134e-2 lamêmeconstanteexpriméeennotationexponentielle

+12134E-2 lanotationexponentielleacceptele"E" majuscule,etle"+" un-aire. 121.34f constantedevaleuridentiquemaisdetypefloatcarsuffixéeparf.

121.34l constantedevaleuridentiquemaisdetypelong doublecarsuffixéepar"l".

3.2.3 Constantes de type caractère

Les constantes du type caractère simple sont toujours entourées d'apostrophes (single gu•te). Elles peuvent être représentées selon quatre méthodes :

  1. lorsquelecaractèreestdisponibleauclaviersaufpourlabarredefractioninverséeetl'apos-trophe,lecaractèrecorrespondantestsimplemententouréd'apostrophes,parexemple'a'.
  2. uncaractèrepeutaussiêtrereprésentéparsavaleurexpriméedanslatableASCIIenutilisant une notation en base huit. Cette valeur est précédée à l'intérieur des apostrophes par une barredefractioninversée.

'\0':octetdunul,ilsertàdélimiterlesfinsdechaînesdecaractères. '\012':sautdeligne(Line Feed,LF);

'\015':retourchariot(Carriage Return,CR);

'\011':tabulationhorizontale(H•riz•ntal Tabulati•n,HT);

  1. demême,uncaractèrepeutêtrereprésentéparsanotationenbaseseize.

'\x0':caractèrenul;

'\xA':sautdeligne(LF);

'\xC':retourchariot(CR);

'\x9':tabulationhorizontale(HT);

  1. uncertainnombred'abréviationsestaussidisponible:

'\a':alert(sonnerie,BEL);

'\b':backspace(BS);

'\f':sautdepage(FF);

'\n':sautdeligne(LF);

'\r':retourchariot(CR);

'\t':tabulationhorizontale(HT); '\v':tabulationverticale(VT);

Pour spécifier qu'une constante caractère est du type (wchar_t), elle doit être précédée d'un "L". Par exemple, L'a' est la constante de type caractère long contenant le caractère "a".

3.2.4 Chaînes de caractères

Les constantes du type chaîne de caractères doivent être mises entre guillemets (double quote). Le compilateur génère une suite d'octets terminée par un caractère nul (tous les bits à 0) à partir des caractères contenus dans la chaîne. Cette suite d'octets peut être placée par le système dans une zone de mémoire en lecture seulement.

La zone correspondante est en fait un tableau de char. La chaîne est référencée par l'adresse du tableau. Par exemple, la chaîne de caractères "message" est générée par le compilateur selon le schéma de la figure 3.1.

3.3 Qualificatifs

Nous avons déjà parlé des qualificatifs unsigned et signed qui s'appliquent aux variables de type entier. Il existe d'autres qualificatifs qui ont été spécifiés par la norme. Il s'agit de const, volatile,staticetregister.

Unedéfinitiondevariablequalifiéedumotconstinformelecompilateurquecettevariableest considérée comme constante et ne doit pas être utilisée dans la partie gauche d'une affectation. Ce type de définition autorise le compilateur à placer la variable dans une zone mémoire accessible enlectureseulementàl'exécution.

Le qualificatif volatile informe le compilateur que la variable correspondante est placée dans une zone de mémoire qui peut être modifiée par d'autres parties du système que le programme lui-même. Ceci supprime les optimisations faites par le compilateur lors de l'accès en lecture de la variable. Ce type de variable sert à décrire des zones de mémoire partagées entre plusieurs programmes ou encore des espaces mémoires correspondant à des zones d'entrée-sorties de la machine.



Les deux qualificatifs peuvent être utilisés sur la même variable, spécifiant que la variable n'est pas modifiée par la partie correspondante du programme mais par l'extérieur.

Lesqualificatifsstaticetregistersontdécritsdanslasection3.5.

3.4 Taille et normalisation

Les tailles des types entiers et avec partie décimale sont définies sur chaque machine à partir de deux fichiers :

limits.h pourlestypesentiers;

float.h pourlestypesavecpartiedécimale.

Ces fichiers contiennent des définitions qui s'adressent au pré-compilateur et donnent les tailles et valeurs maximales des types de base et des types non signés.

Le programme 3.1 est un exemple de fichier <limits.h> et le programme 3.2 est un exemple de fichier <float.h>.

Programme 3.1Exempledefichierlimits.h

/* Number of bits in a char. */

#define CHAR_BIT 8

/* No multibyte characters supported yet. */

#define MB_LEN_MAX 1

/* Min and max values a signed char can hold. */

#define SCHAR_MIN (-128)

#define SCHAR_MAX 127

/* Max value an unsigned char can hold. (Min is 0). */

#define UCHAR_MAX 255U

/* Min and max values a char can hold. */ #define CHAR_MIN SCHAR_MIN #define CHAR_MAX SCHAR_MAX

/* Min and max values a signed short int can hold. */ #define SHRT_MIN (-32768)

#define SHRT_MAX 32767

/* Max value an unsigned short int can hold. (Min is 0). */ #define USHRT_MAX 65535U

/* Min and max values a signed int can hold. */ #define INT_MIN (-INT_MAX-1) #define INT_MAX 2147483647

/* Max value an unsigned int can hold. (Min is 0). */ #define UINT_MAX 4294967295U

/* Min and max values a signed long int can hold. */ #define LONG_MIN (-LONG_MAX-1) #define LONG_MAX 2147483647

/* Max value an unsigned long int can hold. (Min is 0). */ #define ULONG_MAX 4294967295U

La normalisation a aussi introduit un ensemble de types prédéfinis comme size_t qui est le type de la valeur retournée par l'opérateur sizeof. Les définitions exactes de ces types sont dans lefichier<stddef.h>.Leprogramme3.3estunextraitd'unexempledefichier<stddef.h>.

3.5 Définition de variables

Nous appellerons identifiant soit un nom de fonction, soit un nom de variable. Pour compléter ce que nous avons dit dans l'introduction de ce chapitre, voici comment les différents besoins associésàladéfinitiondevariablessontcouvertsparlelangageC:

définition du domaine de valeur decettevariableetlesopérationslégalessurcettevariable; =grâceautype.

réservation de l'espace mémoire nécessaireausupportdelavariablelorsdel'exécution; =grâceautypeetàlaclassemémoire.

initialisation de la variable à l'aide d'une constante dont le type correspond à celui de la variable;

= en faisant suivre le nom par un symbole d'affectation = et une valeur compatible avec la variable.

association d'une durée de vie àlavariablequipermetl'utilisationdanscertainespartiesdu programme(règlesdevisibilité).

=grâceàlaclassemémoireetaulieudedéfinition.

Unedéfinitiondevariableestl'associationd'unidentifiantàuntypeetlaspécification d'une classe mémoire. La classe mémoire sert à expliciter la visibilité d'une variable et son implantation en machine. Nous approfondirons les possibilités associées aux classes mémoire dans le chapitre 9 surlavisibilité.Lesclasses mémoiresont:

global cette classe est celle des variables définies en dehors d'une fonction. Ces variables sont accessibles à toutes les fonctions. La durée de vie des variables de type global est la même quecelleduprogrammeencoursd'exécution.

local ouauto:cetteclassecomprendl'ensembledesvariablesdéfiniesdansunbloc.C'estlecas detoutevariabledéfinieàl'intérieurd'unefonction.L'espacemémoireréservépourcetype devariableestallouédanslapiled'exécution.C'estpourquoiellessontappeléesaussiauto c.a.d automatique car l'espace mémoire associé est créé lors de l'entrée dans la fonction et

il est détruit lors de la sortie de la fonction. La durée de vie des variables de type local est celledelafonctiondanslaquelleellessontdéfinies.

static cequalificatifmodifielavisibilitédelavariable,ousonimplantation:

— dans le cas d'une variable locale il modifie son implantation en attribuant une partie de l'espacedemémoireglobalepourcettevariable.Unevariablelocaledetypestatiqueaun nom local mais a une durée de vie égale à celle du programme en cours d'exécution.

— dans le cas d'une variable globale, ce prédicat restreint la visibilité du nom de la variable à l'unité de compilation. Une variable globale de type statique ne peut pas être utilisée par un autre fichier source participant au même programme par une référence avec le mot réservé extern (voir point suivant).

extern ce qualificatif permet de spécifier que la ligne correspondante n'est pas une tentative de définition mais une déclaration (voir 3.9). Il précise les variables globales (noms et types) quisontdéfiniesdansunautrefichiersourceetquisontutiliséesdanscefichiersource.

register ce qualificatif permet d'informer le compilateur que les variables locales définies dans le reste de la ligne sont utilisées souvent. Le prédicat demande de les mettre si possible dans des registres disponibles du processeur de manière à optimiser le temps d'exécution. Le nombre de registres disponibles pour de telles demandes est variable selon les machines. Il estdetoutefaçonlimité(4pourlesdonnées,4pourlespointeurssurun680X0).Seulesles variableslocalespeuventêtrequalifiéesregister.

Letableau3.2donnequelquesexemplesdedéfinitionsetdéclarationsdevariables.

3.6 Types dérivés des types de base

Un type dérivé est créé à partir des types de base vus précédemment pour l'usage propre à un programme. Les types dérivés sont les tableaux, les structures et les pointeurs.

3.6.1 Tableaux et structures

Les tableaux sont des paquets de données de même type. Ils sont reconnus par la présence de crochets ouvrants et fermants lors de la déclaration ou de la définition de l'objet. La taille du

tableauestdonnéeentrelescrochetslorsdeladéfinition.Poursimplifierletravailducompilateur, lerangdesélémentsdutableaunepeutévoluerqu'entre0etlatailledutableau-1.

Les structures sont des ensembles de données non homogènes. Les données peuvent avoir des

types différents. Les structures sont déclarées ou définies selon le modèle

— struct

— unnomdestructurefacultatif

— {

— lalistedesdonnéescontenuesdanslastructure

— }

— lalistedesvariablesconstruitesseloncemodèle.

Prenonspourexemplelesdéfinitionssuivantes:

int tab[10]; struct st1 {

int a1;

float b1;

long c1; } objst1;

Danscetexemple:

  1. tab est un tableau de 10 entiers, et les éléments du tableau sont référencés par tab[0] jusqu'àtab[9];
  2. st1 est un nom de modèle de structure et objst1 est un objet de type struct st1. Les différentes parties de la structure objst1 sont accessibles par objst1.a1, objst1.b1 et objst1.c1.

Lestableauxetlesstructuressontparfoisappelésagglomératsdedonnées.

3.6.2 Pointeurs

Le pointeur est une variable destinée à contenir une adresse mémoire. Le compilateur connaissant la taille de l'espace adressable de la machine, il sait la taille nécessaire pour contenir un pointeur. Un pointeur est reconnu syntaxiquement par l'étoile (symbole de la multiplication *) qui précédesonnomdanssadéfinition.

Tout pointeur est associé à un type d'objet. Ce type est celui des objets qui sont manipulables grâce au pointeur. Ce type est utilisé en particulier lors des calculs d'adresse qui permettent de manipulerdestableauxàpartirdepointeurs(voirChap.10).

Prenonslesdéfinitionssuivantes:

int *ptint; char *ptchar;

Dans cet exemple, ptint est une variable du type pointeur sur un entier. Cette variable peut donc contenir des3 valeurs qui sont des adresses de variables du type entier (int).

De même, ptchar est une variable du type pointeur sur un caractère. Elle peut donc contenir des valeurs qui sont des adresses de variables de type caractère (char).

Le compilateur C vérifie le type des adresses mises dans un pointeur. Le type du pointeur conditionne les opérations arithmétiques (voir chap. 9 pointeurs et tableaux) sur ce pointeur.

Lesopérationslesplussimplessurunpointeursontlessuivantes:

✸Une valeur à la fois mais comme le pointeur est une variable cette valeur peut changer au cours de l'exécution du programme.

— affectationd'uneadresseaupointeur;

— utilisationdupointeurpouraccéderàl'objetdontilcontientl'adresse. Sil'ondéfinitlesvariablesdelamanièresuivante:

int in;

int tabint[10]; char car; int *ptint; char *ptchar;

Un pointeur peut être affecté avec l'adresse d'une variable ayant un type qui correspond à celui associé au pointeur. Comme nous le verrons dans la partie sur les opérateurs, le é-commercial & donne l'adresse du nom de variable qui le suit et les noms de tableau correspondent à l'adresse dupremierélémentdutableau.Lespointeursprécédentspeuventdoncêtreaffectésdelamanière suivante:

ptint = &in; ptc = &car;

Une fois un pointeur affecté avec l'adresse d'une variable, ce pointeur peut être utilisé pour accéder aux cases mémoires correspondant à la variable (valeur de la variable) :

*ptint = 12; *ptc = 'a';

La première instruction met la valeur entière 12 dans l'entier in; la deuxième instruction met le caractère a minuscule dans l'entier car.

Il est possible de réaliser ces opérations en utilisant le pointeur pour accéder aux éléments du tableau. Ainsi les lignes :

ptint=tab; *ptint=4;

affectent le pointeur ptint avec l'adresse du premier élément du tableau tabint équivalent (comme nous le reverrons dans le chapitre sur pointeurs et tableaux 10) à &tabint [0] ; puis le premier élémentdutableau(tabint[0])estaffectéaveclavaleur4.

3.7 Initialisation de variables

L'initialisationsedéfinitcommel'affectationd'unevaleurlorsdeladéfinitiondelavariable.

Toute modification de valeur d'une variable postérieure à sa définition n'est pas une initialisation mais une affectation.

Par défaut, les variables de type global (définies en dehors de toute fonction) sont initialisées avec la valeur qui correspond à tous les bits à zéro (0 pour un entier ou 0.0 pour un nombre à virgule flottante). Les variables locales, quant à elles ne sont pas initialisées avec des valeurs par défaut. Vous veillerez donc à les initialiser pour vous prémunir contre le risque d'utiliser une variable sans en connaître la valeur initiale. Sur les compilateurs anciens, pour les variables de type table au ou structure, seules les variables globales pouvaient être initialisées.

Il est possible de réaliser des initialisations selon trois techniques suivant le type de la variable

  1. dans le cas d'une variable simple, il suffit de faire suivre la définition du signe égal (=) et de la valeur que l'on veut voir attribuée à la variable.
  2. dans le cas de tableaux ou structures, il faut faire suivre la définition du signe égal suivi d'une accolade ouvrante et de la série de valeurs terminée par une accolade fermante. Les valeurs sont séparées par des virgules. Elles doivent correspondre aux éléments du tableau ou de la structure. La norme préconise que lorsque le nombre de valeurs d'initialisation est inférieur au nombre d'éléments à initialiser, les derniers éléments soient initialisés avec la valeur nulle correspondant à leur type.
  3. les tableaux de caractères peuvent être initialisés à partir d'une chaîne de caractères. Les caractères de la châine sont considérés comme constants.

Letableau3.3donnedesexemplesd'initialisation.

3.8 Conversion de type

La conversion de type est un outil très puissant, elle doit donc être utilisée avec prudence. Le compilateur fait de lui-même des conversions lors de l'évaluation des expressions. Pour cela il applique des règles de conversion implicite. Ces règles ont pour but la perte du minimum d'information dans l'évaluation de l'expression. Ces règles sont décrites dans l'encart ci-dessous.



143