Préprocesseur CLe préprocesseur C ou cpp assure une phase préliminaire de la traduction (compilation) des programmes informatiques écrits dans les langages de programmation C et C++. Comme préprocesseur, il permet principalement l'inclusion d'un segment de code source disponible dans un autre fichier (fichiers d'en-tête ou header), la substitution de chaînes de caractères (macro définition), ainsi que la compilation conditionnelle. Dans de nombreux cas, il s'agit d'un programme distinct du compilateur lui-même et appelé par celui-ci au début de la traduction. Le langage utilisé pour les directives du préprocesseur est indépendant de la syntaxe du langage C, de sorte que le préprocesseur C peut être utilisé isolément pour traiter d'autres types de fichiers sources. PhasesLe préprocesseur s'occupe des quatre premières (sur huit) phases de traduction pour la norme C :
Types de directivesInclusionL'usage le plus fréquent du préprocesseur C est la directive: #include <…>
dont le rôle est de recopier le contenu d'un fichier dans le fichier courant. On l'emploie généralement pour inclure les en-têtes de bibliothèques, telles que les fonctions mathématiques (#include <math.h>) ou les fonctions d'entrée/sortie standard (#include <stdio.h>). Dans l'exemple ci-dessous, la directive « #include <stdio.h>
int main(void)
{
printf("Bonjour le monde!\n");
return 0;
}
Cette directive peut aussi être écrite en utilisant les guillemets doubles ( Par convention, les fichiers à inclure ont une extension ".h" (ou .hpp en C++), et les fichiers ne devant être inclus ont une extension ".c" (ou ".cpp" en C++). Toutefois, il n'est pas nécessaire que cela soit observé. Occasionnellement, des fichiers avec d'autres extensions sont inclus: les fichiers avec une extension de .def peuvent désigner des fichiers destinés à être inclus plusieurs fois, à chaque fois élargissant le contenu à inclure, aussi #include "icon.xbm" fait référence à un fichier image XBM (qui est en même temps un fichier C). La directive #include souvent est accompagnée de directives dites de #include guards ou de la directive #pragma pour éviter la double inclusion. La compilation conditionnelleLes directives #if VERBOSE >= 2
printf("Hello world!");
#endif
La plupart des compilateurs pour Microsoft Windows définissent implicitement #ifdef __unix__ // __unix__ est souvent défini par les compilateurs pour des systèmes Unix
# include <unistd.h>
#elif defined _WIN32 // _Win32 est souvent défini par les compilateurs pour des systèmes Windows 32 ou 64 bit
# include <windows.h>
#endif
Cet exemple de code teste si la macro Il est possible d'utiliser des opérateurs avec la directive #if !(defined __LP64__ || defined __LLP64__) || defined _WIN32 && !defined _WIN64
// compilation pour un système 32-bit
#else
// compilation pour un système 64-bit
#endif
La majorité des langages de programmation modernes n'utilisent pas ces fonctionnalités et dépendent plutôt d'une utilisation des habituels opérateurs if…then…else…, laissant au compilateur la tâche de supprimer le code inutile. Définition de macro et expansionLe mécanisme des macros est fréquemment utilisé en C pour définir de petits extraits de code qui seront réutilisés à divers endroits du programme. Durant l'exécution du préprocesseur, chaque appel de la macro est remplacé, dans le corps du fichier, par la définition de cette macro. La macro aboutirait donc à une exécution plus rapide qu'un appel à une routine, mais il faut souvent y regarder à deux fois, comme on va le voir. Les syntaxes générales pour déclarer une macro de remplacement de type objet et fonction, respectivement, sont : #define <identifiant> <contenu de remplacement> // type objet
#define <identifiant>(<liste des paramètres>) <contenu de remplacement> // type fonction avec paramètres
Les macros de type fonction ne doivent pas contenir d'espace entre l'identifiant et la parenthèse ouvrante. Un exemple de macro de type objet est : #define PI 3.14159
qui définit une macro PI (identifiant) qui prendra la valeur 3.14159 (contenu de remplacement). Cette macro peut être appelée comme n'importe quelle fonction C. Ainsi, après passage du préprocesseur, double z =PI; sera remplacé par double z = 3.14159; Un exemple de macro de type fonction : #define MAX(a,b) a>b?a:b
définit la macro MAX (identifiant), prenant 2 paramètres (a et b) et calculant a>b?a:b (contenu de remplacement). Cette macro peut être appelée comme n'importe quelle fonction C. Ainsi, après passage du préprocesseur, z = MAX(x, y); sera remplacé par z = x>y?x:y; Cet usage des macros est fondamental en C, notamment pour définir des structures de données sûres ou en tant qu'outil de débogage ; cependant il peut ralentir la compilation et parfois l'exécution, et présente de nombreux pièges. Ainsi, si f et g sont deux fonctions, z = MAX(f(), g()); sera remplacée par z = (f() > g())?f():g(); : cette commande nécessite deux fois l'évaluation d'une des deux fonctions, ce qui ralentit le code. À noter aussi, qu'il est possible d'annuler la définition d'une macro avec la directive "#undef" : #undef <identifiant>
Concatenation de tokensL’opérateur ## élimine tous les espaces (ou whitespace) autour de lui et concatène (assemble) deux tokens (ou jetons) en un. Cela sert à créer de nouveaux tokens et ne peut être utilisé que dans des macros, comme : #define ma_macro(x) x ## _suffixe
remplacera ma_macro(identifiant);
par identifiant_suffixe;
Erreurs de compilation définies par l'utilisateurLa directive #error ajoute un message au flux des messages d'erreur. #error "message d'erreur"
Il est ainsi possible d'utiliser ces directives, par exemple pour créer une erreur de traduction : #if RUBY_VERSION == 190
#error 1.9.0 not supported
#endif
ImplémentationsCaractéristiques des préprocesseurs specifiques à un compilateurLa directive #pragmaLa directive #pragma est une directive spécifique à un compilateur. Cette directive est souvent utilisée pour permettre la suppression de certains messages d'erreur ou pour gérer la pile du processeur ou le tas du ramasse-miettes. La version 1999 de la norme C (C99) introduit une série de directives #pragma, telles que #pragma STDC pour contrôler la précision numérique ou fma() pour l'opération combinée Multiplieur-accumulateur. Messages d'alerteDe nombreuses implémentations (dont les compilateurs C pour GNU, intel, Microsoft et IBM) fournissent une directive non standard pour afficher des messages d'alerte dans la sortie sans toutefois arrêter la compilation. Un usage fréquent de ce type de directives est de prévenir qu'une partie du code est « dépréciée » (erreur de traduction très répandue pour deprecated qui signifie 'obsolète') :
#warning "Ne pas utiliser SOPA ou PIPA, qui sont dépréciées. Utilisez CC à la place."
#pragma message("Ne pas utiliser SOPA ou PIPA, qui sont dépréciées. Utilisez CC a la place.")
Autres directives spécifiques
Bibliographie
GlossaireLe terme directive renvoie aux lignes du code source devant être traitées par le préprocesseur (p. ex. #define ou #include). Par extension, il sert à désigner l'instruction ou macro-définition destinée au préprocesseur. OutilsL'outil GNU Cppi vise à la bonne indentation des directives du préprocesseur pour refléter leur imbrication, et veille à ce qu'il y ait exactement un espace entre chaque directive #if, #elif, #define et le jeton suivant, pour in fine écrire le résultat en sortie. Le nombre d'espaces entre le caractère `#' et la directive suivante doit correspondre au niveau d'imbrication de cette directive (voir aussi le livre cppi sur Wikibooks). C'est un paquet GNU maintenu par Jim Meyering et distribué selon les termes de la licence publique générale GNU[1]. Notes et références
|