Support de Cours de Langage C débutant
Support de Cours de Langage C débutant
...
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 ma- nipulent 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.
La règle de fonctionnement du compilateur C, édictée ci-dessus, permet de détecter ce qui est fait di- rectement 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.
2ASCII : Americans Standard Code for Information Interchange. ISO 646 :1983, Information processing - ISO 7-bit coded cha- racter set for information interchange.
FIG. 1.1 – Structure d’un programme C
– Il ne permet pas de manipuler directement des tableaux. En particulier, il ne permet pas 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 normali- sation 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 appe- lé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 pro- grammes 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.
FIG. 1.2 – Fichier source
FIG. 1.3 – Structure d’une fonction C
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.
3Nous verrons plus en détail l’ensemble des possibilités associées aux déclarations et aux définitions dans les chapitres 3 et 9.
Un bloc est constitué :
– d’une accolade ouvrante ;
– des définitions des variables locales au bloc ;
– des instructions ;
– d’une accolade fermante.
Une instruction peut être :
– un bloc4 ;
– ou une expression5 suivie d’un point virgule ( ;) ;
– 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 Enchaîneur de passes
L’étude des différentes actions entreprises lors de ce que nous appelons de manière abusive la com- pilation, 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’un source C, la commande usuelle est cc6 :
cc options nom_du_fichier.c
Ces “compilateurs” sont en fait des enchaî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 à la transformation d’un programme C en un fichier exécutable.
L’enchaîneur de passes cc met en œuvre cinq utilitaires :
– le pré-processeur, que nous appellerons cpp ;
– le compilateur C, que nous appellerons c0+c1 car il peut être découpé en deux parties ;
– l’optimiseur de code, appelé c2 ;
– l’assembleur, que nous nommerons as ;
– l’éditeur de liens, dont le nom le plus courant est ld.
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 (voir chap. 15) :
– inclusion de fichiers ;
– définition d’alias et de macro-expressions ;
– sélection de parties de texte.
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 conditionne les contrôles qu’il peut faire, et explique pourquoi toute variable ou fonction doit être déclarée avant d’être utilisée.
4On voit apparaître une définition récursive à l’intérieur du langage : à savoir un bloc contient des instructions qui peuvent être des blocs qui contiennent des instructions, . . .
5Les expressions sont explicitées dans le chapitre 5.
6cc ou tout autre “compilateur” comme gcc.
- c Christian Bac 1985-2004
6 1.4. ENCHAÎNEUR DE PASSES
1.4.3 Optimiseur de code
L’optimiseur de code élimine les parties du code assembleur qui ne sont pas utiles. Il remplace 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 petits problèmes qui se résument par : après l’optimisation, le programme ne marche plus.
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ésolues par l’éditeur de liens. Sur les machines utilisant un système de type UNIX, ce fichier est suffixé par .o 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 la bibliothèque standard (libc.a). Il utilise aussi un module spécial, crt0.o, qui contient le code de démarrage du programme.
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.
1.4.6 Quelques options de cc
Les options de l’enchaineur de passes sont précédées d’un tiret8 (“-”). Voici les options les plus cou- ramment utilisées :
-c provoque la génération d’un module objet non exécutable, il s’arrête avant l’édition de liens.
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 optimise le code généré (utilisation de c2).
-o nom donne le nom au fichier exécutable au lieu de a.out. cc -o toto toto.c → toto
-v option bavarde, cc annonce ce qu’il fait.
Deux options sont utiles sur le compilateur GNU gcc pour forcer la vérification d’une syntaxe correspon- dant à la norme ANSI :
-ansi avec cette option le compilateur se comporte comme un compilateur de langage C ANSI sans exten- sions de langage correspondant au C GNU ;
-pedantic cette option demande au compilateur de refuser la compilation de programme non ansi ;
-Wall cette option augmente le nombre de messages d’alerte générés par le compilateur lorsqu’il rencontre des constructions dangereuses.
L’utilisation de ces options est recommandée lors de l’apprentissage du langage C en utilisant le compila- teur gcc.
7De mauvaises langues prétendent que sur d’autres types de système un mauvais esprit aurait osé les suffixer par .OBJ
8Selon la tradition du système UNIX.
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 fonc- tions ;
– 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.
8 2.2. MOTS RÉSERVÉS
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 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 :
- définir le domaine de valeur de cette variable (taille en mémoire et représentation machine) ;
- définir les opérations possibles sur cette variable ;
- définir le domaine de visibilité de cette variable ;
- permettre à l’environnement d’exécution du programme d’associer le nom de la variable à une adresse mémoire ;
- initialiser la variable avec une valeur compatible avec le domaine de valeur.
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 à la classe mémoire de la variable. L’initialisation est traitée dans la section3.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 (voir chapitre 10).
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) 1. 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 ce type est très proche de l’octet. Il représente un entier sur huit bits. Sa valeur peut évoluer entre - 128 et +127. Il est le support des caractères au sens commun du terme. Ces caractè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 ce type sert pour les calculs avec des parties décimales.
double c’est un type qui permet de représenter des valeurs ayant une partie décimale avec une plus grande précision que le type float. Comme nous le verrons dans l’expression des constantes (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.
1Dans le cas le plus courant une variable du type entier peut contenir une valeur positive ou négative.
long double ce type est récent, il permet de représenter des nombres avec parties décimales qui néces- sitent 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 unsigned 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/IEEE 754.
double ce type de plus grande précision permet de représenter des valeurs avec parties décimales. Lui aussi est souvent basé sur la norme ISO/IEEE 754.
long double ce type est récent et permet de représenter des nombres avec parties décimales sur une très grande précision, si la machine le permet.
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)
– sizeof (float) sizeof (double) sizeof (longdouble)
où sizeof est un opérateur qui donne la taille en nombre d’octets du type dont le nom est entre paren- thè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 personnel2 et trente-deux bits sur les machines du type station de travail.
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 assez rares.
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 Al- pha). 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 double des long double.
2Pour des problèmes de compatibilité avec les anciennes versions, les compilateurs pour PC génèrent le plus souvent du code compatible 386.
Type
Année PDP 11
1970 Intel 486
1989 Sparc
1993 Pentium
1993 Alpha
1994
char 8 bits 8bits 8bits 8bits 8bits
short 16 bits 16 bits 16 bits 16 bits 16 bits
int 16 bits 16 bits 32 bits 32 bits 32 bits
long 32 bits 32 bits 32 bits 32 bits 64 bits
float 32 bits 32 bits 32 bits 32 bits 32 bits
double 64 bits 64 bits 64 bits 64 bits 64 bits
long double 64 bits 64 bits 64 bits 64 bits 128 bits
TAB. 3.1 – Longueur des types de base sur quelques machines
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 est un 0 ou en base seize lorsque les deux premiers caractères sont 0X (hexadécimal).
Une constante est a priori du type int si le nombre qu’elle représente est plus petit que le plus grand en- tier représentable. Si la valeur de la constante est supérieure au plus grand entier représentable, la constante devient du type long.
Les constantes peuvent être suffixées par un “l” ou “L” pour préciser que leur type associé est long int.
Les constantes peuvent être précédées par un signe “-” ou “+”.
Elles peuvent être suffixées par un “u” ou “U” pour préciser qu’elles expriment une valeur sans signe (qualifiées unsigned).
Voici quelques exemples de constantes de type entier :
– constante sans précision de type :
0377 octal
0X0FF hexadécimal
10 décimal
-20 décimal
– constante longue entière :
- 120L, 0364L, 0x1faL
- 120l, 0364l, 0x1fal
– constante entière non signée : 1. 120U, 0364U, 0x1faU
- 120u, 0364u, 0x1fau
– constante longue entière non signée :
- 120UL, 0364UL, 0x1faUL, 120uL, 0364uL, 0x1fauL
- 120Ul, 0364Ul, 0x1faUl, 120ul, 0364ul, 0x1faul
12 3.2. CONSTANTES ASSOCIÉES AUX TYPES DE BASE
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.
Voici quelques constantes avec partie décimale :
121.34 constante exprimée avec la notation utilisant le point décimal, son type implicite est double.
12134e-2 la même constante exprimée en notation exponentielle
+12134E-2 la notation exponentielle accepte le “E” majuscule, et le “+” un-aire.
121.34f constante de valeur identique mais de type float car suffixée par f.
121.34l constante de valeur identique mais de type long double car suffixée par “l”.
3.2.3 Constantes de type caractère
Les constantes du type caractère simple sont toujours entourées d’apostrophes (single quote). Elles peuvent être représentées selon quatre méthodes :
- lorsque le caractère est disponible au clavier sauf pour la barre de fraction inversée et l’apostrophe, le caractère correspondant est simplement entouré d’apostrophes, par exemple ’a’.
- un caractère peut aussi être représenté par sa valeur exprimée dans la table ASCII en utilisant une notation en base huit. Cette valeur est précédée à l’intérieur des apostrophes par une barre de fraction inversée.
’\0’ : octet du nul, il sert à délimiter les fins de chaînes de caractères.
’\012’ : saut de ligne (Line Feed, LF) ;
’\015’ : retour chariot (Carriage Return, CR) ;
’\011’ : tabulation horizontale (Horizontal Tabulation, HT) ;
- de même, un caractère peut être représenté par sa notation en base seize.
’\x0’ : caractère nul ; ’\xA’ : saut de ligne (LF) ; ’\xD’ : retour chariot (CR) ;
’\x9’ : tabulation horizontale (HT) ;
- un certain nombre d’abréviations est aussi disponible :
’\a’ : alert (sonnerie, BEL) ; ’\b’ : backspace (BS) ; ’\f ’ : saut de page (FF) ; ’\n’ : saut de ligne (LF) ; ’\r’ : retour chariot (CR) ;
’\t’ : tabulation horizontale (HT) ;
’\v’ : tabulation verticale (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”.
FIG. 3.1 – Chaîne de caractères constante
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, static et register.
Une définition de variable qualifiée du mot const informe le compilateur que cette variable est consi- dé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 en lecture seule- ment à 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ées-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.
Les qualificatifs static et register sont décrits dans la section 3.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 pour les types entiers ;
float.h pour les types avec partie dé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>.
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 le fichier
<stddef.h>. Le programme 3.3 est un extrait d’un exemple de fichier <stddef.h>.
Programme 3.1 Exemple de fichier limits.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
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 à la définition de variables sont couverts par le langage C :
définition du domaine de valeur de cette variable et les opérations légales sur cette variable ;
=⇒ grâce au type.
réservation de l’espace mémoire nécessaire au support de la variable lors de l’exécution ;
=⇒ grâce au type et à la classe mé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 à la variable qui permet l’utilisation dans certaines parties du programme (règles de visibilité).
=⇒ grâce à la classe mémoire et au lieu de définition.
Programme 3.2 Exemple de fichier float.h
/* Float definitions */
#define FLT_MANT_DIG 24
#define FLT_EPSILON 1.19209290e-07f
#define FLT_DIG 6
#define FLT_MIN_EXP -125
#define FLT_MIN 1.17549435e-38f
#define FLT_MIN_10_EXP -37
#define FLT_MAX_EXP 128
#define FLT_MAX 3.40282347e+38f
#define FLT_MAX_10_EXP 38
/* Double definitions */
#define DBL_MANT_DIG 53
#define DBL_EPSILON 2.2204460492503131e-16
#define DBL_DIG 15
#define DBL_MIN_EXP -1021
#define DBL_MIN 2.2250738585072014e-308
#define DBL_MIN_10_EXP -307
#define DBL_MAX_EXP 1024
#define DBL_MAX 1.79769313486231570e+308
#define DBL_MAX_10_EXP 308
Programme 3.3 Exemple de fichier stddef.h
typedef long ptrdiff_t; typedef unsigned long size_t; typedef int wchar_t;
Une définition de variable est l’association d’un identifiant à un type et la spé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 sur la visibilité. Les classes mémoire sont :
global cette classe est celle des variables définies en dehors d’une fonction. Ces variables sont acces- sibles à toutes les fonctions. La durée de vie des variables de type global est la même que celle du programme en cours d’exécution.
local ou auto : cette classe comprend l’ensemble des variables définies dans un bloc. C’est le cas de toute variable définie à l’intérieur d’une fonction. L’espace mémoire réservé pour ce type de variable est alloué dans la pile d’exécution. C’est pourquoi elles sont appelées aussi auto 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 celle de la fonction dans laquelle elles sont définies.
static ce qualificatif modifie la visibilité de la variable, ou son implantation :
– dans le cas d’une variable locale il modifie son implantation en attribuant une partie de l’espace de mémoire globale pour cette variable. Une variable locale de type statique a un 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).