C++
preprocessor
Zoeken…
Invoering
De C-preprocessor is een eenvoudige tekstparser / -vervanger die wordt uitgevoerd voordat de code daadwerkelijk wordt gecompileerd. Gebruikt om het gebruik van de taal C (en later C ++) uit te breiden en te vergemakkelijken, kan het worden gebruikt voor:
een. Andere bestanden opnemen met #include
b. Definieer een tekstvervangende macro met #define
c. Voorwaardelijke compilatie met #if
#ifdef
d. Platform / Compiler-specifieke logica (als uitbreiding van voorwaardelijke compilatie)
Opmerkingen
Preprocessor-instructies worden uitgevoerd voordat uw bronbestanden aan de compiler worden overhandigd. Ze zijn in staat tot voorwaardelijke logica op zeer laag niveau. Aangezien preprocessorconstructies (bijv. Objectachtige macro's) niet worden getypt zoals normale functies (de preprocessing-stap gebeurt vóór compilatie) kan de compiler typecontroles niet afdwingen, daarom moeten ze zorgvuldig worden gebruikt.
Inclusief bewakers
Een header-bestand kan worden opgenomen door andere header-bestanden. Een bronbestand (compilatie-eenheid) dat meerdere headers bevat, kan daarom indirect sommige headers meer dan eens bevatten. Als een dergelijk headerbestand dat meer dan eens is opgenomen definities bevat, detecteert de compiler (na voorverwerking) een overtreding van de One Definition Rule (bijv. §3.2 van de C ++ standaard van 2003) en geeft daarom een diagnose en compilatie mislukt.
Meervoudige opname wordt voorkomen met behulp van "include-bewakers", die soms ook wel header-bewakers of macro-bewakers worden genoemd. Deze worden geïmplementeerd met de preprocessor #define
, #ifndef
, #endif
richtlijnen.
bv
// Foo.h
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
class Foo // a class definition
{
};
#endif
Het belangrijkste voordeel van het gebruik van onder meer afschermingen is dat ze met alle standaard compilante compilers en preprocessors werken.
Beveiligers omvatten echter ook enkele problemen voor ontwikkelaars, omdat het noodzakelijk is om ervoor te zorgen dat de macro's uniek zijn in alle headers die in een project worden gebruikt. In het bijzonder, als twee (of meer) headers FOO_H_INCLUDED
als hun bewaker, zal de eerste van die headers in een compilatie-eenheid effectief voorkomen dat de anderen worden opgenomen. Bijzondere uitdagingen worden geïntroduceerd als een project een aantal externe bibliotheken gebruikt met headerbestanden die toevallig worden gebruikt, met inbegrip van gemeenschappelijke bewakers.
Het is ook noodzakelijk om ervoor te zorgen dat de macro's die worden gebruikt in bewakers niet conflicteren met andere macro's die zijn gedefinieerd in header-bestanden.
De meeste C ++ -implementaties ondersteunen ook de #pragma once
richtlijn, die ervoor zorgt dat het bestand slechts eenmaal in een enkele compilatie wordt opgenomen. Dit is een de facto standaardrichtlijn , maar maakt geen deel uit van een ISO C ++ -standaard. Bijvoorbeeld:
// Foo.h
#pragma once
class Foo
{
};
Terwijl #pragma once
een aantal problemen vermijdt die verband houden met onder meer bewakers, is een #pragma
- per definitie in de normen - inherent een compilatorspecifieke hook, en wordt geruisloos genegeerd door compilers die het niet ondersteunen. Projecten die #pragma once
zijn moeilijker te porteren naar compilers die dit niet ondersteunen.
Een aantal coderingsrichtlijnen en betrouwbaarheidsstandaarden voor C ++ ontmoedigt specifiek elk gebruik van de preprocessor anders dan om #include
headerbestanden op te nemen of voor het plaatsen van bewakers in headers.
Voorwaardelijke logica en platformoverschrijdende afhandeling
Kort gezegd gaat voorwaardelijke voorbewerkingslogica over het beschikbaar stellen of niet beschikbaar stellen van codelogica voor compilatie met macrodefinities.
Drie prominente use-cases zijn:
- verschillende app-profielen (bijv. debug, release, testen, geoptimaliseerd) die kandidaten van dezelfde app kunnen zijn (bijv. met extra logboekregistratie).
- platformoverschrijdende compilaties - enkele codebase, meerdere compilatieplatforms.
- gebruik van een gemeenschappelijke codebasis voor meerdere applicatieversies (bijv. Basic-, Premium- en Pro-versies van een software) - met iets andere functies.
Voorbeeld a: een platformonafhankelijke aanpak voor het verwijderen van bestanden (illustratief):
#ifdef _WIN32
#include <windows.h> // and other windows system files
#endif
#include <cstdio>
bool remove_file(const std::string &path)
{
#ifdef _WIN32
return DeleteFile(path.c_str());
#elif defined(_POSIX_VERSION) || defined(__unix__)
return (0 == remove(path.c_str()));
#elif defined(__APPLE__)
//TODO: check if NSAPI has a more specific function with permission dialog
return (0 == remove(path.c_str()));
#else
#error "This platform is not supported"
#endif
}
Macro's zoals _WIN32
, __APPLE__
of __unix__
worden normaal gesproken vooraf bepaald door overeenkomstige implementaties.
Voorbeeld b: Extra logboekregistratie inschakelen voor een debug-build:
void s_PrintAppStateOnUserPrompt()
{
std::cout << "--------BEGIN-DUMP---------------\n"
<< AppState::Instance()->Settings().ToString() << "\n"
#if ( 1 == TESTING_MODE ) //privacy: we want user details only when testing
<< ListToString(AppState::UndoStack()->GetActionNames())
<< AppState::Instance()->CrntDocument().Name()
<< AppState::Instance()->CrntDocument().SignatureSHA() << "\n"
#endif
<< "--------END-DUMP---------------\n"
}
Voorbeeld c: schakel een premium-functie in een afzonderlijke productopbouw in (opmerking: dit is illustratief. Het is vaak een beter idee om een functie te ontgrendelen zonder een toepassing opnieuw te hoeven installeren)
void MainWindow::OnProcessButtonClick()
{
#ifndef _PREMIUM
CreatePurchaseDialog("Buy App Premium", "This feature is available for our App Premium users. Click the Buy button to purchase the Premium version at our website");
return;
#endif
//...actual feature logic here
}
Enkele veel voorkomende trucs:
Symbolen definiëren op het moment van aanroep:
De preprocessor kan worden opgeroepen met vooraf gedefinieerde symbolen (met optionele initialisatie). Bijvoorbeeld deze opdracht ( gcc -E
voert alleen de preprocessor uit)
gcc -E -DOPTIMISE_FOR_OS_X -DTESTING_MODE=1 Sample.cpp
verwerkt Sample.cpp op dezelfde manier als wanneer #define OPTIMISE_FOR_OS_X
en #define TESTING_MODE 1
aan de bovenkant van Sample.cpp werden toegevoegd.
Ervoor zorgen dat een macro is gedefinieerd:
Als een macro niet is gedefinieerd en de waarde ervan wordt vergeleken of gecontroleerd, neemt de preprocessor vrijwel altijd geruisloos aan dat de waarde 0
. Er zijn een paar manieren om hiermee te werken. Een benadering is om aan te nemen dat de standaardinstellingen worden weergegeven als 0 en dat eventuele wijzigingen (bijv. In het app-buildprofiel) expliciet moeten worden gedaan (bijv. ENABLE_EXTRA_DEBUGGING = 0 standaard, stel -DENABLE_EXTRA_DEBUGGING = 1 in om te negeren). Een andere benadering is om alle definities en standaards expliciet te maken. Dit kan worden bereikt met een combinatie van #ifndef
en #error
richtlijnen:
#ifndef (ENABLE_EXTRA_DEBUGGING)
// please include DefaultDefines.h if not already included.
# error "ENABLE_EXTRA_DEBUGGING is not defined"
#else
# if ( 1 == ENABLE_EXTRA_DEBUGGING )
//code
# endif
#endif
macro's
Macro's zijn onderverdeeld in twee hoofdgroepen: objectachtige macro's en functieachtige macro's. Macro's worden vroeg in het compilatieproces als een tokensubstitutie behandeld. Dit betekent dat grote (of herhalende) secties code kunnen worden geabstraheerd in een preprocessor-macro.
// This is an object-like macro
#define PI 3.14159265358979
// This is a function-like macro.
// Note that we can use previously defined macros
// in other macro definitions (object-like or function-like)
// But watch out, its quite useful if you know what you're doing, but the
// Compiler doesnt know which type to handle, so using inline functions instead
// is quite recommended (But e.g. for Minimum/Maximum functions it is quite useful)
#define AREA(r) (PI*(r)*(r))
// They can be used like this:
double pi_macro = PI;
double area_macro = AREA(4.6);
De Qt-bibliotheek maakt gebruik van deze techniek om een meta-objectsysteem te maken door de gebruiker de Q_OBJECT-macro aan het hoofd van de door de gebruiker gedefinieerde klasse die QObject uitbreidt, te laten verklaren.
Macronamen worden meestal in hoofdletters geschreven, zodat ze gemakkelijker van normale code kunnen worden onderscheiden. Dit is geen vereiste, maar wordt door veel programmeurs als een goede stijl beschouwd.
Wanneer een objectachtige macro wordt aangetroffen, wordt deze uitgebreid als een eenvoudige kopieer- en plakbewerking, waarbij de naam van de macro wordt vervangen door de definitie ervan. Wanneer een functieachtige macro wordt aangetroffen, worden zowel de naam als de parameters uitgebreid.
double pi_squared = PI * PI;
// Compiler sees:
double pi_squared = 3.14159265358979 * 3.14159265358979;
double area = AREA(5);
// Compiler sees:
double area = (3.14159265358979*(5)*(5))
Hierdoor worden functieachtige macroparameters vaak ingesloten tussen haakjes, zoals in AREA()
hierboven. Dit om bugs te voorkomen die kunnen optreden tijdens macro-uitbreiding, met name bugs die worden veroorzaakt door een enkele macro-parameter die uit meerdere werkelijke waarden bestaat.
#define BAD_AREA(r) PI * r * r
double bad_area = BAD_AREA(5 + 1.6);
// Compiler sees:
double bad_area = 3.14159265358979 * 5 + 1.6 * 5 + 1.6;
double good_area = AREA(5 + 1.6);
// Compiler sees:
double good_area = (3.14159265358979*(5 + 1.6)*(5 + 1.6));
Merk ook op dat vanwege deze eenvoudige uitbreiding voorzichtig moet worden omgegaan met de parameters die aan macro's worden doorgegeven, om onverwachte bijwerkingen te voorkomen. Als de parameter tijdens de evaluatie wordt gewijzigd, wordt deze telkens gewijzigd wanneer deze in de uitgebreide macro wordt gebruikt, wat meestal niet is wat we willen. Dit is zelfs het geval als de macro de parameters tussen haakjes plaatst om te voorkomen dat expansie iets breekt.
int oops = 5;
double incremental_damage = AREA(oops++);
// Compiler sees:
double incremental_damage = (3.14159265358979*(oops++)*(oops++));
Bovendien bieden macro's geen typeveiligheid, wat leidt tot moeilijk te begrijpen fouten bij typemismatch.
Aangezien programmeurs normaal gesproken lijnen met een puntkomma beëindigen, zijn macro's die bedoeld zijn om als zelfstandige lijnen te worden gebruikt, vaak ontworpen om een puntkomma te "slikken"; dit voorkomt dat onbedoelde bugs worden veroorzaakt door een extra puntkomma.
#define IF_BREAKER(Func) Func();
if (some_condition)
// Oops.
IF_BREAKER(some_func);
else
std::cout << "I am accidentally an orphan." << std::endl;
In dit voorbeeld is de onbedoelde dubbele puntkomma breekt de if...else
blok, het voorkomen van de compiler van de aanpassing van de else
op de if
. Om dit te voorkomen, wordt de puntkomma weggelaten uit de macrodefinitie, waardoor de puntkomma onmiddellijk na gebruik ervan wordt 'ingeslikt'.
#define IF_FIXER(Func) Func()
if (some_condition)
IF_FIXER(some_func);
else
std::cout << "Hooray! I work again!" << std::endl;
Als u de puntkomma weglaat, kan de macro ook worden gebruikt zonder de huidige instructie te beëindigen, wat gunstig kan zijn.
#define DO_SOMETHING(Func, Param) Func(Param, 2)
// ...
some_function(DO_SOMETHING(some_func, 3), DO_SOMETHING(some_func, 42));
Normaal eindigt een macrodefinitie aan het einde van de regel. Als een macro meerdere regels moet beslaan, kan een backslash aan het einde van een regel worden gebruikt om dit aan te geven. Deze backslash moet het laatste teken in de regel zijn, wat de preprocessor aangeeft dat de volgende regel moet worden samengevoegd op de huidige regel en deze als één regel moet behandelen. Dit kan meerdere keren achter elkaar worden gebruikt.
#define TEXT "I \
am \
many \
lines."
// ...
std::cout << TEXT << std::endl; // Output: I am many lines.
Dit is vooral handig in complexe functieachtige macro's, die mogelijk meerdere regels moeten dekken.
#define CREATE_OUTPUT_AND_DELETE(Str) \
std::string* tmp = new std::string(Str); \
std::cout << *tmp << std::endl; \
delete tmp;
// ...
CREATE_OUTPUT_AND_DELETE("There's no real need for this to use 'new'.")
In het geval van complexere functieachtige macro's kan het nuttig zijn om ze hun eigen bereik te geven om mogelijke naambotsingen te voorkomen of om objecten te vernietigen aan het einde van de macro, vergelijkbaar met een werkelijke functie. Een gebruikelijk idioom hiervoor is do while 0 , waarbij de macro is ingesloten in een do-while- blok. Dit blok wordt meestal niet gevolgd door een puntkomma, waardoor het een puntkomma kan inslikken.
#define DO_STUFF(Type, Param, ReturnVar) do { \
Type temp(some_setup_values); \
ReturnVar = temp.process(Param); \
} while (0)
int x;
DO_STUFF(MyClass, 41153.7, x);
// Compiler sees:
int x;
do {
MyClass temp(some_setup_values);
x = temp.process(41153.7);
} while (0);
Er zijn ook variadische macro's; net als variadische functies, hebben deze een variabel aantal argumenten en breiden ze vervolgens allemaal uit in plaats van een speciale "Varargs" -parameter, __VA_ARGS__
.
#define VARIADIC(Param, ...) Param(__VA_ARGS__)
VARIADIC(printf, "%d", 8);
// Compiler sees:
printf("%d", 8);
Merk op dat tijdens de uitbreiding __VA_ARGS__
overal in de definitie kan worden geplaatst en correct zal worden uitgebreid.
#define VARIADIC2(POne, PTwo, PThree, ...) POne(PThree, __VA_ARGS__, PTwo)
VARIADIC2(some_func, 3, 8, 6, 9);
// Compiler sees:
some_func(8, 6, 9, 3);
In het geval van een variadische parameter met nulargumenten, zullen verschillende compilers de volgkomma anders behandelen. Sommige compilers, zoals Visual Studio, slingeren de komma zonder enige speciale syntaxis. Voor andere compilers, zoals GCC, moet u ##
onmiddellijk vóór __VA_ARGS__
. Daarom is het verstandig om variadisch macro's voorwaardelijk te definiëren wanneer draagbaarheid een probleem is.
// In this example, COMPILER is a user-defined macro specifying the compiler being used.
#if COMPILER == "VS"
#define VARIADIC3(Name, Param, ...) Name(Param, __VA_ARGS__)
#elif COMPILER == "GCC"
#define VARIADIC3(Name, Param, ...) Name(Param, ##__VA_ARGS__)
#endif /* COMPILER */
Preprocessor foutmeldingen
Compileerfouten kunnen worden gegenereerd met behulp van de preprocessor. Dit is handig om een aantal redenen, waaronder een melding aan een gebruiker als deze zich op een niet-ondersteund platform of een niet-ondersteunde compiler bevindt.
bijv. Return Error als gcc-versie 3.0.0 of lager is.
#if __GNUC__ < 3
#error "This code requires gcc > 3.0.0"
#endif
bijv. Return Error als compileren op een Apple-computer.
#ifdef __APPLE__
#error "Apple products are not supported in this release"
#endif
Voorgedefinieerde macro's
Vooraf gedefinieerde macro's zijn die welke de compiler definieert (in tegenstelling tot die door de gebruiker in het bronbestand). Deze macro's mogen niet opnieuw worden gedefinieerd of niet worden gedefinieerd door de gebruiker.
De volgende macro's worden vooraf gedefinieerd door de C ++ standaard:
-
__LINE__
bevat het regelnummer van de regel waarop deze macro wordt gebruikt en kan worden gewijzigd door de#line
richtlijn. -
__FILE__
bevat de bestandsnaam van het bestand waarin deze macro wordt gebruikt en kan worden gewijzigd door de#line
richtlijn. -
__DATE__
bevat de datum (in de indeling"Mmm dd yyyy"
) van de bestandscompilatie, waarbij Mmm is opgemaakt alsof deze is verkregen door een aanroep vanstd::asctime()
. -
__TIME__
bevat de tijd (in de indeling"hh:mm:ss"
) van de bestandscompilatie. -
__cplusplus
wordt gedefinieerd door (conforme) C ++ compilers tijdens het compileren van C ++ bestanden. De waarde is de standaardversie waarmee de compiler volledig voldoet, dwz199711L
voor C ++ 98 en C ++ 03,201103L
voor C ++ 11 en201402L
voor C ++ 14 standaard.
-
__STDC_HOSTED__
is gedefinieerd als1
als de implementatie wordt gehost , of0
als deze vrijstaand is .
-
__STDCPP_DEFAULT_NEW_ALIGNMENT__
bevat een letterlijkesize_t
, de uitlijning die wordt gebruikt voor eenoperator new
voor oproep tot uitlijning.
Bovendien mogen de volgende macro's vooraf worden bepaald door implementaties en kunnen ze al dan niet aanwezig zijn:
-
__STDC__
heeft een implementatie-afhankelijke betekenis en wordt meestal alleen gedefinieerd bij het compileren van een bestand als C, om volledige C-standaardcompliance aan te duiden. (Of nooit, als de compiler besluit deze macro niet te ondersteunen.)
-
__STDC_VERSION__
heeft een implementatie-afhankelijke betekenis en de waarde ervan is meestal de C-versie, net zoals__cplusplus
de C ++ -versie is. (Of wordt niet eens gedefinieerd, als de compiler besluit deze macro niet te ondersteunen.) -
__STDC_MB_MIGHT_NEQ_WC__
is gedefinieerd als1
, als waarden van de smalle codering van de basistekenset mogelijk niet gelijk zijn aan de waarden van hun brede tegenhangers (bijvoorbeeld als(uintmax_t)'x' != (uintmax_t)L'x'
) -
__STDC_ISO_10646__
wordt gedefinieerd alswchar_t
is gecodeerd als Unicode en wordt uitgebreid naar een geheel getal in de vormyyyymmL
, waarmee de laatste ondersteunde Unicode-revisie wordt aangegeven. -
__STDCPP_STRICT_POINTER_SAFETY__
is gedefinieerd als1
, als de implementatie strikte aanwijzerveiligheid heeft (anders heeft deze een ontspannen aanwijzerveiligheid ) -
__STDCPP_THREADS__
is gedefinieerd als1
, als het programma meer dan één thread kan hebben (van toepassing op vrijstaande implementatie - gehoste implementaties kunnen altijd meer dan één thread hebben)
Het is ook de moeite waard om __func__
te vermelden, wat geen macro is, maar een vooraf gedefinieerde functie-lokale variabele. Het bevat de naam van de functie waarin het wordt gebruikt, als een statische reeks tekens in een door de implementatie gedefinieerde indeling.
Bovenop die standaard voorgedefinieerde macro's, kunnen compilers hun eigen set voorgedefinieerde macro's hebben. Men moet de documentatie van de compiler raadplegen om die te leren. bv:
Sommige macro's zijn alleen voor ondersteuning van een of andere functie:
#ifdef __cplusplus // if compiled by C++ compiler
extern "C"{ // C code has to be decorated
// C library header declarations here
}
#endif
Anderen zijn erg handig voor het opsporen van fouten:
bool success = doSomething( /*some arguments*/ );
if( !success ){
std::cerr << "ERROR: doSomething() failed on line " << __LINE__ - 2
<< " in function " << __func__ << "()"
<< " in file " << __FILE__
<< std::endl;
}
En anderen voor triviale versiecontrole:
int main( int argc, char *argv[] ){
if( argc == 2 && std::string( argv[1] ) == "-v" ){
std::cout << "Hello World program\n"
<< "v 1.1\n" // I have to remember to update this manually
<< "compiled: " << __DATE__ << ' ' << __TIME__ // this updates automagically
<< std::endl;
}
else{
std::cout << "Hello World!\n";
}
}
X-macros
Een idiomatische techniek voor het genereren van herhalende codestructuren tijdens het compileren.
Een X-macro bestaat uit twee delen: de lijst en de uitvoering van de lijst.
Voorbeeld:
#define LIST \
X(dog) \
X(cat) \
X(racoon)
// class Animal {
// public:
// void say();
// };
#define X(name) Animal name;
LIST
#undef X
int main() {
#define X(name) name.say();
LIST
#undef X
return 0;
}
die door de preprocessor is uitgebreid tot het volgende:
Animal dog;
Animal cat;
Animal racoon;
int main() {
dog.say();
cat.say();
racoon.say();
return 0;
}
Naarmate lijsten groter worden (laten we zeggen meer dan 100 elementen), helpt deze techniek overmatig kopiëren en plakken te voorkomen.
Bron: https://en.wikipedia.org/wiki/X_Macro
Zie ook: X-macro's
Als het niet naar wens is om een nadenkend irrelevante X
definiëren voordat je LIST
, kun je ook een macronaam als argument doorgeven:
#define LIST(MACRO) \
MACRO(dog) \
MACRO(cat) \
MACRO(racoon)
Nu geeft u expliciet op welke macro moet worden gebruikt bij het uitbreiden van de lijst, bijvoorbeeld
#define FORWARD_DECLARE_ANIMAL(name) Animal name;
LIST(FORWARD_DECLARE_ANIMAL)
Als voor elke aanroep van de MACRO
aanvullende parameters nodig zijn - constant met betrekking tot de lijst, kunnen variadische macro's worden gebruikt
//a walkaround for Visual studio
#define EXPAND(x) x
#define LIST(MACRO, ...) \
EXPAND(MACRO(dog, __VA_ARGS__)) \
EXPAND(MACRO(cat, __VA_ARGS__)) \
EXPAND(MACRO(racoon, __VA_ARGS__))
Het eerste argument wordt geleverd door de LIST
, terwijl de rest wordt geleverd door de gebruiker in de LIST
aanroep. Bijvoorbeeld:
#define FORWARD_DECLARE(name, type, prefix) type prefix##name;
LIST(FORWARD_DECLARE,Animal,anim_)
LIST(FORWARD_DECLARE,Object,obj_)
zal uitbreiden naar
Animal anim_dog;
Animal anim_cat;
Animal anim_racoon;
Object obj_dog;
Object obj_cat;
Object obj_racoon;
#pragma eenmaal
De meeste, maar niet alle, C ++ -implementaties ondersteunen de #pragma once
instructie die ervoor zorgt dat het bestand slechts eenmaal in een enkele compilatie wordt opgenomen. Het maakt geen deel uit van een ISO C ++ -standaard. Bijvoorbeeld:
// Foo.h
#pragma once
class Foo
{
};
Terwijl #pragma once
enkele problemen vermijdt die verband houden met onder meer bewakers , is een #pragma
- per definitie in de standaarden - inherent een compiler-specifieke hook, en wordt geruisloos genegeerd door compilers die het niet ondersteunen. Projecten die #pragma once
moeten worden aangepast om aan de norm te voldoen.
Bij sommige compilers - met name die met voorgecompileerde headers - kan #pragma once
leiden tot een aanzienlijke versnelling van het compilatieproces. Evenzo bereiken sommige preprocessors een versnelde compilatie door te volgen welke headers bewakers hebben gebruikt. Het netto voordeel, wanneer zowel #pragma once
als bewakers worden gebruikt, hangt af van de implementatie en kan een toename of afname van de compilatietijden zijn.
#pragma once
gecombineerd met include bewakers was de aanbevolen lay-out voor header-bestanden bij het schrijven van op MFC gebaseerde applicaties op Windows, en werd gegenereerd door Visual Studio's add class
, add dialog
, add windows
wizards. Daarom is het heel gebruikelijk om ze te vinden in C ++ Windows-aanvragers.
Preprocessor-operators
#
operator of stringizing-operator wordt gebruikt om een Macro-parameter te converteren naar een letterlijke tekenreeks. Het kan alleen worden gebruikt met macro's met argumenten.
// preprocessor will convert the parameter x to the string literal x
#define PRINT(x) printf(#x "\n")
PRINT(This line will be converted to string by preprocessor);
// Compiler sees
printf("This line will be converted to string by preprocessor""\n");
Compiler voegt twee tekenreeksen samen en het laatste argument printf()
is een letterlijke tekenreeks met een newline-teken aan het einde.
Preprocessor negeert de spaties voor of na het macroargument. Dus onder print statement zullen ons hetzelfde resultaat geven.
PRINT( This line will be converted to string by preprocessor );
Als de parameter van de letterlijke tekenreeks een escape-reeks vereist, zoals vóór een dubbel aanhalingsteken (), wordt deze automatisch door de preprocessor ingevoegd.
PRINT(This "line" will be converted to "string" by preprocessor);
// Compiler sees
printf("This \"line\" will be converted to \"string\" by preprocessor""\n");
##
operator of Token plakken operator wordt gebruikt om twee parameters of tokens van een Macro samen te voegen.
// preprocessor will combine the variable and the x
#define PRINT(x) printf("variable" #x " = %d", variable##x)
int variableY = 15;
PRINT(Y);
//compiler sees
printf("variable""Y"" = %d", variableY);
en de uiteindelijke output zal zijn
variableY = 15