C Language
Preprocessor en macro's
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 indelingyyyymmL
(de waarde201112L
voor C11, de waarde199901L
voor C99; het was niet gedefinieerd voor C89 / C90) -
__STDC_HOSTED__
1
als het een gehoste implementatie is, anders0
. -
__STDC__
Als1
, 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 vormyyyymmL
(bijvoorbeeld 199712L). Als dit symbool is gedefinieerd, heeft elk teken in de vereiste Unicode-set, wanneer opgeslagen in een object van het typewchar_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 voorwchar_t
geenwchar_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 typechar16_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 typechar32_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 constante201112L
, bedoeld om ondersteuning aan te geven voor de extensies die zijn gedefinieerd in bijlage K (interfaces voor201112L
).__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
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());