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 format yyyymmL (la valeur 201112L pour C11, la valeur 199901L 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, sinon 0 .
  • __STDC__ Si 1 , 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 forme yyyymmL (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 type wchar_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 de wchar_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 type char16_t sont char16_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 type char32_t sont char32_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ère 201112L , 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

C99

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());


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow