Préprocesseur C
# Préprocesseur C

Le 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.

## Phases

Le préprocesseur s'occupe des quatre premières (sur huit) phases de traduction pour la norme C :

* Remplacement de tri-graphes : le préprocesseur remplace les séquences de tri-graphes par les caractères qu'ils représentent ;
* Raccordement de ligne : les lignes physiquement séparées mais qui sont connectées par des séquences de saut de ligne sont reliées pour former des lignes logiques ;
* Tokenisation : le préprocesseur découpe le code résultant en jetons (ou token) de pré-traitement et d'espaces insécables. Il retire les commentaires ;
* Expansion de macros et manipulation directive : les lignes contenant des directives de pré-traitement, dont l'inclusion de fichiers ou la compilation conditionnelle, sont exécutées. Le préprocesseur remplace chaque appel à une macro par sa définition. Dans la version 1999 de la norme C, il traite aussi les opérateurs _Pragma.

## Types de directives

### Inclusion

L'usage le plus fréquent du préprocesseur C est la directive:

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> » est remplacée par le texte contenu dans le fichier stdio.h.

Cette directive peut aussi être écrite en utilisant les guillemets doubles (" ") comme #include "stdio.h". Si le nom du fichier est enfermé à l'intérieur des chevrons ( < > ), le fichier est recherché dans les chemins connus d'un compilateur standard (aussi appelé include paths). Si le nom du fichier est encadré par des guillemets doubles, le répertoire courant est ajouté avant l'ensemble des chemins de recherche de fichiers; la recherche débute donc par le répertoire courant avant de se poursuivre dans ceux de chemins de recherche. Les compilateurs C et les environnements de développement permettent généralement au programmeur d'inclure les répertoires où les fichiers peuvent être trouvés.

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 conditionnelle

Les directives #if, #ifdef, #ifndef, #else, #elif and #endif peuvent être utilisées pour la compilation conditionnelle.

La plupart des compilateurs pour Microsoft Windows définissent implicitement _WIN32. Cela permet au code, y compris les commandes de préprocesseur, de compiler uniquement lorsque l'on cible les systèmes d'exploitation Windows. Quelques compilateurs définissent WIN32 à la place. Pour les compilateurs qui ne définissent pas implicitement la macro _WIN32, il est possible de le spécifier sur la ligne de commande du compilateur, en utilisant -D_WIN32.

Cet exemple de code teste si la macro __unix__ est définie. Si c'est le cas, le fichier <unistd.h> est inclus. Sinon, il teste si la macro _WIN32 est définie, auquel cas le fichier <windows.h> est inclus.

Il est possible d'utiliser des opérateurs avec la directive #if, comme :

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 expansion

Le 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 :

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 :

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 :

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" :

### Concatenation de tokens

L’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 :

remplacera

par

### Erreurs de compilation définies par l'utilisateur

La directive #error ajoute un message au flux des messages d'erreur.

Il est ainsi possible d'utiliser ces directives, par exemple pour créer une erreur de traduction :

## Implémentations

### Caractéristiques des préprocesseurs specifiques à un compilateur

**La directive #pragma**

La 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'alerte**

De 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') :

* Pour GNU, Intel et IBM :
* Microsoft :

**Autres directives spécifiques**

* Le préprocesseur de GCC autorise la directive #include_next. Cette directive indique au préprocesseur de continuer à chercher le nom de fichier spécifié et de l'inclure après le répertoire courant. La syntaxe est similaire à #include.
* Les préprocesseurs de Objective-C comprennent la directive #import, qui est similaire à #include mais n'inclut le fichier qu'une seule fois.
