• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

Ce cours existe en livre papier.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 14/02/2024

Utilisez les directives du préprocesseur

Dans les codes source, on retrouve des lignes un peu particulières qu'on appelle des directives de préprocesseur. Elles commencent toujours par le symbole#.

Utilisez #include pour inclure un fichier dans un autre

#include ne fait rien d'autre qu'insérer un fichier dans un autre.

Utilisez #define pour associer une valeur à un mot

#define permet de définir une constante de préprocesseur, autrement dit, d'associer une valeur à un mot.

L'intérêt, c'est que :

  • ça ne prend pas de place en mémoire, puisque que lors de la compilation il ne reste plus que des nombres dans le code source ;

  • le remplacement se fait dans tout le fichier dans lequel se trouve le#define. Si vous aviez défini une constante en mémoire dans une fonction, celle-ci n'aurait été valable que dans la fonction, puis aurait été supprimée. Le #define en revanche s'appliquera à toutes les fonctions du fichier, ce qui peut s'avérer parfois pratique selon les besoins.

Définissez la taille des tableaux avec  #define

Voici un exemple :

#define TAILLE_MAX      1000

int main(int argc, char *argv[])
{
    char chaine1[TAILLE_MAX], chaine2[TAILLE_MAX];
    // ...

Mais… je croyais qu'on ne pouvait pas mettre de variable ni de constante entre les crochets lors d'une définition de tableau ?

Oui, mais TAILLE_MAX n'est PAS une variable ni une constante. En effet je vous l'ai dit, le préprocesseur transforme le fichier avant compilation en :

int main(int argc, char *argv[])
{
    char chaine1[1000], chaine2[1000];
    // ...

… et cela est valide !

En définissant TAILLE_MAX ainsi, vous pouvez vous en servir pour créer des tableaux d'une certaine taille.

Si à l'avenir cela s'avère insuffisant, vous n'aurez qu'à :

  1. Modifier la ligne du #define  .

  2. Recompiler, et vos tableaux de char prendront tous la nouvelle taille que vous aurez indiquée.

Faites des calculs dans les #define

Il est possible de faire quelques petits calculs dans les #define  .
Par exemple, ce code crée une constante LARGEUR_FENETRE  , une autre HAUTEUR_FENETRE  , puis une troisième NOMBRE_PIXELS qui contiendra le nombre de pixels affichés à l'intérieur de la fenêtre (le calcul est simple : largeur * hauteur) :

#define LARGEUR_FENETRE  800
#define HAUTEUR_FENETRE  600
#define NOMBRE_PIXELS    (LARGEUR_FENETRE * HAUTEUR_FENETRE)

La valeur de NOMBRE_PIXELS est remplacée avant la compilation par le code suivant : (LARGEUR_FENETRE * HAUTEUR_FENETRE), c'est-à-dire par (800 * 600), ce qui fait 480 000.
Mettez toujours votre calcul entre parenthèses, comme je l'ai fait par sécurité pour bien isoler l'opération.

Vous pouvez faire toutes les opérations de base que vous connaissez : addition (+), soustraction (-), multiplication (*), division (/) et modulo (%).

Voyons dans cette courte vidéo comment créer une constante de préprocesseur et l’utiliser dans votre programme. D’une part nous verrons comment créer une constante pour l’utiliser dans une partie du code, et d’autre part comment créer un tableau statique à l’aide de ces constantes :

Découvrez les constantes prédéfinies par le processeur

En plus des constantes que vous pouvez définir vous-même, il existe quelques constantes prédéfinies par le préprocesseur.

  • __LINE__ donne le numéro de la ligne actuelle.

  • __FILE__ donne le nom du fichier actuel.

  • __DATE__ donne la date de la compilation.

  • __TIME__ donne l'heure de la compilation.

Ces constantes peuvent être utiles pour gérer des erreurs, en faisant par exemple ceci :

printf("Erreur a la ligne %d du fichier %s\n", __LINE__, __FILE__);
printf("Ce fichier a ete compile le %s a %s\n", __DATE__, __TIME__);
Erreur a la ligne 9 du fichier main.c

Ce fichier a ete compile le Jan 13 2006 a 19:21:10

Il est aussi possible de faire tout simplement :

#define CONSTANTE

… sans préciser de valeur.

Cela veut dire pour le préprocesseur que le mot CONSTANTE est défini, tout simplement. Il n'a pas de valeur, mais il "existe".

Quel peut en être l'intérêt ?

L'intérêt est moins évident que tout à l'heure, mais il y en a un et nous allons le découvrir très rapidement.

Créez des macros pour remplacer un mot par un code source

Nous avons vu qu'avec le #define on pouvait demander au préprocesseur de remplacer un mot par une valeur. Il s'agit en fait d'un simple rechercher-remplacer fait par le préprocesseur avant la compilation.

Mais ce n'est pas tout…

Créez une macro simple, sans paramètres

Voici un exemple  :

#define COUCOU() printf("Coucou");

Ce qui change ici, ce sont les parenthèses qu'on a ajoutées après le mot-clé (ici, COUCOU()  ). Nous verrons à quoi elles peuvent servir tout à l'heure.

Testons la macro dans un code source :

#define COUCOU() printf("Coucou");

int main(int argc, char *argv[])
{
    COUCOU()

    return 0;
}
Coucou

Je vous l'accorde, ce n'est pas original pour le moment. Ce qu'il faut déjà bien comprendre, c'est que les macros ne sont en fait que des bouts de code qui sont directement remplacés dans votre code source juste avant la compilation.

Le code qu'on vient de voir ressemblera en fait à ça lors de la compilation :

int main(int argc, char *argv[])
{
    printf("Coucou");

    return 0;
}

Si vous avez compris ça, vous avez compris le principe de base des macros.

Mais… on ne peut mettre qu'une seule ligne de code par macro ?

Non, heureusement il est possible de mettre plusieurs lignes de code à la fois. Il suffit de placer un\  avant chaque nouvelle ligne, comme ceci :

#define RACONTER_SA_VIE()   printf("Coucou, je m'appelle Brice\n"); \
                            printf("J'habite a Nice\n"); \
                            printf("J'aime la glisse\n");

int main(int argc, char *argv[])
{
    RACONTER_SA_VIE()

    return 0;
}
Coucou, je m'appelle Brice
J'habite a Nice
J'aime la glisse

Remarquez dans le main que l'appel de la macro ne prend pas de point-virgule à la fin. En effet, c'est une ligne pour le préprocesseur, elle ne nécessite donc pas d'être terminée par un point-virgule.

Créez une macro qui prend des paramètres

Pour le moment, on a vu comment faire une macro sans paramètre, c'est-à-dire avec des parenthèses vides. Le principal intérêt de ce type de macros, c'est de pouvoir "raccourcir" un code un peu long, surtout s'il est amené à être répété de nombreuses fois dans votre code source.

Cependant, les macros deviennent réellement intéressantes lorsqu'on leur met des paramètres. Cela marche quasiment comme avec les fonctions :

#define MAJEUR(age) if (age >= 18) \
                    printf("Vous etes majeur\n");

int main(int argc, char *argv[])
{
    MAJEUR(22)

    return 0;
}
Vous etes majeur

Le principe de notre macro est assez intuitif :

#define MAJEUR(age) if (age >= 18) \
                    printf("Vous etes majeur\n");

On met entre parenthèses le nom d'une "variable" qu'on nomme age. Dans tout notre code de macro, age sera remplacé par le nombre qui est indiqué lors de l'appel à la macro (ici, c'est 22).

Ainsi, notre code source précédent ressemblera à ceci juste après le passage du préprocesseur :

int main(int argc, char *argv[])
{
    if (22 >= 18)
    printf("Vous etes majeur\n");

    return 0;
}

Le code source a été mis à la place de l'appel de la macro, et la valeur de la "variable" age a été mise directement dans le code source de remplacement.

Il est possible aussi de créer une macro qui prend plusieurs paramètres :

#define MAJEUR(age, nom) if (age >= 18) \
                    printf("Vous etes majeur %s\n", nom);

int main(int argc, char *argv[])
{
    MAJEUR(22, "Maxime")

    return 0;
}

Réalisez des conditions en langage préprocesseur

Tenez-vous bien : il est possible de réaliser des conditions en langage préprocesseur ! Voici comment cela fonctionne :

#if condition
    /* Code source à compiler si la condition est vraie */
#elif condition2
    /* Sinon si la condition 2 est vraie compiler ce code source */
#endif

Le mot-clé #if permet d'insérer une condition de préprocesseur. #elif signifie else if(sinon si).

La condition s'arrête lorsque vous insérez un #endif  . Vous noterez qu'il n'y a pas d'accolades en préprocesseur.

L'intérêt, c'est qu'on peut ainsi faire des compilations conditionnelles.

En effet, si la condition est vraie, le code qui suit sera compilé. Sinon, il sera tout simplement supprimé du fichier le temps de la compilation. Il n'apparaîtra donc pas dans le programme final.

Utilisez  #ifdef  et #ifndef

Nous allons voir maintenant l'intérêt de faire un #define d'une constante sans préciser de valeur, comme je vous l'ai montré précédemment :

#define CONSTANTE

En effet, il est possible d'utiliser:

  • #ifdef pour dire « Si la constante est définie ».

  • #ifndef  pour dire « Si la constante n'est pas définie ».

On peut alors imaginer ceci :

#define WINDOWS

#ifdef WINDOWS
    /* Code source pour Windows */
#endif

#ifdef LINUX
    /* Code source pour Linux */
#endif

#ifdef MAC
    /* Code source pour Mac */
#endif

C'est comme ça que font certains programmes multi-plateformes pour s'adapter à l'OS, par exemple. Alors, bien entendu, il faut recompiler le programme pour chaque OS (ce n'est pas magique).

Si vous êtes sous Windows, vous écrivez un #define WINDOWS  en haut, puis vous compilez.

Si vous voulez compiler votre programme pour Linux (avec la partie du code source spécifique à Linux), vous devrez alors modifier le define et mettre à la place #define LINUX. Recompilez, et cette fois c'est la portion de code source pour Linux qui sera compilée, les autres parties étant ignorées.

Évitez les inclusions infinies grâce à #ifndef

#ifndef est très utilisé dans les .h pour éviter les inclusions infinies.

Une inclusion infinie ? C'est-à-dire ?

Imaginez : j'ai un fichier A.h et un fichier B.h  .

Le fichier A.h contient un include du fichier B.h  . Le fichier B est donc inclus dans le fichier A.

Mais, et c'est là que ça commence à coincer, supposez que le fichier B.h contienne à son tour un include du fichierA.h  ! Ça arrive quelquefois en programmation : le premier fichier a besoin du second pour fonctionner, et le second a besoin du premier.

Si on y réfléchit un peu, on imagine vite ce qu'il va se passer :

  1. L'ordinateur lit A.h et voit qu'il faut inclure B.h  .

  2. Il lit B.h pour l'inclure, et là il voit qu'il faut inclure A.h  .

  3. Il inclut donc A.h dans B.h  , mais dans A.h on lui indique qu'il doit inclure B.h  !

  4. Rebelote, il va voir B.h et voit à nouveau qu'il faut inclure A.h  .

  5. Etc. : vous vous doutez bien que tout cela est sans fin !

En fait, à force de faire trop d'inclusions, le préprocesseur s'arrêtera en disant :

"J'en ai marre des inclusions !"

… ce qui fera planter votre compilation.

Comment faire pour éviter cet affreux cauchemar ?

Voici l'astuce. Désormais, je vous demande de faire comme ça dans TOUS vos fichiers .h sans exception :

#ifndef DEF_NOMDUFICHIER // Si la constante n'a pas été définie le fichier n'a jamais été inclus
#define DEF_NOMDUFICHIER // On définit la constante pour que la prochaine fois le fichier ne soit plus inclus

/* Contenu de votre fichier .h (autres include, prototypes, define...) */

#endif

Comprenez-vous bien comment ce code fonctionne ? La première fois qu'on m'a présenté cette technique, j'étais assez désorienté : je vais essayer de vous l'expliquer.

Imaginez que le fichier .hsoit inclus pour la première fois. Le préprocesseur lit la condition "Si la constante DEF_NOMDUFICHIER n'a pas été définie". Comme c'est la première fois que le fichier est lu, la constante n'est pas définie, donc le préprocesseur entre à l'intérieur du if  .

La première instruction qu'il rencontre est justement :

#define DEF_NOMDUFICHIER

Maintenant, la constante est définie. La prochaine fois que le fichier sera inclus, la condition ne sera plus vraie et donc le fichier ne risque plus d'être inclus à nouveau.

Bien entendu, vous appelez votre constante comme vous voulez. Moi, je l'appelleDEF_NOMDUFICHIER par habitude.

Vous remplacerez donc NOMDUFICHIER par le nom de votre fichier.h.

En résumé

  • Le préprocesseur est un programme qui analyse votre code source et y effectue des modifications avant la compilation.

  • L'instruction de préprocesseur #include insère le contenu d'un autre fichier.

  • L'instruction #define définit une constante de préprocesseur. Elle permet de remplacer un mot-clé par une valeur dans le code source.

  • Les macros sont des morceaux de code tout prêts définis à l'aide d'un #define  . Ils peuvent accepter des paramètres.

  • Il est possible d'écrire des conditions en langage préprocesseur pour choisir ce qui sera compilé. On utilise notamment les mots-clés #if  , #elif et #endif  .

  • Pour éviter qu'un fichier .h ne soit inclus un nombre infini de fois, on le protège à l'aide d'une combinaison de constantes de préprocesseur et de conditions. Tous vos futurs fichiers .h devront être protégés de cette manière.

Je vous ai laissé souffler un peu avec ce chapitre. C'est le moment de faire une pause et un quiz tant qu'on y est ! Dans la prochaine partie, on reprendra les choses sérieuses… Et on commencera par voir comment créer vos propres types de variables. 

Exemple de certificat de réussite
Exemple de certificat de réussite