Langage C avancé
Utilisation du préprocesseur cpp
Samuel KOKH
MACS 1 – Institut Galilée
Le préprocesseur cpp
Qu’est-ce que le préprocesseur (preprocessor)
Le préprocesseur est un programme qui effectue des modifications syntaxiques dans les fichiers sources de manière automatique.
shell
$ w h i c h cpp / usr / bin / cpp
Appel du préprocesseur
Appels encapsulés dans les appels à gcc (usage courant) shell
$ gcc - W a l l - c A . c
Lance le préprocesseur sur le fichier A.c et compile le résultat.
Appel « à la main » (éventuellement utile pour du débuggage) Affichage du résulat sur la sortie standard
$ cpp A . c
Envoie du résultat dans un fichier via un pipe
Comment expliquer au préprocesseur ce qu’il doit faire ?
Utilisation de «directives» préprocesseurs. Les directives commencent toujours par le caractère #.
Remplacement de texte (macros)
Code source 1 # d e f i n e L I N E S I Z E 1 0 2 4
2
3 c h a r b u f f e r[L I N E S I Z E];
4 s n p r i n t f(buffer, L I N E S I Z E, " h e l l o \ n " );
Code source apres traitement par cpp 1
2
3 c h a r b u f f e r[ 1 0 2 4 ] ;
4 s n p r i n t f(buffer, 1024 , " h e l l o \ n " );
Exemple d’utilisation des macros
1 # d e f i n e T R U E ( 1 = = 1 ) 2 # d e f i n e F A L S E ( 0 = = 1 ) 3
4 if (t e s t==T R U E){
5 D o S o m e t h i n g 6 }
Code source apres traitement par cpp 1 /* e x t r a i t du f i c h i e r m a t h . h */
2 # d e f i n e M _ P I 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 /* pi */
3
4 /* e x t r a i t du f i c h i e r s t d d e f . h */
5 # d e f i n e N U L L ((v o i d * ) 0 )
Définition de macros avec paramètres
1 # d e f i n e S Q U A R E ( a ) (( a )*( a )) 2 # d e f i n e M U L T I P L Y ( a , b ) (( a )*( b )) 3
4 d o u b l e d = S Q U A R E( 3 . 2 4 ) ;
5 d o u b l e dd = M U L T I P L Y( 3 . 2 , 4 . 5 ) ;
Code source apres traitement par cpp 1 d o u b l e d = ( ( 3 . 2 4 ) * ( 3 . 2 4 ) ) ;
2 d o u b l e dd = ( ( 3 . 2 ) * ( 4 . 5 ) ) ;
Définition de macros avec paramètres : écueils
Les macros avec paramètres peuvent être la source de très gros problèmes. Il convient par exemple de faire très attention à l’usage des parenthèses !
1 # d e f i n e S Q U A R E ( a ) a * a 2 # d e f i n e M U L T I P L Y ( a , b ) a * b 3
4 d o u b l e d = S Q U A R E(3 + 2);
5 d o u b l e dd = M U L T I P L Y(3 + 1 ,2);
Code source apres traitement par cpp 1 d o u b l e d = 3 + 2*3 + 2;
2 d o u b l e dd = 3 + 1 * 2 ;
Définition de macros avec paramètres : écueils
1 # d e f i n e min ( X , Y ) (( X ) < ( Y ) ? ( X ) : ( Y )) 2 # d e f i n e S Q U A R E ( a )*( a )
3
4 int i=3;
5 int j=0;
6 d o u b l e x = 3;
7 d o u b l e m = min(3.145 ,x);
8 d o u b l e M = min(i,j+ + ) ; 9 d o u b l e d = 1./S Q U A R E( 2 ) ;
Code source apres traitement par cpp 1 d o u b l e x = 3;
2 d o u b l e m = ( ( 3 . 1 4 5 ) < (x) ? ( 3 . 1 4 5 ) : (x));
3 d o u b l e M = ((i) < (j++) ? (i) : (j+ + ) ) ; 4 /* j est i n c r e m e n t e d e u x f o i s ! */
Inclusion de fichiers
La directive #includepermet d’inclure dans le fichier à compiler le contenu d’un autre fichier (à la manière d’un copier-coller).
1 # include< s t d i o . h >
2 # i n c l u d e " m y H e a d e r . h "
#include<>cherche les fichiers parmi une liste de directories
« système » comme /usr/local/includeou/usr/include.
#include""cherche les fichiers dans le directory local ou une liste de directories passés en argument à gcc via l’option de compilation-I, par ex. :
$ gcc -I dir1 -I dir2 -I ../dir3 -Wall -c foo.c
Compilation conditionnelle : #ifdef/#else/#endif
1 # i f d e f XXX
2 . . . . . // o p t i o n 1 3 # e l s e
4 . . . . . // o p t i o n 2 5 # e n d i f
Si XXXest défini par une directive#define alors on tient compte des lignes option 1 sinon c’est l’option 2.
1 # i f d e f E N G L I S H
2 c h a r msg[] = " h e l l o " ;
3 # e l s e
4 c h a r msg[] = " b o n j o u r " ;
5 # e n d i f
Compilation conditionnelle : #ifndef/#else/#endif
1 # i f n d e f XXX
2 . . . . . // o p t i o n 1 3 # e l s e
4 . . . . . // o p t i o n 2 5 # e n d i f
Si XXXest n’est pasdéfini par une directive #definealors on tient compte des lignes option 1 sinon c’est l’option 2.
1 # i f n d e f _ W I N 3 2
2 // _ W I N 3 2 is d e f i n e d by a l l W i n d o w s 32 c o m p i l e r s , b u t n o t by o t h e r s .
3 # i n c l u d e < u n i s t d . h >
4 # e l s e
5 # i n c l u d e < w i n d o w s . h >
6 # e n d i f
Compilation conditionnelle : les « include guards »
Tous les fichiers*.h doivent impérativement être écrits comme suit.
fichier myheader.h 1 # i f n d e f M Y _ H E A D E R _ H
2 # d e f i n e M Y _ H E A D E R _ H 3
4 ... c o n t e n u du f i c h i e r m y h e a d e r.h 5
6 # e n d i f
Ceci permet d’éviter les inclusions récursives de fichier*.h.
Compilation conditionnelle : #if//#elif#else/#endif
1 # if t e s t 1
2 . . . . . // o p t i o n 1 3 # e l i f t e s t 2
4 . . . . . // o p t i o n 2 5 # e l s e
6 . . . . . // o p t i o n 3 7 # e n d i f
test1 ettest2 sont des expressions entières évaluées par le préproceseur.
Les valeurs non-nulles sont équivalentes à « vrai » et déclenchent la compilation.
1 # if D E B U G _ L E V E L >= 2
2 p r i n t f( " a lot of d e b u g i n f o \ n " );
3 # e l i f D E B U G _ L E V E L == 1
4
Compilation conditionnelle : commenter un bloc de code
fichier myheader.h 1 # if 0
2 p r i n t f( " c e c i est un b l o c de l i g n e s " );
3 p r i n t f( " de c o d e " );
4 p r i n t f( " que je s o u h a i t e c o m m e n t e r " );
5 # e n d i f
Macros qui génèrent des instructions
Il convient d’être prudent avec les macros qui génèrent des instructions.
1 # d e f i n e E R R O R ( msg ) f p r i n t f ( stderr , msg ); e x i t ( E X I T _ F A I L U R E )
2 3
4 if(N U L L == ptr)
5 E R R O R( " p r o b l e m w i t h the p o i n t e r " );
est transformé en 1 if(N U L L == ptr)
2 f p r i n t f(stderr,msg);
3 e x i t(E X I T _ F A I L U R E);
Macros qui génèrent des instructions
Cette version ne convient pas non plus.
1 # d e f i n e E R R O R ( msg ) { f p r i n t f ( stderr , msg ); e x i t ( E X I T _ F A I L U R E )};
2
3 if(N U L L == ptr)
4 E R R O R( " p r o b l e m w i t h the p o i n t e r " );
est transformé en 1 if(N U L L == ptr){
2 f p r i n t f(stderr,msg);
3 e x i t(E X I T _ F A I L U R E);
4 };
et on ne peut pas ajouter de else 1 if(N U L L == ptr)
2 E R R O R( " p r o b l e m w i t h the p o i n t e r " );
3 e l s e {
Macros qui génèrent des instructions
La solution consiste à utiliser un do {} while(0)
1 # d e f i n e E R R O R ( msg ) do{ f p r i n t f ( stderr , msg ); e x i t ( E X I T _ F A I L U R E )}w h i l e(0)
2
3 if(N U L L == ptr)
4 E R R O R( " p r o b l e m w i t h the p o i n t e r " );
est transformé en 1 if(N U L L == ptr)
2 do {
3 f p r i n t f(stderr,msg);
4 e x i t(E X I T _ F A I L U R E);
5 } w h i l e( 0 ) ;
Scope de définition d’une macro
Le préprocesseur remplace une expression par la valeur de sa macro à partir de l’endroit où elle est définit dans le fichier.
La directive #undefpermet d’annuler la définition d’une macro.
1 bob = X;
2 # d e f i n e X 125 3 a l i c e = X; 4 p e t e r = X; 5 # u n d e f X 6 m a r y = X;
est remplacé par 1 bob = X;
2
On peut écrire les macros sur plusieurs lignes grâce au backslash.
1 # d e f i n e MSG " h e l l o \ 2 w o r l d\
3 "
4
5 # d e f i n e E R R O R ( msg ) do {\
6 f p r i n t f ( stderr , msg );\
7 e x i t ( E X I T _ F A I L U R E )}\
8 w h i l e (0) 9
10
11 p r i n t f ( MSG );
12 if ( N U L L == ptr )
13 E R R O R ( "w r o n g p o i n t e r" );
Quelques Macros prédéfinies
__DATE__: est remplacé par la date de compilation
__FILE__: est remplacé par le nom du fichier source en train d’être compilé
__LINE__: est remplacé par le numéro de ligne dans le fichier source en train d’être compilé
__TIME__: est remplacé par l’heure de compilation
Quelques Macros utiles pour le débuggage
1 # d e f i n e D I S P L A Y L I N E p r i n t f ( " DB % s : % d \ n " , _ _ F I L E _ _ , _ _ L I N E _ _ )
2
3 # d e f i n e S h o w D o u b l e ( var ) p r i n t f ( " DB % s = % g \ n " ,# var , var )
4 # d e f i n e S h o w I n t ( var ) p r i n t f ( " DB % s = % d \ n " ,# var , var )
5 # d e f i n e S h o w C h a r ( var ) p r i n t f ( " DB % s = % c \ n " ,# var , var )
6 # d e f i n e S h o w S t r i n g ( var ) p r i n t f ( " DB % s = % s \ n " ,# var , var )
Définition de macro à l’appel de gcc
Il est possible de définir ou de spécifier la valeur d’une macro à la volée, à l’appel de gcc, sans modifier les fichiers sources.
Définition de la macro myMacro
$ gcc - D m y M a c r o - W a l l - c foo . c
Définition de la valeur de la macromyMacro
$ gcc - D m y M a c r o =2 - W a l l - c foo . c