Les directives de programmation / préprocesseur C et les directives de macros

sont des instructions spéciales adressées au préprocesseur (directive du préprocesseur) ou au compilateur (directive du compilateur) sur la façon dont il doit traiter une partie ou la totalité de votre code source ou définir des indicateurs sur l’objet final et sont utilisées pour faciliter l’écriture du code source (plus portable par exemple) et pour rendre le code source plus compréhensible. Les directives sont gérées par le préprocesseur, qui est soit un programme distinct appelé par le compilateur, soit une partie du compilateur lui-même.

#includeEdit

C a certaines fonctionnalités dans le langage et d’autres dans une bibliothèque standard, qui est un référentiel de code disponible avec chaque compilateur C conforme aux normes. Lorsque le compilateur C compile votre programme, il le lie généralement également à la bibliothèque C standard. Par exemple, lorsque vous rencontrez une directive #include <stdio.h>, elle remplace la directive par le contenu de la stdio.fichier d’en-tête h.

Lorsque vous utilisez des fonctionnalités de la bibliothèque, C vous oblige à déclarer ce que vous utiliseriez. La première ligne du programme est une directive de prétraitement qui devrait ressembler à ceci:

#include <stdio.h>

La ligne ci-dessus provoque les déclarations C qui se trouvent dans le stdio.h en-tête à inclure pour une utilisation dans votre programme. Habituellement, cela est implémenté en insérant simplement dans votre programme le contenu d’un fichier d’en-tête appelé stdio.h, situé dans un emplacement dépendant du système. L’emplacement de ces fichiers peut être décrit dans la documentation de votre compilateur. Une liste des fichiers d’en-tête C standard est répertoriée ci-dessous dans le tableau des en-têtes.

Le stdio.l’en-tête h contient diverses déclarations d’entrée / sortie (E/S) utilisant une abstraction de mécanismes d’E/S appelés flux. Par exemple, il existe un objet de flux de sortie appelé stdout qui est utilisé pour générer du texte sur la sortie standard, qui affiche généralement le texte sur l’écran de l’ordinateur.

Si vous utilisez des crochets comme dans l’exemple ci-dessus, le préprocesseur est invité à rechercher le fichier include le long du chemin d’accès de l’environnement de développement pour les includes standard.

#include "other.h"

Si vous utilisez des guillemets («  »), le préprocesseur est censé rechercher des emplacements supplémentaires, généralement définis par l’utilisateur, pour le fichier d’en-tête, et ne revenir aux chemins d’inclusion standard que s’il n’est pas trouvé dans ces emplacements supplémentaires. Il est courant que ce formulaire inclue une recherche dans le même répertoire que le fichier contenant la directive #include.

REMARQUE : Vous devez vérifier la documentation de l’environnement de développement que vous utilisez pour toute implémentation spécifique au fournisseur de la directive #include.

HeadersEdit

The C90 standard headers list:

  • <assert.h>
  • <ctype.h>
  • <errno.h>
  • <float.h>
  • <limits.h>
  • <locale.h>
  • <math.h>
  • <setjmp.h>
  • <signal.h>
  • <stdarg.h>
  • <stddef.h>
  • <stdio.h>
  • <stdlib.h>
  • <string.h>
  • <time.h>

Headers added since C90:

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <iso646.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>
  • <wchar.h>
  • <wctype.h>

#pragmaEdit

La directive pragma (informations pragmatiques) fait partie de la norme, mais la signification de tout pragma dépend du logiciel mise en œuvre de la norme utilisée. La directive #pragma fournit un moyen de demander un comportement spécial au compilateur. Cette directive est particulièrement utile pour les programmes inhabituellement volumineux ou qui doivent tirer parti des capacités d’un compilateur particulier.

Les pragmas sont utilisés dans le programme source.

#pragma token(s)
  1. pragma est généralement suivi d’un seul jeton, qui représente une commande à laquelle le compilateur doit obéir. Vous devez vérifier l’implémentation logicielle de la norme C que vous avez l’intention d’utiliser pour une liste des jetons pris en charge. Sans surprise, l’ensemble des commandes pouvant apparaître dans les directives #pragma est différent pour chaque compilateur; vous devrez consulter la documentation de votre compilateur pour voir quelles commandes il autorise et ce que font ces commandes.

Par exemple, l’une des directives de préprocesseur les plus implémentées, #pragma once lorsqu’elle est placée au début d’un fichier d’en-tête, indique que le fichier où elle réside sera ignoré s’il est inclus plusieurs fois par le préprocesseur.

REMARQUE: D’autres méthodes existent pour effectuer cette action, communément appelée utilisation de gardes include.

#defineEdit

AVERTISSEMENT: les macros de préprocesseur, bien que tentantes, peuvent produire des résultats assez inattendus si elles ne sont pas bien faites. Gardez toujours à l’esprit que les macros sont des substitutions textuelles faites à votre code source avant que quoi que ce soit ne soit compilé. Le compilateur ne sait rien des macros et ne les voit jamais. Cela peut produire des erreurs obscures, entre autres effets négatifs. Préférez utiliser des fonctionnalités de langage, s’il existe des équivalents (Dans l’exemple, utilisez const int ou enum au lieu de #define d constantes).

Cela dit, il existe des cas où les macros sont très utiles (voir la macro debug ci-dessous pour un exemple).

La directive #define est utilisée pour définir des valeurs ou des macros utilisées par le préprocesseur pour manipuler le code source du programme avant sa compilation. Étant donné que les définitions du préprocesseur sont substituées avant que le compilateur n’agisse sur le code source, les erreurs introduites par #define sont difficiles à tracer.

Par convention, les valeurs définies à l’aide de #define sont nommées en majuscules. Bien que cela ne soit pas une exigence, il est considéré comme une très mauvaise pratique de faire autrement. Cela permet d’identifier facilement les valeurs lors de la lecture du code source.

Aujourd’hui, #define est principalement utilisé pour gérer les différences de compilateur et de plate-forme. Par exemple, une définition peut contenir une constante qui est le code d’erreur approprié pour un appel système. L’utilisation de #define devrait donc être limitée sauf si cela est absolument nécessaire; les instructions typedef et les variables constantes peuvent souvent remplir les mêmes fonctions de manière plus sûre.

Une autre caractéristique de la commande #define est qu’elle peut prendre des arguments, ce qui la rend plutôt utile en tant que créateur de pseudo-fonctions. Considérez le code suivant:

#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}

C’est généralement une bonne idée d’utiliser des parenthèses supplémentaires lors de l’utilisation de macros complexes. Notez que dans l’exemple ci-dessus, la variable « x » est toujours dans son propre ensemble de parenthèses. De cette façon, il sera évalué en entier, avant d’être comparé à 0 ou multiplié par -1. De plus, la macro entière est entourée de parenthèses, pour éviter qu’elle ne soit contaminée par un autre code. Si vous ne faites pas attention, vous courez le risque que le compilateur interprète mal votre code.

En raison des effets secondaires, il est considéré comme une très mauvaise idée d’utiliser des fonctions macro comme décrit ci-dessus.

int x = -10;int y = ABSOLUTE_VALUE( x++ );

Si ABSOLUTE_VALUE() était une fonction réelle ‘x’ aurait maintenant la valeur de ‘-9’, mais comme c’était un argument dans une macro, elle a été développée deux fois et a donc une valeur de -8.

Exemple:

Pour illustrer les dangers des macros, considérez cette macro naïve

#define MAX(a,b) a>b?a:b

et le code

i = MAX(2,3)+5;j = MAX(3,2)+5;

Jetez un coup d’œil à cela et considérez quelle pourrait être la valeur après exécution. Les instructions sont transformées en

int i = 2>3?2:3+5;int j = 3>2?3:2+5;

Ainsi, après exécution i = 8 et j =3 au lieu du résultat attendu de i = j = 8! C’est pourquoi on vous a conseillé d’utiliser un ensemble supplémentaire de parenthèses ci-dessus, mais même avec celles-ci, la route est pleine de dangers. Le lecteur d’alerte peut rapidement se rendre compte que si a ou b contient des expressions, la définition doit mettre entre parenthèses chaque utilisation de a, b dans la définition de macro, comme ceci:

#define MAX(a,b) ((a)>(b)?(a):(b))

Cela fonctionne, à condition que a, b n’ait aucun effet secondaire. En effet,

i = 2;j = 3;k = MAX(i++, j++);

entraînerait k=4, i=3 et j=5. Cela serait très surprenant pour quiconque s’attend à ce que MAX() se comporte comme une fonction.

Alors quelle est la bonne solution? La solution est de ne pas utiliser de macro du tout. Une fonction globale en ligne, comme celle-ci

inline int max(int a, int b) { return a>b?a:b }

n’a aucun des pièges ci-dessus, mais ne fonctionnera pas avec tous les types.

REMARQUE: La déclaration explicite inline n’est pas vraiment nécessaire à moins que la définition ne soit dans un fichier d’en-tête, car votre compilateur peut intégrer des fonctions pour vous (avec gcc, cela peut être fait avec -finline-functions ou -O3). Le compilateur est souvent meilleur que le programmeur pour prédire quelles fonctions valent la peine d’être insérées. De plus, les appels de fonction ne sont pas vraiment chers (ils l’étaient auparavant).

Le compilateur est en fait libre d’ignorer le mot clé inline. Ce n’est qu’un indice (sauf que inline est nécessaire pour permettre de définir une fonction dans un fichier d’en-tête sans générer de message d’erreur car la fonction est définie dans plus d’une unité de traduction).

(#, ##)

Les opérateurs # et ## sont utilisés avec la macro #define. L’utilisation de # provoque le retour du premier argument après le # sous forme de chaîne entre guillemets. Par exemple, la commande

#define as_string( s ) # s

obligera le compilateur à transformer cette commande

puts( as_string( Hello World! ) ) ;

en

puts( "Hello World!" );

En utilisant ## concatène ce qui est avant le # # avec ce qui est après. Par exemple, la commande

#define concatenate( x, y ) x ## y...int xy = 10;...

fera tourner le compilateur

printf( "%d", concatenate( x, y ));

en

printf( "%d", xy);

qui affichera bien sûr 10 en sortie standard.

Il est possible de concaténer un argument de macro avec un préfixe ou un suffixe constant pour obtenir un identifiant valide comme dans

#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )

qui définira une fonction appelée my_bar(). Mais il n’est pas possible d’intégrer un argument de macro dans une chaîne constante en utilisant l’opérateur de concaténation. Afin d’obtenir un tel effet, on peut utiliser la propriété ANSI C selon laquelle deux constantes de chaîne consécutives ou plus sont considérées comme équivalentes à une constante de chaîne unique lorsqu’elles sont rencontrées. En utilisant cette propriété, on peut écrire

#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )

que le macro-processeur transformera en

puts( "I'm eating " "fruit" " today." )

qui à son tour sera interprété par l’analyseur C comme une constante de chaîne unique.

L’astuce suivante peut être utilisée pour transformer une constante numérique en littéraux de chaîne

#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));

C’est un peu délicat, car il est développé en 2 étapes. Tout d’abord num2str(CONST) est remplacé par str(23), qui à son tour est remplacé par "23". Cela peut être utile dans l’exemple suivant:

#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif

Cela vous donnera un bon message de débogage, y compris le fichier et la ligne où le message a été émis. Si le DÉBOGAGE n’est pas défini, le message de débogage disparaîtra complètement de votre code. Veillez à ne pas utiliser ce type de construction avec tout ce qui a des effets secondaires, car cela peut entraîner des bogues, qui apparaissent et disparaissent en fonction des paramètres de compilation.

macrosEdit

Les macros ne sont pas vérifiées par type et n’évaluent donc pas les arguments. De plus, ils n’obéissent pas correctement à la portée, mais prennent simplement la chaîne qui leur est passée et remplacent chaque occurrence de l’argument macro dans le texte de la macro par la chaîne réelle de ce paramètre (le code est littéralement copié à l’emplacement d’où il a été appelé).

Un exemple d’utilisation d’une macro :

 #include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }

— le résultat de « a » doit être « 2 » (b+c=16 – > passé à ADD -> 16/SLICES – >le résultat est « 2 »)

REMARQUE:
Il est généralement mauvais de définir des macros dans les en-têtes.

Une macro ne doit être définie que lorsqu’il n’est pas possible d’obtenir le même résultat avec une fonction ou un autre mécanisme. Certains compilateurs sont capables d’optimiser le code là où les appels à de petites fonctions sont remplacés par du code en ligne, annulant tout avantage de vitesse possible.L’utilisation de typedefs, d’énumérations et d’inline (en C99) est souvent une meilleure option.

L’une des rares situations où les fonctions en ligne ne fonctionnent pas – vous êtes donc à peu près obligé d’utiliser des macros de type fonction à la place – est d’initialiser les constantes de temps de compilation (initialisation statique des structures).Cela se produit lorsque les arguments de la macro sont des littéraux que le compilateur peut optimiser en un autre littéral.

#errorEdit

La directive #error arrête la compilation. Lorsqu’on en rencontre un, la norme spécifie que le compilateur doit émettre un diagnostic contenant les jetons restants dans la directive. Ceci est principalement utilisé à des fins de débogage.

Les programmeurs utilisent « #error » à l’intérieur d’un bloc conditionnel, pour arrêter immédiatement le compilateur lorsque le « #if » ou « #ifdef » – au début du bloc – détecte un problème de compilation.Normalement, le compilateur ignore le bloc (et la directive « #error » à l’intérieur) et la compilation se poursuit.

 #error message

#warningEdit

De nombreux compilateurs prennent en charge une directive #warning. Lorsqu’on en rencontre un, le compilateur émet un diagnostic contenant les jetons restants dans la directive.

 #warning message

#undefEdit

La directive #undef définit une macro. L’identifiant n’a pas besoin d’avoir été défini au préalable.

#if, #else, #elif, #endif(conditionals)Edit

La commande #if vérifie si une expression conditionnelle de contrôle est évaluée à zéro ou non, et exclut ou inclut un bloc de code respectivement. Par exemple :

 #if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif

L’expression conditionnelle peut contenir n’importe quel opérateur C à l’exception des opérateurs d’affectation, des opérateurs d’incrémentation et de décrémentation, de l’opérateur address-of et de l’opérateur sizeof.

Un opérateur unique utilisé dans le prétraitement et nulle part ailleurs n’est l’opérateur défini. Il renvoie 1 si le nom de la macro, éventuellement entre parenthèses, est actuellement défini ; 0 si ce n’est pas le cas.

La commande #endif termine un bloc démarré par #if#ifdef, ou #ifndef.

La commande #elif est similaire à #if, sauf qu’elle est utilisée pour en extraire une à partir d’une série de blocs de code. Par exemple :

 #if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */

#ifdef, #ifndefEdit

La commande #ifdef est similaire à #if, sauf que le bloc de code qui le suit est sélectionné si un nom de macro est défini. À cet égard,

#ifdef NAME

est équivalent à

#if defined NAME

La commande #ifndef est similaire à #ifdef, sauf que le test est inversé :

#ifndef NAME

est équivalent à

#if !defined NAME

#lineEdit

Cette directive de préprocesseur est utilisée pour définir le nom de fichier et le numéro de ligne de la ligne suivant la directive sur de nouvelles valeurs. Ceci est utilisé pour définir les macros __FILE__ et __LINE__.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.