Obligement - L'Amiga au maximum

Mercredi 24 mai 2017 - 19:38  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Soutien

N'hésitez pas à soutenir le projet Obligement



Contact

David Brunet

Courriel

 


Programmation : C - le préprocesseur
(Article écrit par Batchman et extrait d'Amiga News - novembre 1990)


Ce mois-ci, apprenons à mieux connaître le préprocesseur. Après avoir abordé toutes ces notions, voyons ce qui se greffe autour. Nous commencerons par le préprocesseur, qui est un outil très pratique au service du C.

Qu'est-ce que le préprocesseur ?

Comme son nom l'indique, il s'agit d'un programme qui effectue un premier traitement des fichiers sources avant la compilation. Il est inclus dans la première phase du compilateur, ce qui fait que vous ne le voyez pas, mais il pourrait très bien être séparé de qui est le cas en Unix, où il s'appelle "cpp"). Vous pouvez d'ailleurs n'appeler que lui, par une option de compilation. Par exemple, en Lattice, "lc1 - p toto.c" produira un fichier "toto.p" qui est le résultat de la transformation de vos fichiers source par le préprocesseur. C'est cette chose-là qui est ensuite compilée. Je parle de "chose" car (vous polluez le vérifier sur un programme un peu compliqué), ça peut devenir assez difficile à lire...

Que fait le préprocesseur ?

Il parcourt récursivement le fichier source et les fichiers inclus, en faisant au passage des modifications. La première consiste à éliminer les commentaires, dont le compilateur se moque éperdument. Ces commentaires peuvent être placés absolument n'importe où, dans le programme et répondent à la syntaxe suivante :

/* élucubrations diverses */

Absolument n'importe où, ça veut dire même au milieu d'une ligne, d'un calcul, etc. Cela n'a aucune importance puisque le préprocesseur les retire et que le compilateur ne les connaît donc pas. Bien entendu. ils peuvent s'étendre sur plusieurs lignes. Enfin, il est même possible avec certains préprocesseurs d'imbriquer les commentaires :

/* ... /* ... /* ... */ ... */ ... */

C'est très fort mais je ne vois pas bien l'intérêt (comme disait le patron de Sperry, la société qui fabriquait l'Eniac, le premier ordinateur).

Tout cela, c'est la théorie. Dans la pratique, le préprocesseur Lattice ne supprime pas tous les commentaires (vous pouvez le vérifier en inspectant le fichier ".p") mais le compilateur s'en accommode fort bien.

Les directives

Les commandes qui s'adressent au préprocesseur sont les directives, reconnaissables aux "#" en début de ligne, sur la première colonne. Les instructions qui suivent le dièse sont exécutées par le préprocesseur afin de modifier le texte du programme source qui sera finalement compilé. Mais j'insiste là-dessus, ces directives ne sont pas du C ; le compilateur n'en a pas connaissance puisque le texte qu'il reçoit a déjà été modifié.

L'inclusion de fichiers

Elle consiste à placer des directives "#include" selon deux syntaxes différentes :

#include <nom de fichier>

Ou :

#include "nom de fichier"

Le fichier "nom de fichier" est inséré dans le fichier source, à cet endroit-là. Et ce mécanisme est récursif : si le fichier inséré contient un #include, un autre fichier sera inclus dans le fichier inséré.

La différence entre les deux syntaxes s'applique à la recherche du fichier donné : dans le premier cas, celui-ci est uniquement recherché dans les répertoires include (qui contient tous les fichiers ".h" fournis avec le compilateur) ; alors que, dans le cas contraire, le fichier est d'abord recherché dans le répertoire courant, puis, s'il n'y est pas, clans les répertoires include.

Cette deuxième syntaxe permet d'inclure des fichiers de votre création, sans avoir à les placer dans les répertoires include ; vous pouvez les garder dans votre répertoire de travail pour ce programme, c'est plus pratique.

L'utilité d'inclure des fichiers est évidente en C : cela vous évite d'avoir à recopier de longues déclarations (par exemple si vous utilisez Intuition). Cela vous permet aussi de morceler votre programme en grandes fonctionnalités, avec, dans un fichier, ce qui se rapporte à la gestion de l'écran, dans un autre, du calcul numérique, etc. Tout simplement parce qu'il est plus simple de travailler avec des petits fichiers indépendants qu'avec un monstre monolithique grassouillet (rien à voir avec Frédéric "El Yéti" Autechaud qui, lui, est un monstre paléolithique grassouillet).

Un dernier mot sur la syntaxe des noms de fichiers insérés : elle est libre. Mais on donne généralement par convention à ces fichiers le suffixe ".h" pour "header" : fichiers en-tête, puisqu'on place le #include de préférence en tête d'un fichier source, afin que la suite du programme profite des définitions du fichier inclus.

La définition de macros

Rien à voir avec la pègre ou la pêche. C'est le domaine de la directive "#define", qui a trois utilisations distinctes :

#define TOTO

#define PI 3.141592654

#deline CARRE(x) ((x)*(x))

La première définit le symbole "TOTO". Le préprocesseur sait donc qu'il existe. C'est tout, on verra plus loin l'utilité de cette écriture.

La deuxième écriture permet de définir la constante "PI". Ainsi, le préprocesseur, partout où il trouvera la chaîne "PI", substituera la chaîne "3.141592654". Il s'agit vraiment d'une substitution triviale : une chaîne de caractères par une autre. On parle aussi d'expansion de macro. Par exemple :

x = PI * 2;

...sera remplacé par :

x = 3.141592654 * 2;

La substitution fait la différence entre les majuscules et les minuscules : le préprocesseur considère que pi et PI sont deux choses différentes. C'est pourquoi on décide, par convention, d'écrire les définitions de macros tout en majuscules : en effet, elles sont ainsi beaucoup plus faciles à repérer dans un listing. On sait qu'en majuscules, ce sont des constantes ; en minuscules, ce sont des variables.

L'utilité de ces définitions de constantes est évidente : pour les flemmards, il est plus sympa d'écrire PI que 3.141592654. Pour ceux qui aiment des programmes clairs, il y a la possibilité d'écrire :

#define VRAI 1
#define FAUX 0

if (truc == VRAI) ...

Enfin, pour ceux qui sont génie logiciel, il y a la possibilité suivante :

#define TAILLE_BUFFER 1000
char buffer[TAILLE_BUFFER];
for (i=0; i<TAILLE_BUFFER; i++) ...

C'est un bon exemple d'utilisation d'une constante : vous définissez une fois pour toutes la taille d'un tampon (buffer), et toutes les déclarations de tampons du même type, toutes les opérations qui ont besoin de connaître sa taille, se réfèreront à la macro TAILLE_BUFFER. Si vous n'avez pas compris l'intérêt de la manoeuvre, premièrement, vous avez un blame, et deuxièmement, imaginez les modifications qu'il faudrait faire dans tout le programme pour changer la taille du tampon si on l'avait partout écrite explicitement en chiffres. Si vous n'avez toujours pas compris, la solution du problème est dans Télé Z (2 FF) de cette semaine.

Enfin, troisième possibilité d'utilisation de #define, les macros-fonctions.

#define CARRE(x) ((x)*(x))

Cet exemple représente ce que le préprocesseur sait faire de plus compliqué, voire sophistiqué. Ici aussi, la chaîne CARRE(x) va être remplacée par ((x)*(x)), mais le paramètre n'est pas forcément "x". En d'autres termes, il va lui aussi être substitué par la chaîne passée en paramètre de CARRE. Par exemple, la macro définie ci-dessus pourra remplacer CARRE(toto) par ((toto)*(toto)). Encore plus fort, CARRE(PI) sera substitué par ((3.141592654)*(3.141592654)).

La directive #undef

Elle est relativement peu utilisée car elle sert à annuler une définition de macro. Par exemple, après #undef PI, le préprocesseur ne connaîtra plus la signification de la chaîne PI. C'est utile quand vous désirez changer la signification d'une macro. Évidemment, PI ne peut guère avoir d'autre valeur que 3.141592654, mais, par contre, ROUGE peut correspondre à des codes de couleur, par exemple, différents suivant l'utilisation que l'on en fait. Dans ce cas, on peut redéfinir la macro, mais, fort logiquement, le préprocesseur marquera sa désapprobation, car avec l'éventuelle imbrication des fichiers include, on a vite fait de se tromper et de substituer à la macro la mauvaise valeur. Donc, pour valider ce changement de signification, il faut l'expliciter et faire précéder la nouvelle définition par l'annulation de la précédente.

La compilation conditionnelle

C'est le domaine des directives #if, #ifdef, #ifndef, #else, #endif. Le principe est celui du test classique : une séquence est exécutée suivant le résultat d'un test, avec, éventuellement, une alternative s'il est négatif. La séquence consiste à insérer ou non les lignes de programme comprises dans le bloc.

Ce qui correspond aux constructions suivantes :

#if condition (ou #ifdef ou #ifndef)
lignes de programme
#endif

Ou :

#if condition (ou #ifdef ou #ifrulef)
lignes de programme
#else
lignes de programme
#endif

En clair et sans décodeur, si, dans la première construction la condition est vérifiée, les lignes de programme figureront dans le texte soumis au compilateur. Si la condition est fausse, les lignes seront supprimées.

Dans la deuxième construction, si la condition est vérifiée, le premier bloc figurera. Dans le cas contraire, ce sera bien sûr le deuxième bloc.

Parlons un peu des trois tests :

"#if expression-constante" vérifie que l'expression est non nulle. Très peu d'intérêt, absolument pas employé. On passe.

"#ifdef" et "#ifindef" testent respectivement l'existence et la non-existence d'une macro. Si elle a été déclarée auparavant par un "#define", "#ifdef" répondra vrai. Dans le cas contraire, c'est "#ifndef" qui répondra vrai.

Exemple classique, vous écrivez un petit programme qui peut tourner sur plusieurs machines :

C

En faisant figurer en tête du programme, le #define adéquat, le préprocesseur sélectionnera la ligne à garder. Le programme source transmis au compilateur n'aura aucune connaissance de ce choix.

Autre possibilité, pas de #define en tête du fichier mais définition de la macro dans la ligne de commande du compilateur avec l'option "-D" sur le modèle suivant :

lc1 - DAMIGA

Autre utilisation fréquente de la compilation conditionnelle : le débogage. Vous pouvez inclure à certains endroits névralgiques du programme des lignes de débogage sur le modèle suivant :

#ifdef DEBUG
lignes de débogage, affichage de variables importantes, etc
#endif

Si vous avez des problèmes de mise au point du programme. compilez-le avec l'option "-DDEBUG". La macro DEBUG étant définie, les lignes de débogage seront incluses. Une fois le programme sans faille, recompilez-le sans l'option "-DDEBUG". La macro DEBUG n'étant plus définie, les lignes de débogage seront exclues du texte et le programme exécutable sera allégé (une sorte d'exécutable allégé, à teneur non garantie de 0% en bogues...).

Encore plus rusé (mais où s'arrêtera-t-il ?), le débogage à plusieurs niveaux du style :

#ifdef NIVEAU_DEBUG_1
lignes de débogage superficiel
#else
# ifdef NIVEAU_DEBUG_2
lignes de débogage plus subtil
#else
#ifdef NIVEAU_DEBUG 3
lignes de maxi débogage, la totale
#endif
#endif
#endif

J'ai même en ce moment, sous les yeux, une lettre d'un sympathique lecteur Belge qui me signale que ça marche aussi avec quatre, voire cinq niveaux...

La directive #line

Peu connue puisqu'on la trouve rarement, elle s'écrit sous la forme suivante :

#line numéro "nom de fichier"

Après cette directive, tout message affiché par le compilateur (warning, erreur) sera attribué au fichier nom de fichier, avec comme numéro de ligne, un numéro comptabilisé à partir de numéro au lieu du vrai numéro de ligne et du vrai nom du fichier.

Non, ça n'est pas complètement stupide. En fait, cette directive est faite pour les programmes générés automatiquement par un autre programme. Si vous utilisez un programme qui génère des sources, comme Power Window, par exemple (ou Lex ou Yacc en Unix), cette directive permettra de vous renseigner sur l'emplacement exact de l'erreur s'il y en a une dans le fichier généré. Mais comme on lit rarement des programmes écrits automatiquement, on n'a pas l'habitude de la rencontrer. C'est pour cela qu'elle est si peu connue.

C'est tout pour le préprocesseur. Vous voyez qu'il n'y a pas grand-chose mais c'est très puissant.


[Retour en haut] / [Retour aux articles]