
FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 28, nombre de questions : 175, création le 11 janvier 2013
Sommaire→Compilation et édition des liens→Le préprocesseur- Qu'est-ce que le préprocesseur ?
- Que signifie #define N 10 ?
- Comment définir et utiliser une macro paramétrée ?
- Que signifie #define MYMACRO ?
- Comment savoir si une macro est définie ?
- Quels problèmes peuvent poser l'utilisation des macros ?
- Quel est le rôle de # dans la définition d'une macro ?
- Quel est le rôle de ## dans la définition d'une macro ?
- Que signifie #pragma ... ?
- Que signifie #error ... ?
- Peut-on utiliser sizeof dans un #if ?
- Pourquoi je n'arrive pas afficher la date courante avec __DATE__ ?
Avant d'être effectivement compilés, les fichiers sources sont tout d'abord traités par un programme appelé préprocesseur qui se chargera d'exécuter toutes les commandes commençant par # (#include, #define, etc.) jusqu'à ce qu'il n'y en ait plus une seule. C'est à ce moment et à ce moment seulement, c'est-à-dire après passage du préprocesseur si besoin était, que la compilation proprement dite (c'est-à-dire traduction du fichier source en fichier objet) peut enfin être lancée.
#define N 10 définit une macro N que le préprocesseur devra remplacée par 10 après son passage. Cela signifie que :
#define N 10
int f(void)
{
return N + 1;
}
Donnera après passage du préprocesseur :
int f(void)
{
return 10 + 1;
}
Les macros peuvent accepter des paramètres, un peu comme les fonctions. Le nom de la macro doit être immédiatement suivi (sans même un espace) de la parenthèse ouvrante, des paramètres puis de la parenthèse fermante. Vient ensuite le corps de la macro qui peut s'étaler sur plusieurs lignes, chacune sauf la dernière se terminant par un anti-slash : \. Par exemple :
#define PRINT(x) printf("MESSAGE : %s\n", x)
int main(void)
{
PRINT("Hello, world !");
return 0;
}
Donnera après passage du préprocesseur :
int main(void)
{
printf("MESSAGE : %s\n", "Hello, world !");
return 0;
}
#define MYMACRO définit une macro MYMACRO qui sera remplacée, après passage du préprocesseur, par ... rien ! Par exemple :
#define IN
#define OUT
int f(IN int n, OUT int * p1, OUT int * p2)
{
*p1 = n - 1;
*p2 = n + 1;
return 2 * n;
}
Donnera après passage du préprocesseur :
int f( int n, int * p1, int * p2)
{
*p1 = n - 1;
*p2 = n + 1;
return 2 * n;
}
Le test if defined(NOM_MACRO) ou ifdef NOM_MACRO permet de savoir si une macro appelée NOM_MACRO est définie. Voici quelques exemples d'utilisation.
#include <stdio.h>
#if defined(UPPERCASE)
# define MESSAGE "HELLO, WORLD !"
#else
# define MESSAGE "Hello, world !"
#endif
int main(void)
{
printf("%s\n", MESSAGE);
return 0;
}
Dans cet exemple, puisque la macro UPPERCASE n'a pas été définie, tout ce qui est situé entre le test et le else sera ignoré (supprimé !) par le préprocesseur. Plus précisément, du bloc if ... else ... endif, il ne restera plus que ce qui se trouvait entre le else et le endif. Ici, MESSAGE sera donc remplacé par le préprocesseur par "Hello, world !". Si UPPERCASE avait cependant été définie, MESSAGE aurait alors été remplacé par "HELLO, WORLD !".
Il y en a plusieurs ! Voici quelques points auxquels il faut faire très bien attention lorsqu'on fait usage des macros.
Parenthèses :
Soit la macro CARRE dont le rôle est de fournir le carré de la valeur passée en paramètre. Si elle est définie de la sorte : '#define CARRE(x) x * x',
Son utilisation pour un paramètre tel que 9 + 1 sera erronée. En effet 'CARRE(9 + 1)' sera remplacé par '9 + 1 * 9 + 1' qui en C est équivalent à
'9 + (1 * 9) + 1', la multiplication étant prioritaire par rapport à l'addition, ce qui n'est bien entendu pas le carré de 9 + 1.
Il convient donc de toujours parenthéser à l'extrême les macros, la nôtre devant s'écrire alors : '#define CARRE(x) ((x) * (x))'.
Effets de bord :
Soit la macro MAX fournissant le maximum de deux nombres : '#define MAX(x, y) ((x) > (y) ? (x) : (y))'. Appelée de la manière suivante : 'k = MAX(3, 2)', k
aura évidemment la valeur 3. Soient maintenant i et j, deux variables de type int vallant toutes 2. On pense alors qu'avec 'k = MAX(++i, j)', k aura encore la valeur 3
comme dans l'exemple précédent alors que cette fois-ci elle aura la valeur 4 ! En effet, 'k = MAX(++i, j)' est remplacé par le préprocesseur par 'k = ((++i) > (j) ? (++i) : (j))'.
Comme i a été incrémenté deux fois, on aura k = 4 et non 3 comme il était attendu.
Nom des paramètres :
Une erreur de ce genre peut également arriver : avec '#define ERR_PRINT_INT(n) fprintf(stderr, "%d\n", n)', ERR_PRINT_INT(10) sera remplacé par fprintf(stderr, "%d\10", 10) !
Dans la définition d'une macro, l'opérateur # permet de transformer un argument en chaîne de caractères, quel que soit l'argument. Par exemple :
#include <stdio.h>
#define TOSTR(x) #x
#define NNNNN 99999
int main(void)
{
printf("%s\n", TOSTR(10000)); /* --> "10000" */
printf("%s\n", TOSTR(1 + 1)); /* --> "1 + 1" */
printf("%s\n", TOSTR(n + 1)); /* --> "n + 1" */
printf("%s\n", TOSTR(float)); /* --> "float" */
printf("%s\n", TOSTR(NNNNN)); /* --> "NNNNN" */
return 0;
}
Comme on peut le constater, TOSTR(NNNNN) est traité par le préprocesseur comme #NNNNN et, sachant qu'il est en train de développer une macro, va remplacer cette séquence en "NNNNN" et non en "99999" ! Pour avoir "99999" au lieu de "NNNNN", on pourra utiliser l'astuce suivante :
#define TOSTR(x) __STR(x)
#define __STR(x) #x
Dans ce cas, au passage du préprocesseur, TOSTR(NNNNN) sera dans un premier temps remplacé par __STR(99999) qui sera à son tour (dans une deuxième passe), remplacé par "99999".
Dans la définition d'une macro, l'opérateur ## permet de concaténer deux arguments. Ainsi avec : '#define CAT(x, y) x##y', CAT(C, 90) sera remplacé par le préprocesseur par C90. Voici un autre exemple avec programme complet :
#include <stdio.h>
#include <wchar.h>
#define WIDESTR(x) L##x
#define AU_REVOIR "Au revoir"
int main(void)
{
wprintf(WIDESTR("%s\n"), WIDESTR("Bonjour")); /* --> wprintf(L"%s\n", L"Bonjour"); */
return 0;
}
Par contre, WIDESTR(AU_REVOIRE) sera traité par le préprocesseur comme L##AU_REVOIR et, sachant qu'il est en train de développer une macro, va remplacer cette séquence en LAU_REVOIR et non en L"Au revoir" ! Pour avoir L"Au revoir" au lieu de LAU_REVOIR, on pourra utiliser l'astuce suivante :
#define WIDESTR(x) ___WSTR(x)
#define ___WSTR(x) L##x
Dans ce cas, au passage du préprocesseur, WIDESTR(AU_REVOIR) sera dans un premier temps remplacé par ___WSTR("Au revoir") qui sera à son tour (dans une deuxième passe), remplacé par L"Au revoir".
La directive pragma permet d'envoyer une commande (par exemple une option de compilation, d'édition des liens, etc.) au compilateur. Ces commandes sont toutefois spécifiques au compilateur. La norme requiert que lorsque le compilateur ne reconnaît pas une commande, il doit tout simplement l'ignorer (les compilateurs émettent généralement un warning pour informer l'utilisateur de ce fait).
La directive error permet d'interrompre la compilation, comme si une erreur s'était produite. Par exemple, pour faire une source qui ne puisse compiler que si la taille d'un char sur la cible est de 8 bits, on peut faire :
#include <limits.h>
...
#if (CHAR_BIT != 8)
# error Ce programme requiert que la taille d'un char soit de 8 bits.
#endif
...
Non car sizeof (...) est évaluée pendant la compilation, pas avant. L'astuce suivante permet de générer une véritable erreur de compilation lorsqu'un test échoue (ici, on va générer une erreur lorsque sizeof(int) est différent de sizeof(long)).
/* Si sizeof(int) != sizeof(long), le tableau assert_int_long aura une taille negative, */
/* ce qui ne sera pas apprecie par le compilateur ... */
static int assert_int_long[sizeof(int) == sizeof(long) ? 1 : -1];
__DATE__ est une macro, elle est remplacée par le préprocesseur par la date de compilation du fichier. Elle est donc traitée par le préprocesseur, pas évaluée à l'exécution.



