C Language
Préprocesseur et Macros
Recherche…
Introduction
Toutes les commandes du préprocesseur commencent par le symbole dièse (dound) #
. La macro AC est simplement une commande de préprocesseur définie à l'aide de la directive de préprocesseur #define
. Pendant la phase de prétraitement, le préprocesseur C (une partie du compilateur C) remplace simplement le corps de la macro où son nom apparaît.
Remarques
Lorsqu'un compilateur rencontre une macro dans le code, il effectue un remplacement de chaîne simple, aucune opération supplémentaire n'est effectuée. Pour cette raison, les modifications apportées par le préprocesseur ne respectent pas la portée des programmes C - par exemple, une définition de macro ne se limite pas à être dans un bloc, elle n'est donc pas affectée par un '}'
qui termine une instruction de bloc.
Le préprocesseur n'est, de par sa conception, pas complet - il existe plusieurs types de calcul que le préprocesseur ne peut effectuer seul.
Les compilateurs ont généralement un indicateur de ligne de commande (ou paramètre de configuration) qui nous permet d'arrêter la compilation après la phase de prétraitement et d'inspecter le résultat. Sur les plates-formes POSIX, cet indicateur est -E
. Ainsi, l'exécution de gcc avec cet indicateur imprime la source étendue à stdout:
$ gcc -E cprog.c
Souvent, le préprocesseur est implémenté en tant que programme séparé, qui est appelé par le compilateur. Le nom commun de ce programme est cpp
. Un certain nombre de préprocesseurs émettent des informations complémentaires, telles que des informations sur les numéros de ligne, qui sont utilisées par les phases ultérieures de compilation pour générer des informations de débogage. Dans le cas où le préprocesseur est basé sur gcc, l'option -P supprime ces informations.
$ cpp -P cprog.c
Inclusion conditionnelle et modification de signature de fonction conditionnelle
Pour inclure conditionnellement un bloc de code, le préprocesseur a plusieurs directives (par exemple, #if
, #ifdef
, #else
, #endif
, etc.).
/* Defines a conditional `printf` macro, which only prints if `DEBUG`
* has been defined
*/
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif
Les opérateurs relationnels C normaux peuvent être utilisés pour la condition #if
#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif
Les directives #if
se comportent de la même façon if
instruction C if
, elles ne doivent contenir que des expressions constantes intégrales et aucune projection. Il prend en charge un opérateur unaire supplémentaire, defined( identifier )
, qui renvoie 1
si l’identifiant est défini, et 0
sinon.
#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif
Modification de la signature de la fonction conditionnelle
Dans la plupart des cas, une version d'une application devrait avoir le moins de charge possible. Toutefois, lors du test d'une version intermédiaire, des journaux supplémentaires et des informations sur les problèmes détectés peuvent être utiles.
Par exemple, supposons qu'il y ait une fonction SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
qui, lorsque vous souhaitez créer un test, générera un journal sur son utilisation. Cependant, cette fonction est utilisée à plusieurs endroits et il est souhaitable que lors de la génération du journal, une partie de l'information consiste à savoir d'où provient la fonction appelée.
Donc, en utilisant la compilation conditionnelle, vous pouvez avoir quelque chose comme ceci dans le fichier d'inclusion déclarant la fonction. Cela remplace la version standard de la fonction par une version de débogage de la fonction. Le préprocesseur est utilisé pour remplacer les appels à la fonction SerOpPluAllRead()
par des appels à la fonction SerOpPluAllRead_Debug()
avec deux arguments supplémentaires, le nom du fichier et le numéro de ligne où la fonction est utilisée.
La compilation conditionnelle est utilisée pour choisir de remplacer ou non la fonction standard par une version de débogage.
#if 0
// function declaration and prototype for our debug version of the function.
SHORT SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);
// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif
Cela vous permet de remplacer la version standard de la fonction SerOpPluAllRead()
par une version qui fournira le nom du fichier et le numéro de ligne dans le fichier où la fonction est appelée.
Il y a une considération importante: tout fichier utilisant cette fonction doit inclure le fichier d'en-tête où cette approche est utilisée pour que le préprocesseur puisse modifier la fonction. Sinon, vous verrez une erreur de l'éditeur de liens.
La définition de la fonction ressemblerait à ce qui suit. Qu'est-ce que cette source fait est de demander que le préprocesseur renomme la fonction SerOpPluAllRead()
pour être SerOpPluAllRead_Debug()
et de modifier la liste des arguments pour inclure deux arguments supplémentaires, un pointeur sur le nom du fichier où la fonction a été appelée et le numéro de ligne dans le fichier dans lequel la fonction est utilisée.
#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);
SHORT SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
int iLen = 0;
char xBuffer[256];
// only print the last 30 characters of the file name to shorten the logs.
iLen = strlen (aszFilePath);
if (iLen > 30) {
iLen = iLen - 30;
}
else {
iLen = 0;
}
sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
IssueDebugLog(xBuffer);
// now that we have issued the log, continue with standard processing.
return SerOpPluAllRead_Special(pPif, usLockHnd);
}
// our special replacement function name for when we are generating logs.
SHORT SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
if (STUB_SELF == SstReadAsMaster()) {
return OpPluAllRead(pPif, usLockHnd);
}
return OP_NOT_MASTER;
}
Inclusion du fichier source
Les utilisations les plus courantes des directives de prétraitement #include
sont les suivantes:
#include <stdio.h>
#include "myheader.h"
#include
remplace l'instruction par le contenu du fichier auquel il est fait référence. Les crochets d'angle (<>) font référence aux fichiers d'en-tête installés sur le système, tandis que les guillemets ("") sont destinés aux fichiers fournis par l'utilisateur.
Les macros elles-mêmes peuvent développer d'autres macros une fois, comme l'illustre cet exemple:
#if VERSION == 1
#define INCFILE "vers1.h"
#elif VERSION == 2
#define INCFILE "vers2.h"
/* and so on */
#else
#define INCFILE "versN.h"
#endif
/* ... */
#include INCFILE
Remplacement Macro
La forme la plus simple de remplacement de macro consiste à définir une manifest constant
, comme dans
#define ARRSIZE 100
int array[ARRSIZE];
Ceci définit une macro de type fonction qui multiplie une variable par 10
et stocke la nouvelle valeur:
#define TIMES10(A) ((A) *= 10)
double b = 34;
int c = 23;
TIMES10(b); // good: ((b) *= 10);
TIMES10(c); // good: ((c) *= 10);
TIMES10(5); // bad: ((5) *= 10);
Le remplacement est effectué avant toute autre interprétation du texte du programme. Dans le premier appel à TIMES10
le nom A
de la définition est remplacé par b
et le texte ainsi étendu est alors mis à la place de l'appel. Notez que cette définition de TIMES10
n’est pas équivalente à
#define TIMES10(A) ((A) = (A) * 10)
parce que cela pourrait évaluer le remplacement de A
, deux fois, ce qui peut avoir des effets secondaires indésirables.
Ce qui suit définit une macro de type fonction dont la valeur est le maximum de ses arguments. Il présente l'avantage de fonctionner pour tous les types compatibles d'arguments et de générer du code en ligne sans la surcharge des appels de fonctions. Il présente l'inconvénient d'évaluer une ou l'autre de ses arguments une seconde fois (y compris les effets secondaires) et de générer plus de code qu'une fonction si elle est invoquée plusieurs fois.
#define max(a, b) ((a) > (b) ? (a) : (b))
int maxVal = max(11, 43); /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */
/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j); /* i == 4 */
De ce fait, les macros qui évaluent leurs arguments plusieurs fois sont généralement évitées dans le code de production. Depuis C11, il y a la fonctionnalité _Generic
qui permet d'éviter de telles invocations multiples.
Les parenthèses abondantes dans les extensions de macro (à droite de la définition) garantissent que les arguments et l'expression résultante sont correctement liés et s'intègrent bien dans le contexte dans lequel la macro est appelée.
Directive d'erreur
Si le préprocesseur rencontre une directive #error
, la compilation est interrompue et le message de diagnostic inclus est imprimé.
#define DEBUG
#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif
int main(void) {
return 0;
}
Sortie possible:
$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"
#if 0 pour bloquer les sections de code
Si vous envisagez de supprimer des sections de code ou de les désactiver temporairement, vous pouvez les commenter avec un commentaire de bloc.
/* Block comment around whole function to keep it from getting used.
* What's even the purpose of this function?
int myUnusedFunction(void)
{
int i = 5;
return i;
}
*/
Cependant, si le code source que vous avez entouré d'un commentaire de bloc contient des commentaires de style bloc dans la source, la fin * / des commentaires de bloc existants peut rendre votre nouveau commentaire de bloc non valide et entraîner des problèmes de compilation.
/* Block comment around whole function to keep it from getting used.
* What's even the purpose of this function?
int myUnusedFunction(void)
{
int i = 5;
/* Return 5 */
return i;
}
*/
Dans l'exemple précédent, les deux dernières lignes de la fonction et le dernier '* /' sont vus par le compilateur, il serait donc compilé avec des erreurs. Une méthode plus sûre consiste à utiliser une directive #if 0
autour du code que vous souhaitez bloquer.
#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
* removed by the preprocessor. */
int myUnusedFunction(void)
{
int i = 5;
return i;
}
#endif
Un avantage est que lorsque vous voulez revenir en arrière et trouver le code, il est beaucoup plus facile de rechercher "# 0" que de rechercher tous vos commentaires.
Un autre avantage très important est que vous pouvez imbriquer du code avec #if 0
. Cela ne peut pas être fait avec des commentaires.
Une alternative à l'utilisation de #if 0
consiste à utiliser un nom qui ne sera pas #defined
mais qui expliquera davantage pourquoi le code est bloqué. Par exemple, s'il existe une fonction qui semble être du code mort inutile, vous pouvez utiliser #if defined(POSSIBLE_DEAD_CODE)
ou #if defined(FUTURE_CODE_REL_020201)
pour le code nécessaire une fois que d'autres fonctionnalités sont en place ou quelque chose de similaire. Ensuite, lors du retour en arrière pour supprimer ou activer cette source, ces sections de source sont faciles à trouver.
Coller de jeton
Le collage de jetons permet de coller deux arguments de macro. Par exemple, front##back
frontback
. Un exemple célèbre est l’en-tête <TCHAR.H>
de Win32. En standard C, on peut écrire L"string"
pour déclarer une chaîne de caractères large. Cependant, Windows API permet de convertir entre des chaînes de caractères larges et des chaînes de caractères étroites simplement par #define
UNICODE
. Pour implémenter les littéraux de chaîne, TCHAR.H
utilise cette
#ifdef UNICODE
#define TEXT(x) L##x
#endif
Chaque fois qu'un utilisateur écrit TEXT("hello, world")
et que UNICODE est défini, le préprocesseur C concatène L
et l'argument de la macro. L
concaténé avec "hello, world"
donne L"hello, world"
.
Macros prédéfinies
Une macro prédéfinie est une macro qui est déjà comprise par le préprocesseur C sans que le programme ait besoin de la définir. Les exemples comprennent
Macros prédéfinies obligatoires
-
__FILE__
, qui donne le nom du fichier source actuel (un littéral de chaîne), -
__LINE__
pour le numéro de ligne actuel (une constante entière), -
__DATE__
pour la date de compilation (un littéral de chaîne), -
__TIME__
pour l'heure de compilation (un littéral de chaîne).
Il existe également un identifiant prédéfini, __func__
(ISO / IEC 9899: 2011 §6.4.2.2), qui n'est pas une macro:
L'identifiant
__func__
doit être implicitement déclaré par le traducteur comme si, immédiatement après l'accolade d'ouverture de chaque définition de fonction, la déclaration:static const char __func__[] = "function-name";
est apparu, où nom-fonction est le nom de la fonction lexicale.
__FILE__
, __LINE__
et __func__
sont particulièrement utiles à des fins de débogage. Par exemple:
fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);
Les compilateurs antérieurs à la norme C99 peuvent prendre en __func__
ou non __func__
ou peuvent avoir une macro qui agit de la même manière, nommée différemment. Par exemple, gcc a utilisé __FUNCTION__
en mode C89.
Les macros ci-dessous permettent de demander des détails sur l'implémentation:
-
__STDC_VERSION__
La version du standard C implémentée. C'est un entier constant utilisant le formatyyyymmL
(la valeur201112L
pour C11, la valeur199901L
pour C99; elle n'a pas été définie pour C89 / C90) -
__STDC_HOSTED__
1
s'il s'agit d'une implémentation hébergée, sinon0
. -
__STDC__
Si1
, la mise en œuvre est conforme à la norme C.
Autres macros prédéfinies (non obligatoire)
ISO / IEC 9899: 2011 §6.10.9.2 Macros d'environnement:
__STDC_ISO_10646__
Une constante entière de la formeyyyymmL
(par exemple, 199712L). Si ce symbole est défini, chaque caractère de l'ensemble requis Unicode, lorsqu'il est stocké dans un objet de typewchar_t
, a la même valeur que l'identificateur abrégé de ce caractère. L'ensemble requis Unicode comprend tous les caractères définis par ISO / IEC 10646, ainsi que tous les amendements et corrigenda techniques, à compter de l'année et du mois spécifiés. Si un autre encodage est utilisé, la macro ne doit pas être définie et le codage réel utilisé est défini par la mise en œuvre.
__STDC_MB_MIGHT_NEQ_WC__
La constante entière 1, destinée à indiquer que, dans l'encodage dewchar_t
, un membre du jeu de caractères de base n'a pas besoin d'une valeur de code égale à sa valeur lorsqu'il est utilisé comme caractère unique dans une constante de caractère entier.
__STDC_UTF_16__
La constante entière 1, destinée à indiquer que les valeurs de typechar16_t
sontchar16_t
UTF-16. Si un autre encodage est utilisé, la macro ne doit pas être définie et le codage réel utilisé est défini par la mise en œuvre.
__STDC_UTF_32__
La constante entière 1, destinée à indiquer que les valeurs de typechar32_t
sontchar32_t
UTF-32. Si un autre encodage est utilisé, la macro ne doit pas être définie et le codage réel utilisé est défini par la mise en œuvre.
ISO / IEC 9899: 2011 §6.10.8.3 Macros à caractéristiques conditionnelles
__STDC_ANALYZABLE__
La constante entière 1, destinée à indiquer la conformité aux spécifications de l'annexe L (Analyse).__STDC_IEC_559__
La constante entière 1, destinée à indiquer la conformité aux spécifications de l'annexe F (arithmétique à virgule flottante CEI 60559).__STDC_IEC_559_COMPLEX__
La constante entière 1, destinée à indiquer le respect des spécifications de l'annexe G (arithmétique complexe compatible CEI 60559).__STDC_LIB_EXT1__
La constante entière201112L
, destinée à indiquer la prise en charge des extensions définies dans l'annexe K (interfaces de vérification des limites).__STDC_NO_ATOMICS__
La constante entière 1, destinée à indiquer que l'implémentation ne prend pas en charge les types atomiques (y compris le_Atomic
type_Atomic
) et l'en-tête<stdatomic.h>
.__STDC_NO_COMPLEX__
La constante entière 1, destinée à indiquer que l'implémentation ne prend pas en charge les types complexes ou l'en-tête<complex.h>
.__STDC_NO_THREADS__
La constante entière 1, destinée à indiquer que l'implémentation ne prend pas en charge l'en-tête<threads.h>
.__STDC_NO_VLA__
La constante entière 1, destinée à indiquer que l'implémentation ne prend pas en charge les tableaux de longueur variable ou les types modifiés de manière variable.
Header Inclure les gardes
Presque tous les fichiers d'en-tête doivent suivre l'idiome include guard :
my-header-file.h
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// Code body for header file
#endif
Cela garantit que lorsque vous #include "my-header-file.h"
à plusieurs endroits, vous n'obtenez pas de déclarations en double des fonctions, des variables, etc. Imaginez la hiérarchie de fichiers suivante:
en-tête-1.h
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
en-tête-2.h
#include "header-1.h"
int myFunction2(MyStruct *value);
principal c
#include "header-1.h"
#include "header-2.h"
int main() {
// do something
}
Ce code pose un grave problème: le contenu détaillé de MyStruct
est défini deux fois, ce qui n’est pas autorisé. Cela entraînerait une erreur de compilation qui peut être difficile à détecter, car un fichier d'en-tête en inclut un autre. Si vous l'avez fait avec des gardes d'en-tête:
en-tête-1.h
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
en-tête-2.h
#ifndef HEADER_2_H
#define HEADER_2_H
#include "header-1.h"
int myFunction2(MyStruct *value);
#endif
principal c
#include "header-1.h"
#include "header-2.h"
int main() {
// do something
}
Ce serait ensuite élargir à:
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
#ifndef HEADER_2_H
#define HEADER_2_H
#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
int myFunction2(MyStruct *value);
#endif
int main() {
// do something
}
Lorsque le compilateur atteint la seconde inclusion de header-1.h , HEADER_1_H
était déjà défini par l'inclusion précédente. Ergo, cela se résume à ce qui suit:
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#define HEADER_2_H
int myFunction2(MyStruct *value);
int main() {
// do something
}
Et donc il n'y a pas d'erreur de compilation.
Remarque: il existe plusieurs conventions différentes pour nommer les gardes d’en-tête. Certains utilisateurs aiment l' HEADER_2_H_
, certains incluent le nom du projet comme MY_PROJECT_HEADER_2_H
. L'important est de s'assurer que la convention que vous suivez fait en sorte que chaque fichier de votre projet ait une protection d'en-tête unique.
Si les détails de la structure n'étaient pas inclus dans l'en-tête, le type déclaré serait incomplet ou un type opaque . De tels types peuvent être utiles, en masquant les détails de la mise en œuvre aux utilisateurs des fonctions. À bien des égards, le type FILE
dans la bibliothèque C standard peut être considéré comme un type opaque (bien qu’il ne soit généralement pas opaque, de sorte que les implémentations macro des fonctions E / S standard puissent utiliser les éléments internes de la structure). Dans ce cas, l' header-1.h
pourrait contenir:
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct MyStruct MyStruct;
int myFunction(MyStruct *value);
#endif
Notez que la structure doit avoir un nom de balise (ici MyStruct
- qui se trouve dans l’espace de noms des balises, distinct de l’espace de noms des identificateurs ordinaires du nom de MyStruct
), et que le { … }
est omis. Cela dit "il existe un type de structure struct MyStruct
et il existe un alias pour MyStruct
".
Dans le fichier d'implémentation, les détails de la structure peuvent être définis pour que le type soit complet:
struct MyStruct {
…
};
Si vous utilisez C11, vous pouvez répéter typedef struct MyStruct MyStruct;
déclaration sans provoquer une erreur de compilation, mais les versions antérieures de C se plaignent. Par conséquent, il est toujours préférable d'utiliser l'idiome include guard, même si dans cet exemple, il serait facultatif que le code ne soit compilé qu'avec des compilateurs prenant en charge C11.
De nombreux compilateurs prennent en charge la directive #pragma once
, qui a les mêmes résultats:
my-header-file.h
#pragma once
// Code for header file
Cependant, #pragma once
ne fait pas partie du standard C, le code est moins portable si vous l'utilisez.
Quelques en-têtes n'utilisent pas l'idiome include guard. Un exemple spécifique est l'en-tête standard <assert.h>
. Il peut être inclus plusieurs fois dans une seule unité de traduction, ce qui dépend si la macro NDEBUG
est définie chaque fois que l'en-tête est inclus. Vous pouvez parfois avoir une exigence analogue; de tels cas seront rares. Normalement, vos en-têtes doivent être protégés par l'idiome de garde inclus.
Mise en œuvre de la prévention
Nous pouvons également utiliser des macros pour rendre le code plus facile à lire et à écrire. Par exemple, nous pouvons implémenter des macros pour implémenter la construction foreach
dans C pour certaines structures de données telles que les listes à liaisons simples et doubles, les files d'attente, etc.
Voici un petit exemple.
#include <stdio.h>
#include <stdlib.h>
struct LinkedListNode
{
int data;
struct LinkedListNode *next;
};
#define FOREACH_LIST(node, list) \
for (node=list; node; node=node->next)
/* Usage */
int main(void)
{
struct LinkedListNode *list, **plist = &list, *node;
int i;
for (i=0; i<10; i++)
{
*plist = malloc(sizeof(struct LinkedListNode));
(*plist)->data = i;
(*plist)->next = NULL;
plist = &(*plist)->next;
}
/* printing the elements here */
FOREACH_LIST(node, list)
{
printf("%d\n", node->data);
}
}
Vous pouvez créer une interface standard pour de telles structures de données et écrire une implémentation générique de FOREACH
comme FOREACH
:
#include <stdio.h>
#include <stdlib.h>
typedef struct CollectionItem_
{
int data;
struct CollectionItem_ *next;
} CollectionItem;
typedef struct Collection_
{
/* interface functions */
void* (*first)(void *coll);
void* (*last) (void *coll);
void* (*next) (void *coll, CollectionItem *currItem);
CollectionItem *collectionHead;
/* Other fields */
} Collection;
/* must implement */
void *first(void *coll)
{
return ((Collection*)coll)->collectionHead;
}
/* must implement */
void *last(void *coll)
{
return NULL;
}
/* must implement */
void *next(void *coll, CollectionItem *curr)
{
return curr->next;
}
CollectionItem *new_CollectionItem(int data)
{
CollectionItem *item = malloc(sizeof(CollectionItem));
item->data = data;
item->next = NULL;
return item;
}
void Add_Collection(Collection *coll, int data)
{
CollectionItem **item = &coll->collectionHead;
while(*item)
item = &(*item)->next;
(*item) = new_CollectionItem(data);
}
Collection *new_Collection()
{
Collection *nc = malloc(sizeof(Collection));
nc->first = first;
nc->last = last;
nc->next = next;
return nc;
}
/* generic implementation */
#define FOREACH(node, collection) \
for (node = (collection)->first(collection); \
node != (collection)->last(collection); \
node = (collection)->next(collection, node))
int main(void)
{
Collection *coll = new_Collection();
CollectionItem *node;
int i;
for(i=0; i<10; i++)
{
Add_Collection(coll, i);
}
/* printing the elements here */
FOREACH(node, coll)
{
printf("%d\n", node->data);
}
}
Pour utiliser cette implémentation générique, implémentez simplement ces fonctions pour votre structure de données.
1. void* (*first)(void *coll);
2. void* (*last) (void *coll);
3. void* (*next) (void *coll, CollectionItem *currItem);
__cplusplus pour utiliser des C externes dans du code C ++ compilé avec C ++
Il existe des moments où un fichier include doit générer une sortie différente du préprocesseur selon que le compilateur est un compilateur C ou un compilateur C ++ en raison de différences de langage.
Par exemple, une fonction ou un autre composant externe est défini dans un fichier source C mais est utilisé dans un fichier source C ++. Comme C ++ utilise la gestion des noms (ou la décoration des noms) pour générer des noms de fonctions uniques basés sur des types d'arguments de fonction, une déclaration de fonction C utilisée dans un fichier source C ++ provoquera des erreurs de liaison. Le compilateur C ++ modifiera le nom externe spécifié pour la sortie du compilateur en utilisant les règles de gestion de noms pour C ++. Le résultat est des erreurs de lien dues à des externes non trouvés lorsque la sortie du compilateur C ++ est liée à la sortie du compilateur C.
Comme les compilateurs C ne gèrent pas les noms mais que les compilateurs C ++ le font pour tous les libellés externes (noms de fonctions ou noms de variables) générés par le compilateur C ++, une macro de préprocesseur prédéfinie, __cplusplus
, a été introduite pour permettre la détection du compilateur.
Pour contourner ce problème de sortie de compilateur incompatible pour les noms externes entre C et C ++, la macro __cplusplus
est définie dans le préprocesseur C ++ et n'est pas définie dans le préprocesseur C. Ce nom de macro peut être utilisé avec la directive #ifdef
ou le #ifdef
#if
préprocesseur conditionnel avec l'opérateur defined()
pour indiquer si un code source ou un fichier d'inclusion est en cours de compilation en C ++ ou C.
#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif
Ou vous pourriez utiliser
#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif
Pour spécifier le nom de fonction correct d'une fonction à partir d'un fichier source C compilé avec le compilateur C utilisé dans un fichier source C ++, vous pouvez rechercher la constante __cplusplus
définie pour provoquer l' extern "C" { /* ... */ };
à utiliser pour déclarer des C externes lorsque le fichier d'en-tête est inclus dans un fichier source C ++. Cependant, lorsque compilé avec un compilateur C, le extern "C" { */ ... */ };
N'est pas utilisé. Cette compilation conditionnelle est nécessaire car extern "C" { /* ... */ };
est valide en C ++ mais pas en C.
#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif
// exported C function list.
int foo (void);
#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif
Macros de type fonction
Les macros de type fonction sont similaires aux fonctions inline
, elles sont utiles dans certains cas, comme le journal de débogage temporaire:
#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"
# define LOG(str) do { \
FILE *fp = fopen(LOGFILENAME, "a"); \
if (fp) { \
fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
/* don't print null pointer */ \
str ?str :"<null>"); \
fclose(fp); \
} \
else { \
perror("Opening '" LOGFILENAME "' failed"); \
} \
} while (0)
#else
/* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif
#include <stdio.h>
int main(int argc, char* argv[])
{
if (argc > 1)
LOG("There are command line arguments");
else
LOG("No command line arguments");
return 0;
}
Dans les deux cas (avec DEBUG
ou non), l'appel se comporte de la même manière qu'une fonction avec un type de retour void
. Cela garantit que les if/else
sont interprétées comme prévu.
Dans le cas de DEBUG
, ceci est implémenté via une construction do { ... } while(0)
. Dans l'autre cas, (void)0
est une déclaration sans effet secondaire qui est simplement ignorée.
Une alternative pour ce dernier serait
#define LOG(LINE) do { /* empty */ } while (0)
tel que dans tous les cas, il est syntaxiquement équivalent au premier.
Si vous utilisez GCC, vous pouvez également mettre en œuvre une macro de type fonction qui renvoie le résultat en utilisant une extension GNU non standard - expressions de déclaration . Par exemple:
#include <stdio.h>
#define POW(X, Y) \
({ \
int i, r = 1; \
for (i = 0; i < Y; ++i) \
r *= X; \
r; \ // returned value is result of last operation
})
int main(void)
{
int result;
result = POW(2, 3);
printf("Result: %d\n", result);
}
La macro des arguments Variadic
Macros avec des arguments variadic:
Disons que vous voulez créer une macro d'impression pour déboguer votre code, prenons cette macro comme exemple:
#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)
Quelques exemples d'utilisation:
La fonction somefunc()
renvoie -1 si elle échoue et 0 si elle réussit, et elle est appelée depuis de nombreux endroits différents du code:
int retVal = somefunc();
if(retVal == -1)
{
debug_printf("somefunc() has failed");
}
/* some other code */
retVal = somefunc();
if(retVal == -1)
{
debug_printf("somefunc() has failed");
}
Que se passe-t-il si l'implémentation de somefunc()
change, et renvoie maintenant des valeurs différentes correspondant à différents types d'erreur possibles? Vous souhaitez toujours utiliser la macro de débogage et imprimer la valeur d'erreur.
debug_printf(retVal); /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */
Pour résoudre ce problème, la macro __VA_ARGS__
été introduite. Cette macro permet plusieurs paramètres X-macro:
Exemple:
#define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)
Usage:
int retVal = somefunc();
debug_print("retVal of somefunc() is-> %d", retVal);
Cette macro vous permet de transmettre plusieurs paramètres et de les imprimer, mais cela vous interdit désormais d’envoyer des paramètres.
debug_print("Hey");
Cela soulèverait une erreur de syntaxe car la macro attend au moins un argument supplémentaire et le pré-processeur n'ignorerait pas le manque de virgule dans la macro debug_print()
. Aussi debug_print("Hey",);
générer une erreur de syntaxe car vous ne pouvez pas garder l'argument passé à la macro vide.
Pour résoudre ce problème, la macro ##__VA_ARGS__
été introduite, cette macro indique que si aucun argument variable n'existe, la virgule est supprimée par le pré-processeur du code.
Exemple:
#define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)
Usage:
debug_print("Ret val of somefunc()?");
debug_print("%d",somefunc());