la programmation modulaire(3/3)




        Nous finissons ce cours-ci la programmation modulaire avec le pré processeur qui va nous permettre de contrôler la compilation et de changer l'aspect de nos sources.




certains programmeurs ont une certaine conception du diviser pour mieux régner



Nous avons depuis le début de cette initiation à la programmation souvent été confronté à la directive "#include", et donc par extention au pré processeur. Celui-ci est en fait un programme dans votre programme qui est exécuté avant toute compilation.
Une directive du pré processeur tient sur une ligne qui peut être introduite à n'importe quel endroit du programme commençant obligatoirement par un '#' suivit d'un mot clé:

#include


Le pré processeur n'a pas pour unique possibilité d'incorporer des fichiers sources comme le montre l'exemple ci-dessus, mais aussi de définir des macros et des symboles, ainsi que de permettre une compilation conditionnelle.




La directive #include


        En fait le programme du mois dernier est bien mal écrit.
Un module objet doit toujours s'accompagner, d'un Header (fichier .h) qui contient les déclaration de fonctions qu'il définis, ainsi que des définitions de macro et des définitions de symboles.
Bien sur nous éviterons d'y inscrire les fonctions déclarées "static".
Pour operations.o (toujours sur le cd), il nous faut créer operations.h.
Voici le contenu de ce fichier tel qu'il doit être fait ;

#ifndef OPERATION_H
#define OPERATION_H

//inclusion des hearder file
#include


//declaration des fonctions
long addition(int operande1,int operande2);
long multiplication(int operande1,int operande2);
long division(int operande1,int operande2);
long soustraction(int operande1,int operande2);
#endif


Il suffira de mettre alors dans le fichier operations.c avant la compilation la ligne :

#include "operation.h"


puis la définition des différentes fonctions.
Quelle est la différence entre : #include et #include "nom" ?
#include
recherche le header file dans un emplacement defini par l'implémentation (dans c:\djgpp\include\ pour les utilisateurs de DJGPP)
#include "nomdefichier.h"
recherche le fichier a partir du répertoire ou se trouve le fichier contenant cette instruction.




la directive #define pour les symboles


        La directive :

#define CAPACITE_RESERVOIR 40


demande au compilateur de remplacer le symbole "CAPACITE_RESERVOIR" par le nombre 5 a chaque fois qu'il verra ce symbole dans le fichier source. Cela est tres utile dans la mesure où :
le code deviens plus clair :

if (contenuReservoir < CAPACITE_RESERVOIR)
//...


est beaucoup plus clair que :

if (contenuReservoir < 40) //...


et la programmation plus facile :
si la capacité du réservoir doit être changée, il suffit de modifier la directive #define pour que les changements s'effectuent dans tout le fichier source. Sans le symbole "CAPACITE_RESERVOIR", il faudrait parcourir tout le code à la recherche du nombre '40'...
Nous pouvons aussi faire des substitutions de la sorte :

#define entier int


nous permettant de faire :

entier a = 5 ;


mais aussi de transformer son code de la sorte :


#include


#define BEGIN {
#define END }
#define BONJOUR printf(" bonjour ! ! ! ")
#define PAS_DE_PARAMETRES (void)
#define NE_RENVOIS_RIEN void

NE_RENVOIS_RIEN main PAS_DE_PARAMETRES
BEGIN
     BONJOUR ;
END


Il est ainsi possible de réécrire totalement de langage C.
'#define' prends toute son importance dans les grands projets.
Imaginons un grand projet contenant un fichier source
Quelques remarques avant d'aborder les macros :
Il ne faut jamais introduire de signe '=' dans les defines du genre #define PI = 3.14, car pour le compilateur 'PI' devra être remplacé par '= 3.14'.
Ne jamais terminer une directive du compilateur apr un point virgule, ce la peut prêter a confusion (sauf bien sûr si l'on veurt changer totalement la syntaxe du C)
Enfin bien que le dernier exmple ne le montre pas, nous préferons un "define" dans un fichier d'entête (header file) plutôt quand dans un '.c'.




la directive #define pour les macros


        La définition de macro est quasiment identique à celle des symboles à ceci près que l'on doit comme pour les fonctions faire intervenir les paramètres :

#define add(a,b) a+b


ainsi à n'importe quel endroit du source, le compilateur remplacera l'expression :

add(operateur1,operateur2) ;


par :

operateur1+operateur2 ;


Ce qui remplace avantageusement notre fonction addition !
Il est bien sur possible d'imbriquer plusieurs macros ensemble :

#define mul (a,b) a*b
#define add(a,b) a+b

//...
mul(add(x,y),add(y,y)) ;


Nous pourrons faire aussi :

#define afficher_entier(a) printf("%d\n", a) ;


ce qui avec :

afficher_entier(variable+9) ;


sera pour le compilateur équivalent à :

printf("%d\n", variable+9) ;


Quelques remarques :
L'utilisations de macro peuvent aboutir à des erreurs incompréhensibles du genre :

#define carre(a) a*a

//...
carre(a++) ;


où est le problème ? et bien pour le compilateur, l'instruction "carre(a++)" est équivalente à "(a++)*(a++)" ce qui bien sûr n'est pas ce qu'on voulait.
De même il faut éviter de mettre un espace entre le nom d'une macro et la signature de cette macro :

#define add (a+b) a+b


cela peut aboutir à des aberrations.
Enfin :

#undef symbole


demande au compilateur "d'oublier " une definition. Cette directive marche sans erreur même si le symbole n'avait pas été defini.




la compilation conditionnelle


        La compilation conditionnelle permet d'exclure ou de rajouter des portions de codes au moment de la compilation.
Cette condition peut être tout d'abord l'existence d'un symbole :
Nous avions vu que nous pouvions définir des symboles de la sorte :

#define symbole valeur


mais il est aussi possible de faire :

#define symbole valeur




#define OPERATION


Cette directive donne juste naissance à un symbole : "OPERATION". Ce symbole existe pour le compilateur mais ne représente rien.
Il est alors possible de faire des conditions de cette manière :

//si "symbole" a été defini
#ifdef symbole
    //portion de code a compiler
//sinon si "symbole" est inconnu
#else
    //portion de code a compiler
//fin de la condition
#endif


Il est important de ne pas confondre, les symboles definis et les variables dans ce genre de conditions ; lors de la compilation du pré processeur, le compilateur n'as pas encore regardé notre source.
Nous aurions pu aussi faire :

//si "symbole" n'a pas été defini
#ifndef symbole
    //portion de code a compiler
//sinon si "symbole" est defini
#else
    //portion de code a compiler
//fin de la condition
#endif


Cette condition peut être enfin la valeur d'un define :

#define valeur 1
//...
//si la valeur vaut 1
#if valeur == 1
    //code...
//sinon si 2
#elif valeur == 2
    //code...
//sinon
#else
    //code...
#endif


De même un opérateur "defined" permet de savoir si un symbole existe :

#if defined(symbole)






compilation avec le pré processeur




Voila c'est terminé. Nous ne saurions trop vous conseiller d'utiliser l'utilitaire make pour améliorer vos connaissances




Précédent  |  Index  |  Suivant  ]


par Valentin BILLOTTE
Dernière mise à jour: 07/02/2001 - 00:27:05