Le préprocesseur

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Cela vous aidera à comparer et à choisir le meilleur cours pour vous

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Posez une question et d'autres utilisateurs vous répondront

Qui voulez-vous pour répondre à votre question?

Nous ne publierons que votre nom et votre question

Le programme

Introduction du cours

Bonjour à tous,

M@teo21 dans son tutoriel sur le langage C nous fait un petit aperçu de ce qu'est le préprocesseur mais il y a encore beaucoup de choses à apprendre !
Toutefois, ce que je vais vous montrer n'est pas indispensable, mais c'est toujours mieux de savoir ce que c'est quand on en rencontre.
Vous allez voir que pour créer des macros il faut réfléchir un peu avant de faire n'importe quoi, parce que pour débugger ce n'est pas aussi facile qu'avec des fonctions.

Pour bien comprendre ce tutoriel il faut avoir compris et être un minimum à l'aise avec le préprocesseur.

Je ne reparlerai pas des #include #ifdef #else #elif #endif. Si vous avez un doute là-dessus retourner voir le tuto de M@teo21.

* Bonus GCC *
Si vous utilisez GCC vous pouvez voir le code après le passage du préprocesseur et avant la compilation. Il faut utiliser -E. C'est option est très utile pour débugger les parties qui contiennent le préprocesseur. :)

#define, defined, #undef#define

Je ne vais pas trop m'étaler sur ce paramètre car il est expliqué dans le tuto de M@teo21.
Pour résumer, cette directive sert à définir un nom ou une macro.

La macro suivante définit un nom sans aucun paramètre :

#define DEBUG

La macro suivante définit un nom et associe une valeur à ce nom :

#define NOIR 0x00000000

Une macro peut être redéfinie autre part dans le programme à la seule condition que celle-ci soit exactement pareille, seul quelques espaces peuvent être rajoutés. Exemple :

/* Incorrect */ #define N 1 #define N 2 /* Correct */ #define N 1 #define N 1

La macro suivante contient 1 paramètre et retourne la valeur absolue de ce paramètre :

#define MY_ABS(x) (((x) < 0) ? -(x) : (x))

Notez que les parenthèses sont très importantes dans les macros dues à la priorité des opérateurs. Certaines peuvent être omises, d'autres non. Pour être sûr de ne pas avoir de mauvaise surprise je vous conseille d'abuser des parenthèses pour ne laisser aucune ambiguïté possible !

Il faut faire très attention aux effets de bords. Une macro appelée normalement ne pose pas de problèmes, mais on aurait aussi pu appeler notre macro comme ça MY_ABS(a++). Cette expression évalue 2 fois (a++), ce qui est gênant. Mettre des parenthèses limite déjà certains effets de bords, mais c'est à l'utilisateur de faire attention à ce qu'il fait !

Vous pouvez créer des macros qui portent le même nom qu'une fonction déjà existante. Cependant, il faut bien différencier l'appel de la fonction de celle de la macro, sinon c'est uniquement la macro qui est prise en compte et pas la fonction. Pour utiliser la macro il faut l'appeler comme on le ferait normalement, par contre pour la fonction il faut entourer le nom de parenthèses. Certains puristes diront qu'il faut aussi déclarer votre fonction avec des parenthèses pour ne pas interférer avec les macros.

#include <stdio.h> #include <stdlib.h> #define puts(s) printf("Macro : %s\n", (s)) int main(void) { char s[] = "Salut :-)"; puts(s); /* Macro */ (puts)(s); /* Fonction */ return EXIT_SUCCESS; } Les macros sur plusieurs lignes

Les macros peuvent être définies sur plusieurs lignes, pour ça il faut terminer la ligne par un antislash (\) et avoir un retour à la ligne juste après. Un antislash non suivit d'un retour la la ligne termine la macro et tout ce qui suit n'est pas compris dedans.
Il est important de noter qu'aucune macro ne peut contenir de directives commençant par # (y compris la directive nulle). Dès que le préprocesseur rencontre un antislash suivit d'un retour à la ligne il supprime ces 2 là pour en faire une seule ligne. Voilà pourquoi l'utilisation d'autres directives à l'intérieur d'une macro est incorrect. :)
On peut parfois retrouver cette utilisation pour les chaînes de caractères. Une chaîne de caractère longue peut être 'coupée' par l'antislash + retour à la ligne et être terminée sur la ligne d'après.
Voilà un exemple de ce qu'il faut pas faire :

#define MAJEUR(age) \ #if (age >= 18) \ ...

Un petit exemple pour montrer comment agit le préprocesseur :

#define M() 3 + \ 2 + 1; int main(void) { char s[] = "Bonjour a tous ! Vous etes bien sur le site du zero\ mais comme cette chaine est trop longue je la met sur plusieurs\ lignes. Evitez tout de meme d'utiliser ca, c'est pas tres propre !"; M() return 0; } int main(void) {   char s[] = "Bonjour a tous ! Vous etes bien sur le site du zero  mais comme cette chaine est trop longue je la met sur plusieurs  lignes. Evitez tout de meme d'utiliser ca, c'est pas tres propre !";   3 + 2 + 1;   return 0; }

Il faut faire attention tout de même aux macros faites sur plusieurs lignes. Le piège étant de mettre une suite d'instruction et d'appeler cette macro dans un if sans accolades, par exemple. Un exemple vaut mieux qu'un long discours :

#include <stdio.h> #include <stdlib.h> #define M(a) \ if ((a) < 0) \ printf("Inferieur a 0\n"); \ if ((a) > 0) \ printf("Superieur a 0\n"); \ if ((a) == 0) \ printf("Egal a 0"); int main(void) { int a = 5; if (a < 0) M(a) else printf("Autre instruction"); return EXIT_SUCCESS; }

Ce code bidon et anodin n'affichera pas Autre instruction mais Superieur a 0 Autre instruction.
Pour palier ce problème, une astuce assez courante est d'utiliser la boucle do { ... } while(0); qui permet de regrouper les instructions et éventuellement, d'obliger le programmeur à mettre un point virgule après l'appel de la macro. Pour que le code du dessus soit correct, on aurait dû écrire :

#include <stdio.h> #include <stdlib.h> #define M(a) \ do { \ if ((a) < 0) \ printf("Inferieur a 0\n"); \ if ((a) > 0) \ printf("Superieur a 0\n"); \ if ((a) == 0) \ printf("Egal a 0"); \ } while(0) int main(void) { int a = 5; if (a < 0) M(a); else printf("Autre instruction"); return EXIT_SUCCESS; }

On peut aussi n'utiliser que les accolades, mais l'utilisation de la boucle sert à bien montrer au programmeur qu'il s'agit d'un bloc d'instructions.

defined

L'opérateur defined agit au même titre que #ifdef : il vérifie si la macro existe ; il est remplacé par 1 si elle existe, par 0 le cas échéant. Avec #if ou #ifdef on ne peut tester qu'un seul paramètre à la fois, avec l'utilisation de defined on va pouvoir en tester plusieurs. :)
L'utilisation se fait avec ou sans parenthèses.

Exemple pour l'implémentation sur différents systèmes d'exploitation :

#if defined __APPLE__ || defined linux # include <unistd.h> #elif defined ( WIN32 ) || defined ( WIN64 ) # include <windows.h> #endif

Vous trouverez une liste des différentes constantes définies en fonction de l'implémentation en allant sur ce lien.

* Bonus GCC *

Plutôt que de mettre des #define un peu partout pour faire des tests (par exemple le #ifdef DEBUG qu'on retrouve souvent) on peut définir ce paramètre avec l'option -D de GCC. Avec l'exemple du DEBUG cité précédemment, on a $ gcc -DDEBUG main.c. Cette commande aura la même action que #define DEBUG et agira sur l'ensemble du fichier.

Cette astuce fonctionne avec Code::Blocks, il suffit d'aller dans les options de compilation.

#undef

Le #undef fait exactement l'inverse de ce que fait #define. :p
Il supprime ce qui a été défini auparavant !
Utilisation très simple :

#undef X

Où X est le nom à supprimer.

Le # et le ##L'opérateur #

Vous avez déjà dû le rencontrer plus d'une fois celui-là dans les déclarations telles que #define #if #else etc.
Maintenant je vais vous montrer une autre manière de l'utiliser. :p

On va commencer par la plus simple : la directive nulle !
C'est tout simplement le # sans rien derrière (éventuellement des espaces/tabulations ou commentaires).
On s'en sert généralement pour 'lier' différentes directives. Ce que j'entends par 'lier' c'est en quelque sorte faire un 'bloc' de directives.
Un exemple vaut mieux qu'un long discours :

#if defined ( __APPLE__ ) # /* Si on est sur du matériel APPLE on inclut */ # /* <unistd.h> et tout le reste pour les sockets */ # include <unistd.h> # include <sys/socket.h> # include <sys/types.h> # include <arpa/inet.h> #elif defined ( linux ) # /* Si on est sur Linux on inclut ... la même chose */ # include <unistd.h> # include <sys/socket.h> # include <sys/types.h> # include <arpa/inet.h> #else # /* Sinon on est sous Windows */ # include <windows.h> # include <winsock2.h> #endif

Il ne peut en aucun cas être placé dans une macro.

Maintenant le plus intéressant !
L'opérateur # suivi d'un nom remplace automatiquement ce nom en chaîne de caractères.
Petit exemple :

#define AFFICHE_INT(x) printf( #x " = %d\n", (x) );

Comme vous pouvez le remarquer j'ai placé #x au début du printf, ce qui aura pour effet de transformer l'argument x en chaîne de caractère. Les espaces contenus entre le # et le paramètre sont ignorés (#a est équivalent à # a).
Un petit exemple pour illustrer tout ça :

#include <stdio.h> #include <stdlib.h> #define AFFICHE_INT(x) printf( #x " = %d\n", (x) ); int main(void) { int a = 5, b = 6; AFFICHE_INT(a) AFFICHE_INT(b) AFFICHE_INT(a + b) return EXIT_SUCCESS; }

Ce code sera 'transformé' en sortie en :

#include <stdio.h> #include <stdlib.h> int main(void) { int a = 5, b = 6; printf( "a" " = %d\n", a ); printf( "b" " = %d\n", b ); printf( "a + b" " = %d\n", a + b ); return EXIT_SUCCESS; }

Ce qui nous donne en sortie :

a = 5 b = 6 a + b = 11L'opérateur ##

L'opérateur ## concatène l'argument de gauche avec celui de droite tout en restant 'macro'. Les espaces contenus entre les différents arguments sont ignorés ( A ## B est égal à A##B).
Comme la résultante reste un argument 'macro', il faut que celle-ci soit définie auparavant pour être utilisée à bon escient. Si elle n'est pas définie au moment de l'appel, le compilateur nous indiquera une erreur.
Exemple :

#define AFFICHE_INT(x, y) printf( #x #y " = %d\n", x##y );

Dans ce cas-là si on appelle notre macro comme ça : AFFICHE_INT(a, b), elle sera remplacée par : printf( "a" "b" " = %d\n", ab );.
Il faut donc que l'argument ab soit défini avant l'appel de la macro. Notez que j'ai mis 'avant l'appel' et pas 'avant la déclaration', ce qui signifie que AFFICHE_INT peut être défini sans que ab ne soit connu. ab peut être soit défini par le préprocesseur (#define) ou alors comme une simple variable.

#include <stdio.h> #include <stdlib.h> #define AFFICHE_INT(x, y) printf( #x #y " = %d\n", x##y ); #define ab 5 int main(void) { int a = 5, b = 6; int ba = 10; AFFICHE_INT(a, b) /* Affiche 'ab' défini par le préprocesseur */ AFFICHE_INT(b, a) /* Affiche la variable 'ba' */ return EXIT_SUCCESS; }

Ce code nous affichera :

ab = 5 ba = 10Utilisation des opérateurs # et ## dans la même expression.

Si vous avez fait des tests en essayant d'utiliser à la fois # et ## vous aurez remarqué que le compilateur apprécie modérément.
De ce fait, pour pouvoir utiliser ces 2 opérateurs en même temps, il va falloir faire plusieurs macros afin de la créer.

Un exemple pour montrer comment concaténer 2 chaînes de caractères :

#include <stdio.h> #include <stdlib.h> #define CREER_CHAINE(chaine) #chaine #define TMP(chaine) CREER_CHAINE(chaine) #define CONCAT_CHAINE(chaine1, chaine2) TMP(chaine1 ## chaine2) int main(void) { printf("%s", CONCAT_CHAINE(Hello\n, Ca va ?)); return EXIT_SUCCESS; }

Comme je vous l'ai dit pour pouvoir utiliser les 2 opérateurs, il faut faire plusieurs macros. C'est en fait pour 'isoler' chaque paramètre afin que la macro qui contient un # ou ## ne 'voit' pas le # ou ## de l'autre.

Petit exercice

Écrivez une macro PRINT qui prend 2 paramètres. Le premier est le type de l'élément à afficher et le second est l'expression à afficher.
Le 'prototype' est celui-là : #define PRINT(type, expr).
Exemple d'utilisation :

PRINT(int, 1+3); PRINT(double, 4.0 * atan(1.0)); PRINT(char, 'c');

Dois nous retourner :

1+3 = 4 4.0 * atan(1.0) = 3.141593 'c' = c

Indice :

Il faut définir, à partir du type, le formateur approprié. On peut définir une macro du type : #define PRINT_(type) PRINT_##type.
Il faut ensuite définir les différents types qui seront utilisés ( PRINT_int, PRINT_double, PRINT_char) et leur associer le formateur adéquat. :)

Correction :

Á partir de l'indice on peut arriver à ce résultat-là :

#include <stdio.h> #include <stdlib.h> #include <math.h> #define PRINT_(type) PRINT_##type #define PRINT_int "%d" #define PRINT_double "%f" #define PRINT_char "%c" #define PRINT(type, expr) printf(#expr " = " PRINT_(type) "\n", expr) int main(void) { PRINT(int, 1+3); PRINT(double, 4.0 * atan(1.0)); PRINT(char, 'c'); return EXIT_SUCCESS; } #line, #error, #pragma

Appeler le centre

Avez-vous besoin d'un coach de formation?

Cela vous aidera à comparer et à choisir le meilleur cours pour vous

Le préprocesseur

Prix sur demande