Zoeken…


Invoering

Alle preprocessoropdrachten beginnen met het hekje (pond) # . AC-macro is slechts een preprocessoropdracht die wordt gedefinieerd met behulp van de #define preprocessorrichtlijn. Tijdens de voorbereidingsfase vervangt de C-preprocessor (een onderdeel van de C-compiler) eenvoudig de body van de macro, ongeacht waar de naam verschijnt.

Opmerkingen

Wanneer een compiler een macro in de code tegenkomt, voert deze een eenvoudige stringvervanging uit en worden er geen extra bewerkingen uitgevoerd. Hierdoor houden wijzigingen door de preprocessor geen rekening met het bereik van C-programma's - een macrodefinitie is bijvoorbeeld niet beperkt tot een blok en wordt dus niet beïnvloed door een '}' die een blokinstructie beëindigt.

De preprocessor is door het ontwerp niet volledig turing - er zijn verschillende soorten berekeningen die niet alleen door de preprocessor kunnen worden uitgevoerd.

Meestal hebben compilers een opdrachtregelvlag (of configuratie-instelling) waarmee we de compilatie na de voorbereidingsfase kunnen stoppen en het resultaat kunnen inspecteren. Op POSIX-platforms is deze vlag -E . Dus als gcc met deze vlag wordt uitgevoerd, wordt de uitgebreide bron afgedrukt naar stdout:

$ gcc -E cprog.c

Vaak wordt de preprocessor geïmplementeerd als een afzonderlijk programma, dat wordt aangeroepen door de compiler, de gemeenschappelijke naam voor dat programma is cpp . Een aantal preprocessors zendt ondersteunende informatie uit, zoals informatie over regelnummers - die wordt gebruikt door opeenvolgende fasen van compilatie om foutopsporingsinformatie te genereren. In het geval dat de preprocessor op gcc is gebaseerd, onderdrukt de optie -P dergelijke informatie.

$ cpp -P cprog.c

Voorwaardelijke opname en wijziging van de voorwaardelijke functiesignatuur

Om een codeblok voorwaardelijk op te nemen, heeft de preprocessor verschillende richtlijnen (bijv. #if , #ifdef , #else , #endif , enz.).

/* 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

Normale relationele C-operatoren kunnen worden gebruikt voor de #if voorwaarde

#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

De #if richtlijnen gedragen zich vergelijkbaar met de C if instructie, deze bevat alleen integrale constante expressies en geen casts. Het ondersteunt een extra unaire operator, defined( identifier ) , die 1 retourneert als de identifier is gedefinieerd en anders 0 .

#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Voorwaardelijke functie Handtekening wijziging

In de meeste gevallen zal een release-build van een applicatie naar verwachting zo weinig mogelijk overhead hebben. Tijdens het testen van een tussentijdse build kunnen echter extra logboeken en informatie over gevonden problemen nuttig zijn.

Neem bijvoorbeeld aan dat er een functie is SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) die bij het uitvoeren van een SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) gewenst is, een logboek over het gebruik ervan genereren. Deze functie wordt echter op meerdere plaatsen gebruikt en het is gewenst dat bij het genereren van het logboek, een deel van de informatie is om te weten waar de functie vandaan komt.

Met voorwaardelijke compilatie kunt u dus iets als het volgende in het include-bestand opnemen waarin de functie wordt aangegeven. Dit vervangt de standaardversie van de functie door een foutopsporingsversie van de functie. De preprocessor wordt gebruikt om aanroepen van de functie SerOpPluAllRead() te vervangen door aanroepen van de functie SerOpPluAllRead_Debug() door twee extra argumenten, de naam van het bestand en het regelnummer waar de functie wordt gebruikt.

Voorwaardelijke compilatie wordt gebruikt om te kiezen of de standaardfunctie wordt vervangen door een foutopsporingsversie of niet.

#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

Hiermee kunt u de standaardversie van de functie SerOpPluAllRead() door een versie die de naam van het bestand en het regelnummer geeft in het bestand waar de functie wordt genoemd.

Er is een belangrijke overweging: elk bestand dat deze functie gebruikt, moet het header-bestand bevatten waarin deze aanpak wordt gebruikt, zodat de preprocessor de functie kan wijzigen. Anders ziet u een linkerfout.

De definitie van de functie zou er ongeveer als volgt uitzien. Wat deze bron doet, is de preprocessor verzoeken de functie SerOpPluAllRead() te hernoemen naar SerOpPluAllRead_Debug() en de lijst met argumenten wijzigen om twee extra argumenten op te nemen, een pointer naar de naam van het bestand waar de functie werd genoemd en het regelnummer in het bestand waarin de functie wordt gebruikt.

#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;
}

Opname van bronbestand

De meest voorkomende toepassingen van #include preprocessing-richtlijnen zijn als volgt:

#include <stdio.h>
#include "myheader.h"

#include vervangt de instructie door de inhoud van het bestand waarnaar wordt verwezen. Haakjes (<>) verwijzen naar koptekstbestanden die op het systeem zijn geïnstalleerd, terwijl aanhalingstekens ("") voor door de gebruiker geleverde bestanden zijn.

Macro's zelf kunnen eenmaal andere macro's uitbreiden, zoals dit voorbeeld illustreert:

#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

Macro vervangen

De eenvoudigste vorm van macrovervanging is het definiëren van een manifest constant , zoals in

#define ARRSIZE 100
int array[ARRSIZE];

Dit definieert een functieachtige macro die een variabele met 10 vermenigvuldigt en de nieuwe waarde opslaat:

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

De vervanging vindt plaats vóór enige andere interpretatie van de programmatekst. In de eerste aanroep naar TIMES10 de naam A uit de definitie vervangen door b en wordt de zo uitgebreide tekst vervolgens in plaats van de aanroep geplaatst. Merk op dat deze definitie van TIMES10 niet equivalent is aan

#define TIMES10(A) ((A) = (A) * 10)

omdat dit de vervanging van A tweemaal kan evalueren, wat ongewenste bijwerkingen kan hebben.

Het volgende definieert een functieachtige macro waarvan de waarde het maximum is van zijn argumenten. Het heeft het voordeel dat het voor alle compatibele typen argumenten werkt en in-line code genereert zonder de overhead van functieaanroepen. Het heeft het nadeel dat het de ene of de andere van zijn argumenten een tweede keer evalueert (inclusief bijwerkingen) en meer code genereert dan een functie als het meerdere keren wordt aangeroepen.

#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 */

Daarom worden macro's die hun argumenten meerdere keren evalueren, meestal vermeden in productiecode. Sinds C11 is er de _Generic functie waarmee dergelijke meerdere invocaties kunnen worden vermeden.

De overvloedige haakjes in de macro-uitbreidingen (rechterkant van de definitie) zorgen ervoor dat de argumenten en de resulterende uitdrukking goed zijn gebonden en goed passen in de context waarin de macro wordt aangeroepen.

Foutrichtlijn

Als de preprocessor een #error instructie tegenkomt, wordt de compilatie gestopt en wordt het bijbehorende diagnostische bericht afgedrukt.

#define DEBUG

#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif

int main(void) {
    return 0;
}

Mogelijke output:

$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"

#if 0 om codesecties te blokkeren

Als er secties code zijn die u overweegt te verwijderen of die u tijdelijk wilt uitschakelen, kunt u dit met een blokopmerking becommentariëren.

/* 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;
}
*/

Als de broncode die u met een blokcommentaar hebt omgeven echter blokstijlopmerkingen in de bron bevat, kan het einde * / van de bestaande blokopmerkingen ervoor zorgen dat uw nieuwe blokopmerking ongeldig is en compilatieproblemen veroorzaken.

/* 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;
}
*/ 

In het vorige voorbeeld worden de laatste twee regels van de functie en de laatste '* /' gezien door de compiler, dus deze zou met fouten compileren. Een veiligere methode is om een #if 0 richtlijn te gebruiken rond de code die u wilt blokkeren.

#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

Een voordeel hiervan is dat wanneer u terug wilt gaan en de code wilt vinden, het veel gemakkelijker is om een zoekopdracht naar "#if 0" te doen dan al uw opmerkingen te doorzoeken.

Een ander zeer belangrijk voordeel is dat je commentaarcode kunt nesten met #if 0 . Dit kan niet worden gedaan met opmerkingen.

Een alternatief voor het gebruik van #if 0 is om een naam te gebruiken die niet #defined maar meer beschrijft waarom de code wordt geblokkeerd. Als er bijvoorbeeld een functie is die nutteloze dode code lijkt te zijn, kunt u #if defined(POSSIBLE_DEAD_CODE) #if defined(FUTURE_CODE_REL_020201) #if defined(POSSIBLE_DEAD_CODE) of #if defined(FUTURE_CODE_REL_020201) voor code die nodig is zodra andere functionaliteit aanwezig is of iets dergelijks. Wanneer u vervolgens teruggaat om die bron te verwijderen of in te schakelen, zijn die secties van de bron gemakkelijk te vinden.

Token plakken

Met Token Plakken kan men twee macro-argumenten aan elkaar lijmen. front##back levert bijvoorbeeld frontback . Een beroemd voorbeeld is de header <TCHAR.H> Win32. In standaard C kan men L"string" om een brede tekenreeks aan te geven. Met Windows API is het echter mogelijk om tussen brede tekenreeksen en smalle tekenreeksen te converteren, eenvoudig door #define ing UNICODE . TCHAR.H gebruikt dit om de TCHAR.H te implementeren

#ifdef UNICODE
#define TEXT(x) L##x
#endif

Wanneer een gebruiker TEXT("hello, world") schrijft en UNICODE is gedefinieerd, voegt de C-preprocessor L en het macroargument samen. L samengevoegd met "hello, world" geeft L"hello, world" .

Voorgedefinieerde macro's

Een vooraf gedefinieerde macro is een macro die al door de C-voorprocessor wordt begrepen zonder dat het programma deze hoeft te definiëren. Voorbeelden hiervan zijn

Verplichte vooraf gedefinieerde macro's

  • __FILE__ , die de bestandsnaam van het huidige bronbestand (een letterlijke tekenreeks) geeft,
  • __LINE__ voor het huidige regelnummer (een geheel getal constant),
  • __DATE__ voor de compilatiedatum (een letterlijke tekenreeks),
  • __TIME__ voor de compilatietijd (een letterlijke tekenreeks).

Er is ook een gerelateerde vooraf gedefinieerde identifier, __func__ (ISO / IEC 9899: 2011 §6.4.2.2), die geen macro is:

De identificatie __func__ wordt impliciet door de vertaler aangegeven alsof de verklaring onmiddellijk na het begin van elke functiedefinitie:

 static const char __func__[] = "function-name";

verscheen, waarbij functienaam de naam is van de lexic-enclosing function.

__FILE__ , __LINE__ en __func__ zijn vooral handig voor het opsporen van fouten. Bijvoorbeeld:

fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);

Pre-C99-compilers ondersteunen __func__ dan niet __func__ of kunnen een macro hebben die hetzelfde werkt en een andere naam heeft. Gcc gebruikte bijvoorbeeld __FUNCTION__ in de C89-modus.

Met de onderstaande macro's kunt u om details vragen over de implementatie:

  • __STDC_VERSION__ De __STDC_VERSION__ versie van de C-standaard. Dit is een constant geheel getal met de indeling yyyymmL (de waarde 201112L voor C11, de waarde 199901L voor C99; het was niet gedefinieerd voor C89 / C90)
  • __STDC_HOSTED__ 1 als het een gehoste implementatie is, anders 0 .
  • __STDC__ Als 1 , voldoet de implementatie aan de C-norm.

Andere vooraf gedefinieerde macro's (niet verplicht)

ISO / IEC 9899: 2011 §6.10.9.2 Omgevingsmacro's:

  • __STDC_ISO_10646__ Een geheel getal-constante van de vorm yyyymmL (bijvoorbeeld 199712L). Als dit symbool is gedefinieerd, heeft elk teken in de vereiste Unicode-set, wanneer opgeslagen in een object van het type wchar_t , dezelfde waarde als de korte id van dat teken. De vereiste Unicode-set bestaat uit alle tekens die zijn gedefinieerd door ISO / IEC 10646, samen met alle wijzigingen en technische rectificaties, vanaf het opgegeven jaar en de maand. Als een andere codering wordt gebruikt, wordt de macro niet gedefinieerd en wordt de daadwerkelijke codering geïmplementeerd.

  • __STDC_MB_MIGHT_NEQ_WC__ De gehele __STDC_MB_MIGHT_NEQ_WC__ 1, bedoeld om aan te geven dat een lid van de basistekenset in de codering voor wchar_t geen wchar_t hoeft te hebben die gelijk is aan zijn waarde wanneer deze wordt gebruikt als het enige teken in een constante van een geheel getal.

  • __STDC_UTF_16__ De gehele constante 1, bedoeld om aan te geven dat waarden van het type char16_t UTF-16 zijn gecodeerd. Als een andere codering wordt gebruikt, wordt de macro niet gedefinieerd en wordt de daadwerkelijke codering geïmplementeerd.

  • __STDC_UTF_32__ De gehele constante 1, bedoeld om aan te geven dat waarden van het type char32_t UTF-32 zijn gecodeerd. Als een andere codering wordt gebruikt, wordt de macro niet gedefinieerd en wordt de daadwerkelijke codering geïmplementeerd.

ISO / IEC 9899: 2011 §6.10.8.3 Macro's met voorwaardelijke kenmerken

  • __STDC_ANALYZABLE__ De gehele constante 1, bedoeld om conformiteit aan te geven aan de specificaties in bijlage L (Analyseerbaarheid).
  • __STDC_IEC_559__ De gehele constante 1, bedoeld om conformiteit aan te geven aan de specificaties in bijlage F (IEC 60559 rekenkundig zwevend punt).
  • __STDC_IEC_559_COMPLEX__ De gehele constante 1, bedoeld om aan te geven dat wordt voldaan aan de specificaties in bijlage G (IEC 60559 compatibele complexe rekenkunde).
  • __STDC_LIB_EXT1__ De gehele constante 201112L , bedoeld om ondersteuning aan te geven voor de extensies die zijn gedefinieerd in bijlage K (interfaces voor 201112L ).
  • __STDC_NO_ATOMICS__ De gehele constante 1, bedoeld om aan te geven dat de implementatie geen atomaire typen ondersteunt (inclusief de kwalificatie _Atomic type) en de kop <stdatomic.h> .
  • __STDC_NO_COMPLEX__ De gehele constante 1, bedoeld om aan te geven dat de implementatie geen complexe typen of de <complex.h> -kop ondersteunt.
  • __STDC_NO_THREADS__ De gehele constante 1, bedoeld om aan te geven dat de implementatie de <threads.h> header niet ondersteunt.
  • __STDC_NO_VLA__ De integer-constante 1, bedoeld om aan te geven dat de implementatie geen arrays met variabele lengte of variabel gemodificeerde typen ondersteunt.

Koptekst Inclusief bewakers

Vrijwel elk header-bestand moet het include-beveiligingsidioom volgen:

my-header-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Dit zorgt ervoor dat wanneer u #include "my-header-file.h" op meerdere plaatsen #include "my-header-file.h" , u geen dubbele declaraties van functies, variabelen, etc. krijgt. Stel u de volgende hiërarchie van bestanden voor:

header-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

header-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Deze code heeft een serieus probleem: de gedetailleerde inhoud van MyStruct wordt twee keer gedefinieerd, wat niet is toegestaan. Dit zou resulteren in een compilatiefout die moeilijk op te sporen is, omdat het ene headerbestand een ander bevat. Als je het in plaats daarvan met kopbeschermers deed:

header-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

header-2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Dit zou zich dan uitbreiden naar:

#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
}

Toen de compiler de tweede opname van header- HEADER_1_H was HEADER_1_H al gedefinieerd door de vorige opname. Ergo, het komt neer op het volgende:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

En dus is er geen compilatiefout.

Opmerking: er zijn meerdere verschillende conventies voor het benoemen van de kopbeschermers. Sommige mensen noemen het HEADER_2_H_ , anderen bevatten de projectnaam zoals MY_PROJECT_HEADER_2_H . Het belangrijkste is om ervoor te zorgen dat de conventie die u volgt ervoor zorgt dat elk bestand in uw project een unieke headerbewaking heeft.


Als de structuurdetails niet in de koptekst zouden worden opgenomen, zou het aangegeven type onvolledig of ondoorzichtig zijn . Dergelijke typen kunnen nuttig zijn, waardoor implementatiedetails verborgen blijven voor gebruikers van de functies. Voor vele doeleinden kan het FILE type in de standaard C-bibliotheek worden beschouwd als een ondoorzichtig type (hoewel het meestal niet ondoorzichtig is, zodat macro-implementaties van de standaard I / O-functies gebruik kunnen maken van de interne onderdelen van de structuur). In dat geval kan de header-1.h bevatten:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Merk op dat de structuur een tagnaam moet hebben (hier MyStruct - dat staat in de tags-naamruimte, gescheiden van de gewone ID-naamruimte van de typedef-naam MyStruct ), en dat de { … } is weggelaten. Dit zegt "er is een structuurtype struct MyStruct en er is een alias voor MyStruct ".

In het implementatiebestand kunnen de details van de structuur worden gedefinieerd om het type compleet te maken:

struct MyStruct {
    …
};

Als u C11 gebruikt, kunt u de typedef struct MyStruct MyStruct; herhalen typedef struct MyStruct MyStruct; verklaring zonder een compilatiefout te veroorzaken, maar eerdere versies van C zouden klagen. Daarom is het nog steeds het beste om het include-beveiligingsidioom te gebruiken, hoewel het in dit voorbeeld optioneel zou zijn als de code alleen ooit zou worden gecompileerd met compilers die C11 ondersteunen.


Veel compilers ondersteunen de #pragma once richtlijn, die dezelfde resultaten heeft:

my-header-file.h

#pragma once

// Code for header file

#pragma once geen deel uit van de C-standaard, dus de code is minder draagbaar als u deze gebruikt.


Een paar headers gebruiken niet het id van de bewaker. Een specifiek voorbeeld is de standaard <assert.h> header. Het kan meerdere keren worden opgenomen in een enkele vertaaleenheid, en het effect hiervan hangt af van het feit of de macro NDEBUG wordt gedefinieerd telkens wanneer de kop wordt opgenomen. Mogelijk hebt u af en toe een analoge eis; dergelijke gevallen zullen weinigen en ver tussen zijn. Gewoonlijk moeten uw kopteksten worden beschermd door het id van de bewaker opnemen.

VOOR ELKE implementatie

We kunnen ook macro's gebruiken om code gemakkelijker te kunnen lezen en schrijven. We kunnen bijvoorbeeld macro's implementeren voor het implementeren van het foreach construct in C voor sommige datastructuren zoals enkel- en dubbel gekoppelde lijsten, wachtrijen, etc.

Hier is een klein voorbeeld.

#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);
    }
}

U kunt een standaardinterface maken voor dergelijke gegevensstructuren en een generieke implementatie van FOREACH als:

#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);
    }
}

Om deze generieke implementatie te gebruiken, implementeert u deze functies gewoon voor uw gegevensstructuur.

1.  void* (*first)(void *coll);
2.  void* (*last) (void *coll);
3.  void* (*next) (void *coll, CollectionItem *currItem);

__cplusplus voor het gebruik van C-externe apparaten in C ++ code gecompileerd met C ++ - naammenging

Er zijn momenten waarop een include-bestand verschillende uitvoer van de preprocessor moet genereren, afhankelijk van of de compiler een C-compiler of een C ++ -compiler is vanwege taalverschillen.

Een functie of een andere externe is bijvoorbeeld gedefinieerd in een C-bronbestand, maar wordt gebruikt in een C ++ -bronbestand. Omdat C ++ naammenging (of naamdecoratie) gebruikt om unieke functienamen te genereren op basis van functieargumenttypen, zal een C-functieverklaring die in een C ++ -bronbestand wordt gebruikt, linkfouten veroorzaken. De C ++ compiler zal de gespecificeerde externe naam voor de compileruitvoer wijzigen met behulp van de regels voor naammenging voor C ++. Het resultaat is koppelingsfouten als gevolg van externe apparaten die niet worden gevonden wanneer de uitvoer van de C ++ compiler is gekoppeld aan de uitvoer van de C compiler.

Aangezien C-compilers geen namen verwerken, maar C ++ -compilers wel voor alle externe labels (functienamen of variabelenamen) die door de C ++ -compiler worden gegenereerd, is een vooraf gedefinieerde preprocessormacro __cplusplus geïntroduceerd om compilerdetectie mogelijk te maken.

Om dit probleem van incompatibele compileruitvoer voor externe namen tussen C en C ++ te __cplusplus , wordt de macro __cplusplus gedefinieerd in de C ++ Preprocessor en niet in de C Preprocessor. Deze #ifdef kan worden gebruikt met de voorwaardelijke preprocessor #ifdef instructie of #if met de defined() operator om te bepalen of een broncode of een bestand wordt gecompileerd als C ++ of C.

#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif

Of je zou kunnen gebruiken

#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif

Om de juiste functienaam op te geven van een functie uit een C-bronbestand dat is gecompileerd met de C-compiler die wordt gebruikt in een C ++ __cplusplus kunt u controleren op de __cplusplus gedefinieerde constante om de extern "C" { /* ... */ }; te gebruiken om C-externals te declareren wanneer het header-bestand is opgenomen in een C ++ bronbestand. Echter, wanneer gecompileerd met een C-compiler, de extern "C" { */ ... */ }; het is niet gebruikt. Deze voorwaardelijke compilatie is nodig omdat extern "C" { /* ... */ }; is geldig in C ++ maar niet in 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

Functie-achtige macro's

Functieachtige macro's zijn vergelijkbaar met inline functies, deze zijn in sommige gevallen handig, zoals een tijdelijk foutopsporingslogboek:

#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;
}

Hier gedraagt de oproep zich in beide gevallen (met DEBUG of niet) op dezelfde manier als een functie met een void retourtype. Dit zorgt ervoor dat de if/else voorwaarden worden geïnterpreteerd zoals verwacht.

In het DEBUG geval wordt dit geïmplementeerd via een do { ... } while(0) -construct. In het andere geval is (void)0 een verklaring zonder bijwerking die alleen wordt genegeerd.

Een alternatief voor dit laatste zou zijn

#define LOG(LINE) do { /* empty */ } while (0)

zodat het in alle gevallen syntactisch equivalent is aan de eerste.

Als u GCC gebruikt, kunt u ook een functieachtige macro implementeren die resultaat retourneert met een niet-standaard GNU-extensie - statement-expressies . Bijvoorbeeld:

#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);
}

Variadische argumenten macro

C99

Macro's met variadische args:

Stel dat u een afdrukmacro wilt maken voor het debuggen van uw code, laten we deze macro als voorbeeld nemen:

#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)

Enkele voorbeelden van gebruik:

De functie somefunc() retourneert -1 als het mislukt is en 0 als het is gelukt, en het wordt op verschillende plaatsen in de code aangeroepen:

int retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

/* some other code */

 retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

Wat gebeurt er als de implementatie van somefunc() verandert en nu verschillende waarden retourneert die overeenkomen met verschillende mogelijke fouttypen? U wilt nog steeds de foutopsporingsmacro gebruiken en de foutwaarde afdrukken.

debug_printf(retVal);      /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */

Om dit probleem op te lossen werd de __VA_ARGS__ macro geïntroduceerd. Deze macro laat meerdere parameters X-macro's toe:

Voorbeeld:

 #define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
                               printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)

Gebruik:

int retVal = somefunc();

debug_print("retVal of somefunc() is-> %d", retVal);

Met deze macro kunt u meerdere parameters doorgeven en afdrukken, maar nu is het u helemaal verboden om parameters te verzenden.

debug_print("Hey");

Dit zou een syntaxisfout veroorzaken, omdat de macro ten minste nog een argument verwacht en de pre-processor het gebrek aan komma's in de macro debug_print() niet zou negeren. Ook debug_print("Hey",); zou een syntaxisfout veroorzaken omdat u het doorgegeven argument niet leeg kunt houden.

Om dit op te lossen, werd ##__VA_ARGS__ macro geïntroduceerd, deze macro geeft aan dat als er geen variabele argumenten bestaan, de komma door de pre-processor uit de code wordt verwijderd.

Voorbeeld:

 #define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
                               printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)

Gebruik:

 debug_print("Ret val of somefunc()?");
 debug_print("%d",somefunc());


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow