Next: 10 Programmation avancée
Up: Le langage C
Previous: 8 Entrées-sorties
Comme nous l'avons déjà dit, la transformation d'un texte source en un programme exécutable se fait en plusieurs étapes (voir figure 9.1).
La phase de transformation d'un fichier source en fichier objet se décompose en deux phases (figure 9.2):
Les directives du préprocesseur ne font par partie du langage C à proprement parlé. Celles-ci se distinguent des instructions du langage C par le fait qu'elles commencent toujours par le caractère #.
#include <stdio.h>
demande au préprocesseur d'inclure le fichier stdio.h
dans le fichier source avant de le compiler. Ce fichier stdio.h
contient toutes les déclarations nécessaires pour
l'utilisation des fonctions entrée-sorties.
Comme vous l'avez sûrement déjà noté, les fichiers include
ont traditionellement l'extension .h: ce sont les fichiers d'interface ou header files. Le fichier stdio.h
et bien d'autres encore sont livrés avec le compilateur C. Vous trouverez
généralement tous ces fichiers dans le répertoire /usr/include.
Ces fichiers sont éditables et il est très vivement déconseillé de les modifier.
En dehors des fichiers fournis avec le compilateur, un programmeur peut créer ses propres fichiers d'interface pour les programmes qu'il réalise. Supposons que l'on veuille réaliser un programme qui utilise des piles de caractères. On décompose ce programme en deux modules (ou plusieurs), l'un qui implante les piles de caractères et l'autre qui les uitlise. Le programmeur qui réalise le module d'implantation des piles founit au(x) programmeur(s) qui réalisent le reste du programme deux fichiers: le premier contenant l'implantation effective des piles et la deuxième une interface pour ce module.
Les utilisateurs de ce module ignorent parfaitement les détails d'implantation de cette pile. En particulier, s'agit-il d'un tableau ou d'une liste chainée ? Seul l'implanteur de ce module le sait. L'utilisation de ce module par d'autres se fait alors de la manière suivante:
/* Interface du module PILE de caracteres (pile.h) */typedef char * pile; pile creer_pile(int); void empiler(char, pile); char depiler(pile); void detruire_pile(pile); /* Fin de l'interface */
Le préprocesseur, selon que la directive est de la forme
/* Module XXX utilisant une pile de caractères */#include "pile.h"
void une_fonction(void) { short c1, c2; pile P; P = creer_pile(100); empiler('a', P); empiler('b', P); ... c1 = depiler(P); if (c1 == EOF) erreur("la pile est vide"); c2 = depiler(P); if (c2 == EOF) erreur("la pile est vide"); ... } /* Fin du module XXX */
#include <fichier>
ou
de la forme #include "fichier" ira chercher ce fichier dans le répertoire
/usr/include ou dans le répertoire spécifié par l'utilisateur.
Les constantes symboliques se définissent à l'aide de la directive
Par exemple, lorsqu'on définit un tableau de 20 éléments par exemple, on pourrait avoir le code suivant:
On notera que la valeur 20 se trouve éparpillé dans tout le code du programme. Un manière élégante de regrouper cette constante en un seul endroit consiste à définir une constante symbolique
int tab[20]; main() { ... for (i=0; i<20; i++) printf("tab[%i]=%d, tab[i]); ... }
MAX_TAB
et utiliser
celle-ci dans tout le programme. Ainsi, si l'on veut modifier la taille du tableau, on se contentera de modifier la seule définition de la constante symbolique
#define MAX_TAB 20 int tab[MAX_TAB];main() { ... for (i=0; i < MAX_TAB; i++) printf("tab[%i]=%d, tab[i]); ... }
MAX_TAB
et le reste programme reste inchangé.
On peut également définir des constantes symboliques sans valeur comme :
Le texte à remplacer étant vide, le préprocesseur se contentera de remplacer toutes les occurences de
#define UNIX
UNIX
par la chaîne vide.
Quoiqu'étant remplacé par ``rien'', cette constante sybolique est considéré
définie par le préprocesseur. Ce type de constantes sont utilisées avec les directives
conditionnelles, comme nous le verrons plus loin.
Il est également possible de supprimer la définition d'un nom symbolique par la directive
#undef UNIX
Comme les constantes symboliques, les macros se définissent à l'aide de la directive
#define BONJOUR printf("Bonjour, tout le monde !!!");
Puisque la directive define ne sert qu'à substituer un nom par le texte défini, rien ne s'oppose à donner un nom symbolique à une suite d'instructions ou tout partie de programme valide.
Comme vous l'avez remarqué, tout le texte à remplacer se trouve sur une même ligne. Et c'est là une restriction (désagréable, j'en conviens) de la directive define. Cette restriction étant inappliquable, on contourne celle-ci avec les fameux caractère
#define ERREUR_MALLOC { printf("La fonction malloc a produit une code d'erreur"); exit(0); }
\
comme nous l'avions fait pour les chaînes de caractères (voir 1.5.4).
#define ERREUR_MALLOC { \ printf("La fonction malloc a produit une code d'erreur"); \ exit(0); \ }
Attention, il ne s'agit pas de fonction, ce ne sont que des macros.
#define ERREUR(x) { \ printf("La fonction x a produit une code d'erreur"); \ exit(0); \ }
#define coller(x, y) x##ymain() { int coller(toto, 5); équivalent à la déclaration int toto5; }
La compilation conditionelle va permettre de réaliser un seul code contenant l'ensemble des codes nécessaire à toutes les machines et en spécifiant au compilateur, selon la machine choisie, quelle partie du code il faudra utiliser.
Pour ce faire, on dispose des directives
Voici des bouts de programmes trouvés dans le programme xcoral:
#if #elif #else #endif #ifdef #indef
Contrairement à la directive
... #ifdef SYSV #includeextern void bzero(); #else #include #endif ... #ifdef USE_NANOSLEEP struct timespec rqtp; ... #elif USE_POLL struct pollfd unused; ... #else /* default to select */ struct timeval delay; ... #endif /* USE_NANOSLEEP */
#ifdef
qui est toujours associée à une
seule constante symbolique, la directive #if
peut être combinée
avec une expression à condition qu'elle soit constante entière.
Voici quelques exemples trouvés dans les sources de Linux.
#if EVERY_ACCESS || ERRORS_ONLY || DEBUG_ABORT ... #endif#if ((~0UL) == 0xffffffff) ... #else ... #endif
#if (((DEBUG & PHASE_ETC) == PHASE_ETC) || (DEBUG & PRINT_COMMAND) ) ... #enif
La directive #if
peut être combinée
avec l'opérateur defined.
Le code
est équivalent au code
#if defined(CONSTANTE) ... #endif
L'intérêt de cet opérateur que l'utilisation de la directive
#ifdef (CONSTANTE) ... #endif
#if
(au lieu de #ifdef
) permet d'utiliser
des expressions complexes.
#if defined(MODULE) && !defined(GFP_DMA) ... #endif
#line n "fichier"
Touraivane