ebook - Support de cours Langage C de Patrick Corde


Télécharger ebook - Support de cours Langage C de Patrick Corde
3.53.5 étoiles sur 5 a partir de 1 votes.
Votez ce document:

Télécharger aussi :


Ebook - Support de cours Langage C de Patrick Corde

...

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 le compilateur le plus répandu. Le PC Ca longtemps assuré sa propre norme puisqu'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.

— 1989 sortie du premier document normalisé appelé norme ANSIX3-159.

— 1990 réalisation du document final normalisé auprès de l'ISO:ISO/IEC9899[ISO89];

— 1999 première révision de la norm eISO/IEC9899[ISO99].

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 chapitres pour n'être approfondis que plus tard.

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:

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.

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 œuvre cinq utilitaires:

— le préprocesseur, que nous appellerons cpp;

— le compilateur C, que nous appellerons c0+c1 car il peut être découpéendeuxparties;

— l'optimiseurdecode,appeléc2;

— l'assembleur, que nous nommeron sas;

— l'éditeur de liens, dont le nom le plus courant est ld.

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.

...

Généralités sur la syntaxe

Cechapitreintroduitlespremiersconceptsindispensablesàlacompréhensiond'unprogramme C, à savoir les règles qui constituent la syntaxe de base du langage. Ces règles sont utilisées par les compilateurs pour déterminer si une série de caractères correspond à un mot réservé, à un nom ouàuneconstante.



2.1 Mise en page

Le format du texte est libre. La mise en page n'a aucune signification pour le compilateur. Elle est importante pour la lisibilité du programme. Les lignes de directives de pré-compilation (voir chapitre15)commencentparun#.

2.1.1 Identifiant

Les identifiants sont utilisés pour donner des noms aux différentes entités utilisés dans le langage. Un identifiant est construit selon le modèle

— il commence par une lettre ou le souligné

2.1.2 Espaces lexicaux

Les différents identifiants d'un programme en langage C sont classés dans des espaces lexi¬caux qui permettent d'isoler les noms selon leur signification. Les espaces lexicaux utilisés par un compilateur Csontles suivants:

Ces espaces sont isolés ce qui permet d'avoir une étiquette qui porte le même nom qu'une variable mais jamais une fonction qui porte le même nom qu'une variable.

2.2 Mots réservés

Ce sont les mots prédéfinis du langage C. Ils ne peuvent pas être réutilisés pour des identifiants. Ils sont relatifs aux différents concepts du langage

type des données

char const double float int long short signed unsigned void volatile

classes d'allocation

auto extern register static

constructeurs

enum struct typedef union

instructions de boucle do for while

sélections

case default else if switch

ruptures de séquence

break continue goto return

diversasm entry fortran sizeof

2.3 Constantes

Les constantes servent dans l'expression des tailles, l'initialisation des variables et dans les expressions. Les constantes de type chaîne de caractères ont un statut particulier : elles permettent de constituer des tableaux de caractères anonymes. Les caractères de ces tableaux peuvent être constants.

Nous approfondirons l'expression des constantes dans la section 3.2 qui traite des types de variables. Voici cependant quelques exemples de constantes

— constante entière:10;

— constante flottante:121.34;

— caractère simple : 'a';

— chaîne de caractères : "message".

2.4 Instructions

Une instruction est

— soit une instruction simple,

— soit un instruction composée.

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

Ce sont les types prédéfinis du compilateur. Ils sont au nombre de six:

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 de type int et une variable de type signed 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.

float ce type sert pour les calculs avec des parties dé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 ce type est récent, il permet de représenter des nombres avec parties décimales qui nécessitent une très grande pré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 un signed comme l'opérateur "+" unaire fait pendant à l'opérateur"-" unaire.

3.1.2 Types avec parties décimales

Comme nous l'avons déjà dit, les types avec parties décimales sont au nombre de trois:

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

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 sont des inégalités non strictes, à 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 machines du type station d etravail.

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.

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.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 et le programme 3.2 est un exemple de fichier .

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.Leprogramme3.3estunextraitd'unexempledefichier.

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.

...

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'infor¬mation dans l'évaluation de l'expression. Ces règles sont décrites dans l'encart ci-dessous.

...

Les pointeurs

Origine du besoin

Les tableaux et pointeurs Les pointeurs

Origine du besoin

Les pointeurs n’ont pas été inventés par hasard...

Impossibilité de modifier une variable passée en paramètre

Tableaux de taille variable à parcourir

Notions de structures (struct)

Arithmétique des pointeurs est simple

Proximité de la manipulation de la mémoire



D’où

La notion d’adresse d’une case mémoire

La notion de pointeur (contient une adresse)

J.-F. Lalande Programmation C 83/204

Les pointeurs n’ont pas été inventés par hasard...

– Impossibilité de modifier une variable passée en paramètre

– Tableaux de taille variable à parcourir

– Notions de structures (struct)

– Arithmétique des pointeurs est simple

– Proximité de la manipulation de la mémoire

D’où

– La notion d’adresse d’une case mémoire

– La notion de pointeur (contient une adresse)

Adresse et pointeur

Les tableaux et pointeurs Les pointeurs

Adresse et pointeur I

Adresse d’une variable :

int variable;

scanf("%i", &variable);

printf("addresse de variable: %i \n", &variable);

printf("contenu de variable: %i \n", variable);

Pointeur p vers une variable :

int variable;

int ∗ p;

p = &variable;

J.-F. Lalande Programmation C 84/204

Adresse d’une variable :

int variable; 1

scanf("%i", &variable); 2

printf("addresse de variable: %i \n", &variable); 3

printf("contenu de variable: %i \n", variable); 4

Pointeur p vers une variable :

int variable; 1

int ∗ p; 2

p = &variable; 3

Exemple d’un pointeur sur un entier :

– Le pointeur p pointe vers la variable

– Le pointeur p contient l’adresse de la variable

– d’où l’homogénéité de p = &variable;.

Opérateur * donne la valeur d’un pointeur :

int variable; 1

int ∗ p; 2

p = &variable; 3

printf("addresse de variable: %i \n", p); 4

printf("contenu de variable: %i \n", ∗p); 5

Ne pas confondre :

– La signification du &

– La signification du *

– Et s’aider de petits dessins à l’occasion !

Exercice 34 Créez un pointeur p sur un float et un float f. Enregistrez 12.3 dans f et faites pointer p sur ce flottant.

Exercice 35 Créez un pointeur q sur un float. Initialisez directement la valeur pointée par q avec 45.5. Créez un pointeur r et faites le pointer sur la même valeur que q.

Exercice 36 Créez deux pointeurs et faites les pointer sur deux entiers. Echangez alors les valeurs pointés.

3.3 Conséquences sur les tableaux, fonctions, chaines

Retour sur les fonctions

Les tableaux et pointeurs Conséquences sur les tableaux, fonctions, chaines

Retour sur les fonctions

Utilisation dans les fonctions :

Passage de paramètre par adresse ou par pointeur

Retour de fonction : adresse ou pointeur

int main() {

int ∗ p; int ∗ res;

res = ma_fonction(p); }

void ma_fonction(int ∗ param)

{

∗param = 45;

}

J.-F. Lalande Programmation C 87/204

Utilisation dans les fonctions :

– Passage de paramètre par adresse ou par pointeur

– Retour de fonction : adresse ou pointeur

long ∗px, ∗px2; 1

px2 = px + i; // qui équivaut en fait a: 2

px2 = (long ∗) ((int) px + i ∗ sizeof (long)); 3

i = px2 − px; 4

Le parcours d’un tableau :

– se fait en utilisant un pointeur sur les éléments

– utilise l’arithmétique des pointeurs (très pratique)

Exemple :

int tableau[256] = {12,142,12,2,48}; 1

int ∗ p1 = tableau; 2

for (i= 0; i < 5; i++) 3

printf("tableau[%i]=%i\n", ∗(p1+i)); 4

for (i= 0; i < 5; i++) // Est aussi possible: 5

printf("tableau[%i]=%i\n", ∗(tableau+i)); 6

while (p1 != tableau + 5) 7

printf("tableau[%i]=%i\n", ∗(p1++)); 8

Exercice 40 Quel est l’erreur dans le while précédent ? Comment la corriger ?

Exercice 41 Codez deux pointeurs qui pointent sur le début et la fin d’un tableau. Puis faites parcourir à ces deux

pointeurs le tableau l’un de gauche à droite et l’autre de droite à gauche.

Exercice 42 Ecrire une fonction qui prend en paramètre un tableau et renvoie un pointeur sur l’élément minimum


200