Cours gratuits » Cours informatique » Cours programmation » Cours langage C » Ebook : support de cours de langage C Christian Bac

Ebook : support de cours de langage C Christian Bac


Télécharger



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

E-book - Support de Cours de Langage C Christian Bac

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 cela avoir à les mémoriser.

1.1 Historique

Le langage C [DR78] est lié à la conception du système UNIX1 par les Bell-Labs. Les langages ayant influencé son développement sont:

– le langage BC

PL de M. Richards 1967;

– 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 PCC a 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é : X3 J-11.

– 1989 sortie du premier document normalisé appelé norme ANSI X3-159.

– 1990 réalisation du document final normalisé auprès de l’ISO : ISO/IEC 9899 [ISO89] ;

– 1999 première révision de la norme ISO/IEC 9899 [ISO99].

Jusqu’en 1987, il n’y avait pas de norme. Le livre "The C Programming Language" [BK78] de B. W. Kernighan et D. M. Ritchie définit le langage. Ce livre contient une description précise du langage appelée "CReference Manual". Ce livre qui a lui aussi été remis au gout du jour en 1988 [BK88] ainsi que la norme ISO [ISO89] sont à la base de ce cours.

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 de langages évolués tels que Fortran, Pascal ou ADA.

Le langage C a été conçu pour l’écriture de systèmes d’exploitation et du logiciel de base. 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). De plus, tous les utilitaires du système sont écrits en C (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, tels que Microsoft Word ou Microsoft Excel, sont eux-aussi écrits à partir de langage C ou de son successeur orienté 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égorie des langages de haut niveau. Il est aussi un des premiers langages offrant des possibilité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 autonome pour obtenir un fichier objet. L’ensemble des fichiers objets participants à un programme doivent être associés pour constituer un fichier exécutable.

Lorsque nous parlons du langage C, dans cette première partie, nous faisons référence à ce que 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 la compilation. Il serait plus juste de dire les compilations suivies par les traductions d’assembleur en objet, suivies de la réunion des fichiers objets.

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ègle par quelques exemples :

– 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 qui représentent des valeurs entières ou des valeurs décimales.

– Il permet de manipuler des caractères en considérant qu’un caractère est représenté par un octet à partir du code ASCII2.

– 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().

– De manière contradictoire à la règle précédente, le compilateur accepte l’affectation d’une collection de données groupées (structure) par une collection de données de type identique, depuis la normalisation du langage. Ceci s’explique par le fait qu’une structure est considérée dans le langage comme une donnée simple, elle doit donc pouvoir être affectée à une autre donnée de même type.

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 à être compilés séparément et à subir une édition de liens commune. Ces fichiers sources 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 manipulent les mêmes variables ou qui participent aux mêmes algorithmes.

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 rang de note et affiche cette liste ainsi triée. Puis il trie la liste des élèves par ordre alphabétique à partir de leurs noms et stocke cette liste dans un fichier sur disque. Ce type d’application peut se découper en trois parties correspondant à la figure 1.1 :

– la partie interactive qui échange les informations avec l’utilisateur (fichier1.c;

– la partie de calcul qui dans ce cas est un tri (par note ou par nom) (fichier2.c);

– la partie stockage des données sur disque une fois ces données trié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.

...

Chapitre 2

Généralités sur la syntaxe

Ce chapitre introduit les premiers concepts indispensables à la compréhension d’un programme 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 à une constante.

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 chapitre 15) commencent par un #.

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 :

– à partir de l’alphabet : “a-z, A-Z, 0-9, _” ;

– il peut avoir jusqu’à trente et un caractères significatifs à l’intérieur d’une unité de compilation. La norme mentionne que les noms peuvent être limités à six caractères entre unités de compilation et que les éditeurs de liens peuvent ne pas tenir compte des majuscules et minuscules. Cette contrainte n’est pas suivie par les compilateurs modernes.

– 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 lexicaux qui permettent d’isoler les noms selon leur signification. Les espaces lexicaux utilisés par un compilateur C sont les suivants :

– le premier espace contient les identifiants relatifs aux types synonymes, aux variables et aux fonctions;

– le second espace est réservé aux étiquettes pour les branchements inconditionnels;

– le troisième espace est utilisé pour les noms de modèles de structures, d’unions ou d’énumérations ;

– pour chaque modèle de structure ou d’union, un espace de noms est créé pour contenir les noms de champs de la structure ou de l’union.

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

divers

asm 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.

Une instruction simple est toujours terminée par un ;.

Les instructions composées sont contenues entre deux accolades : {}.

...

Chapitre 4

Éléments de base

Le langage C est utilisé dans un contexte interactif. Ce qui veut dire que la plupart des programmes écrits en langage C font des échanges d’information avec un utilisateur du programme.

Bien sûr, le langage C est un langage des années 70 et l’idée de l’interaction avec l’utilisateur est celle des systèmes centralisés à temps partagé. Un utilisateur de ce type de système est connecté via une voie d’entrée-sortie qui permet d’échanger des caractères. Ces voies sont la plupart du temps reliées à un télétype (écran, clavier, avec sortie optionnelle sur papier). Les caractères sont écrits sur l’écran du terminal et lus à partir du clavier.

Ce modèle écran clavier qui est repris par le système UNIX est celui des interactions en langage C à partir des fichiers standard d’entrée et de sortie.

Les entrée-sorties en langage C ne sont pas prises en charge directement par le compilateur mais elles sont réalisées à travers des fonctions de bibliothèque. Le compilateur ne peut pas faire de contrôle de cohérence dans les arguments passés à ces fonctions car ils sont de type variable. Ceci explique l’attention toute particulière avec laquelle ces opérations doivent être programmées.

4.1 Bonjour

Le premier programme que tout un chacun écrit lorsqu’il apprend un nouveau langage répond à la question suivante : comment écrire un programme principal qui imprime Bonjour? Ce programme est donné dans la figure 4.1.

Il est constitué de la manière suivante :

– la première ligne est une directive pour le prprocesseur qui demande à celui-ci de lire le fichier représentant l’interface standard des entrées sorties. Le contenu de ce fichier est décrit de manière approfondie dans le chapitre 16. Cette première ligne doit être présente dans tout module faisant appel à des fonctions d’entrées sorties et en particulier aux fonctions printf() et scanf() ;

– il n’y a pas de variable globale;

– le programme contient une seule fonction main() qui joue le rôle de programme principal. Cette fonction est utilisée sans argument;

– la définition de la fonction main() commence comme pour toute fonction par une accolade ouvrante;

– elle ne contient pas de variable locale;

– la seule instruction correspond à un appel de fonction qui provoque l’écriture sur le terminal. Cette instruction est une demande d’écriture d’une chaîne de caractères;

– la fonction main() est terminée par une accolade fermante.

La fonction printf()1 est une fonction2 qui reçoit un nombre d’arguments variable. Ces arguments sont transformés en une chaîne de caractères. Cette transformation fait appel à une notion de format. Comme nous le verrons plus loin, le format définit le type de la donnée en mémoire et le mode de représentation de la donnée lorsqu’elle est affichée.

4.2 Lire et écrire

Une fonction scanf() fait le pendant à la fonction printf(). Elle permet de lire des valeurs sur le clavier. Le programme 4.1 est un exemple de lecture et d’écriture à l’écran d’une chaîne de caractères.

PROGRAMME 4.1 LECTURE ET ÉCRITURE DE CHAÎNE PAR S C AN F() ET P R I N T F()

1 #include <stdio.h> 

2 char tt[80]; /* Tableau de 80 caracteres */

3 int

4 main (int argc, char *argv[])

5 { 

6 printf ("ecrivez une chaine de caracteres : ");

7 scanf ("%s", tt); 

8 printf ("\nLa chaine entree est : %s\n", tt);

9 return 0; 

10 } 

DONNÉES EN ENTRÉE

bonjour

DONNÉES ÉCRITES SUR LE FICHIER STANDARD DE SORTIE

ecrivez une chaine de caracteres : La chaine entree est : bonjour

Ce programme ne décrit toujours pas les arguments de main(). Il contient cependant la définition d’une variable globale. Cette variable est un tableau de quatre-vingt caractères destiné à recevoir les caractères lus au clavier. Il n’y a toujours pas de variable locale. Les seules instructions sont les appels aux fonctions de lecture et d’écriture (scanf() et printf()).

4.3 Quelques opérations

Les affectations peuvent être écrites comme dans de nombreux autres langages informatiques. En voici quelques exemples:

a = b + c ;

a = b * c ;

a = b / c ;

a =  b - c ;

4.4 Plus sur printf() et scanf()

Les fonctions printf() et scanf() transforment des objets d’une représentation à partir d’une chaîne de caractères (vision humaine) en une représentation manipulable par la machine (vision machine), et vice et versa. Pour réaliser ces transformations ces fonctions sont guidées par des formats qui décrivent le type des objets manipulés (vision interne) et la représentation en chaîne de caractères cible (vision externe). Par exemple, un format du type %x signifie d’une part que la variable est du type entier et d’autre part que la chaîne de caractères qui la représente est exprimée en base 16 (hexadécimal).

Les types de conversion les plus usuels sont donnés dans la table4.1.

Dans une première approche de scanf(), nous considérons qu’il ne faut mettre que des types de conversion dans le format de lecture. Le lecteur curieux peut se reporter à la section 16.5.

Le tableau 4.2 donne un résumé des déclarations de variables et des formats nécessaires à leurs mani-pulations pour printf et scanf.

Le & est un opérateur du langage C dont nous parlons dans la section 5.1. Cet opérateur doit être mis devant le nom de la variable, dans les formats destinés à la fonction scanf, comme le montre le tableau 4.2, pour les variables dont nous avons déjà parlé sauf pour les variables du type tableau de caractères.

Les exemples de lecture d’une chaîne de caractères montrent que l’adresse du premier élément d’un tableau correspond au nom de tableau, nous en reparlerons dans le chapitre 10.

Le programme 4.2, montre qu’il est possible de faire l’écriture ou la lecture de plusieurs variables en utilisant une seule chaîne de caractères contenant plusieurs descriptions de formats.

PROGRAMME 4.2 LECTURES MULTIPLES AVEC S CANF()    

1 #include <stdio.h>     

2 int     

3 main (int argc, char *argv[])     

4 {      

5 int i = 10;     

6 float l = 3.14159;     

7 char p[50] = "Bonjour";     

8 printf ("%d bonjour %f %s\n", i, l, p);   

9 scanf ("%d%f%s", &i, &l, p);     

10 printf ("Apres lecture au clavier : %d %f %s\n", i, l, p);

11 return 0;     

12 }     

DONNÉES EN ENTRÉE

23 6.55957 salut

DONNÉES ÉCRITES SUR LE FICHIER STANDARD DE SORTIE

10 bonjour 3.141590 Bonjour

Apres lecture au clavier : 23 6.559570 salut

4.5 Exercices sur printf() et scanf()

4.5.1 Exercice 1

Réaliser un programme dans un fichier contenant une fonction main() qui réalise les écritures sui  vantes :

– écrire le caractère ’a’;

– écrire la chaîne de caractères "bonjour";

– écrire l’entier 32567 dans les formats :

– décimal;

– hexadécimal;

– octal;

– non signé;

– écrire le flottant 3.1415927 dans les formats suivants :

– notation exponentielle;

– notation avec point décimal;

– variable (g).

4.5.2 Exercice 2

Reprendre l’exercice 1 en séparant chaque impression par un retour chariot.

4.5.3 Exercice 3

Déclarer des variables des types suivants :

– entier;

– caractère;

– flottant;

– chaîne de caractères;

PROGRAMME 4.3 SUGGESTION DE CORRIGÉ CHAPITRE 4 EXERCICE 1

1 #include <stdio.h>

2 int

3 main (int argc, char *argv[], char **envp){

 4 /* ecriture de a bonjour 32567 32567 hexa 32567 octal 32567 non signe */

 5 printf("%c", ’a’);

6 printf("%s", "bonjour");

 7 printf("%d", 32567);

 8 printf("%x", 32567);

 9 printf("%o", 32567);

 10 printf("%d", (unsigned) 32567);

 11 /* ecriture de pi format e f g */

 12 printf("%e", 3.1415927);

 13 printf("%9.7f", 3.1415927);

 14 printf("%g", 3.1415927);

 15 return 0;

16 }

DONNÉES ÉCRITES SUR LE FICHIER STANDARD DE SORTIE

abonjour325677f3777467325673.141593e+003.14159273.14159 puis réaliser des opérations de lecture afin d’affecter ces variables.

4.5.4 Exercice 4

Lire et réécrire les éléments de l’exercice 3.

PROGRAMME 4.4 SUGGESTION DE CORRIGÉ CHAPITRE 4 EXERCICE 2

1 #include <stdio.h>

2 int

3 main (int argc, char *argv[], char **envp){

 4 /* ecriture de a bonjour 32567 32567 hexa 32567 octal 32567 non signe */

5 printf("%c\n", ’a’);

6 printf("%s\n", "bonjour");

 7 printf("%d\n", 32567);

 8 printf("%x\n", 32567);

 9 printf("%o\n", 32567);

10 printf("%d\n", (unsigned) 32567);

 11 /* ecriture de pi au format e f g */

 12 printf("%e\n", 3.1415927);

 13 printf("%9.7f\n", 3.1415927);

 14 printf("%g\n", 3.1415927);

 15 return 0;

16 }

DONNÉES ÉCRITES SUR LE FICHIER STANDARD DE SORTIE

abonjour

32567 7f37

77467

32567

3.141593e+00

3.1415927

3.14159

Chapitre 5

Opérateurs et expressions

Le langage C est connu pour la richesse de ces opérateurs. Il apporte aussi quelques notions innovantes en matière d’opérateurs. En particulier, le langage C considère l’affectation comme un opérateur normal alors que les langages qui l’ont précédé (par exemple FORTRAN, ADA) la considèrent comme une opération privilégiée.

Cette richesse au niveau des opérateurs permet d’écrire des expressions (combinaisons d’opérateurs et d’opérandes) parfois complexes.

Les opérateurs sont les éléments du langage qui permettent de faire du calcul ou de définir des relations. Ils servent à combiner des variables et des constantes pour réaliser des expressions.

La classification faite ci-après est guidée par le nombre d’opérandes mis en cause par l’opérateur et non par l’utilisation des opérateurs.

5.1 Opérateurs unaires

Un opérateur unaire agit sur un opérande qui peut être une constante, une variable, ou une expression. Ainsi, l’opérateur unaire - permet d’inverser le signe et on peut écrire :

-2 où 2 est une constante;

-i où i est une variable;

-(i+2) où i+2 est une expression.

Le tableau 5.1 donne la liste des opérateurs un-aires.


73