FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 35, nombre de questions : 194, dernière mise à jour : 18 février 2018 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez devenir rédacteur, lisez ceci .
Sur ce, je vous souhaite une bonne lecture.
- 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 peut 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__ ?
- Que signifie l'underscore (_) en début du nom d'une fonction ou d'une macro, etc. ?
Avant d'être effectivement compilés, les fichiers source 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 remplacer par 10 après son passage. Cela signifie que :
Code c : | Sélectionner tout |
1 2 3 4 5 6 | #define N 10 int f(void) { return N + 1; } |
Code c : | Sélectionner tout |
1 2 3 4 | 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 :
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #define PRINT(x) printf("MESSAGE : %s\n", x) int main(void) { PRINT("Hello, world !"); return 0; } |
Code C : | Sélectionner tout |
1 2 3 4 5 6 | 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 :
Code c : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | #define IN #define OUT int f(IN int n, OUT int * p1, OUT int * p2) { *p1 = n - 1; *p2 = n + 1; return 2 * n; } |
Code C : | Sélectionner tout |
1 2 3 4 5 6 | 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.
Code c : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #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; } |
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 valant 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 :
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #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; } |
Code C : | Sélectionner tout |
1 2 | #define TOSTR(x) __STR(x) #define __STR(x) #x |
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 :
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | #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; } |
Code C : | Sélectionner tout |
1 2 | #define WIDESTR(x) ___WSTR(x) #define ___WSTR(x) L##x |
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 :
Code C : | Sélectionner tout |
1 2 3 4 5 6 | #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)).
Code C : | Sélectionner tout |
1 2 3 | /* Si sizeof(int) != sizeof(long), le tableau assert_int_long aura une taille négative, */ /* ce qui ne sera pas apprécié 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.
Il signifie la plupart du temps que la fonction ou la macro en question n'est pas standard (donc pas très portable…). Plus précisément, qu'elle fait partie de la version de la bibliothèque standard proposée par l'environnement que vous utilisez, mais qu'elle n'est pourtant pas reconnue par la norme. Par exemple, les fonctions et macros DOS/Windows _getch, _kbhit, _spawn*, _P_WAIT, etc. font partie de la bibliothèque du C (appelée CRT sous Windows pour C Run-Time Library) alors qu'elles ne sont pas standard, d'où l'underscore. Cette règle de l'underscore ne s'applique toutefois bien évidemment pas aux fonctions des bibliothèques tierces qui accompagnent le compilateur (API du système et autres bibliothèques spécifiques). Par exemple, sous les systèmes supportant POSIX (c'est-à-dire UNIX et tous ses clones), les fonctions read, write, exec* font partie de l'API POSIX (on les appelle souvent « appels système », car ce sont la plupart du temps des fonctions du système) et non de la bibliothèque du C donc pas besoin d'underscore. L'API POSIX est cependant proposée en tant qu'extension de CRT sous Windows (et elle n'est pas supportée à 100 %…) d'où l'apparition des noms avec underscore (_read, _write, _exec*, etc.).
Cette convention de nommage fut adoptée pour la première fois par le langage C++ et est utilisée aujourd'hui par tous les compilateurs respectant la norme de ce langage (la première version de cette norme date de 1998). Elle n'est pas obligatoire en C, mais beaucoup de compilateurs C (ou C++…) sont en fait des compilateurs C/C++ (c'est-à-dire qu'ils savent compiler aussi bien du code C que du code C++, même si ce sont des langages différents) donc ils appliquent ce système aux deux langages, ce qui n'est pas incompatible avec la norme du C.
Pour les mot-clés non standard, on met deux underscores en avant au lieu d'un seul. Par exemple : __declspec, __stdcall, __asm, __int32, __int64, etc.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.