C förprocessor

C/C++-förprocessor ( eng.  preprocessor , preprocessor) - ett program som förbereder programkoden i C / C++ för kompilering .

Grundläggande funktioner för förprocessorn

Förprocessorn gör följande:

Villkorlig kompilering låter dig välja vilken kod som ska kompileras baserat på:

Förbehandlare steg:

C/C++-förprocessorspråket är inte Turing komplett, om så bara för att det är omöjligt att få förprocessorn att hänga sig med hjälp av direktiv. Se rekursiv funktion (beräkbarhetsteori) .

Syntax för direktiv

Ett förbehandlardirektiv (kommandorad) är en rad i källkoden som har följande format: #ключевое_слово параметры:

Sökordslista:

Beskrivning av direktiv

Infogar filer (#include)

När direktiv #include "..."och hittas #include <...>, där "..." är ett filnamn, läser förbehandlaren innehållet i den angivna filen, utför direktiv och ersättningar (ersättningar), ersätter direktivet #includemed ett direktiv #lineoch det behandlade filinnehållet.

För att #include "..."söka efter en fil, utförs den i den aktuella mappen och mappar som anges på kompilatorns kommandorad. För att #include <...>söka efter en fil utförs den i mappar som innehåller standardbiblioteksfiler (sökvägarna till dessa mappar beror på kompilatorns implementering).

Om ett direktiv hittas som #include последовательность-лексем inte matchar någon av de tidigare formerna, betraktar det sekvensen av tokens som text, som, som ett resultat av alla makrosubstitutioner, bör ge #include <...>eller #include "...". Direktivet som genereras på detta sätt kommer att tolkas vidare i enlighet med den mottagna blanketten.

Inkluderade filer innehåller vanligtvis:

Direktivet #includeanges vanligtvis i början av filen (i rubriken), så inkluderade filer kallas rubrikfiler .

Ett exempel på att inkludera filer från C -standardbiblioteket .

#inkludera <math.h> // inkludera matematiska funktionsdeklarationer #include <stdio.h> // include I/O-funktionsdeklarationer

Att använda en förprocessor anses ineffektivt av följande skäl:

  • varje gång filer ingår, exekveras direktiv och ersättningar (ersättningar); kompilatorn kan lagra resultaten av förbearbetningen för framtida användning;
  • flera inkluderingar av samma fil måste förhindras manuellt med villkorliga kompileringsdirektiv; kompilatorn kan göra den här uppgiften själv.

Med början på 1970-talet började metoder dyka upp som ersatte införandet av filer. Språken Java och Common Lisp använder paket (sökord package) (se paket i Java ),  Pascal använder engelska.  enheter (sökord unitoch uses), i Modula , OCaml , Haskell och Python  , moduler. Designad för att ersätta språken C och C++ , D använder nyckelorden och . moduleimport

Konstanter och makron #definiera

Preprocessorkonstanter och makron används för att definiera små bitar av kod.

// konstant #define BUFFER_SIZE ( 1024 ) // makro #define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )

Varje konstant och varje makro ersätts av dess motsvarande definition. Makron har funktionsliknande parametrar och används för att minska omkostnaderna för funktionsanrop i de fall där den lilla mängd kod som funktionen anropar räcker för att orsaka en märkbar prestandaträff.

Exempel. Definition av makrot max , som tar två argument: a och b .

#define max( a, b ) ( (a) > (b) ? (a) : (b) )

Ett makro kallas precis som vilken funktion som helst.

z = max ( x , y );

Efter att ha ersatt makrot kommer koden att se ut så här:

z = ( ( x ) > ( y ) a ( x ) : ( y ) );

Men tillsammans med fördelarna med att använda makron på C-språket, till exempel för att definiera generiska datatyper eller felsökningsverktyg, minskar de också effektiviteten i användningen något och kan till och med leda till fel.

Till exempel, om f och g  är två funktioner, anropet

z = max ( f (), g () );

kommer inte att utvärdera f() en gång och g() en gång , och sätter det största värdet i z , som du kan förvänta dig. Istället kommer en av funktionerna att utvärderas två gånger. Om en funktion har biverkningar är det troligt att dess beteende blir annorlunda än förväntat.

C-makron kan vara som funktioner, skapa ny syntax i viss utsträckning, och kan även utökas med godtycklig text (även om C-kompilatorn kräver att texten är i felfri C-kod eller formaterad som en kommentar), men de har vissa begränsningar som mjukvarustrukturer. Funktionsliknande makron kan till exempel kallas som "riktiga" funktioner, men ett makro kan inte skickas till en annan funktion med hjälp av en pekare, eftersom själva makrot inte har någon adress.

Vissa moderna språk använder vanligtvis inte den här typen av metaprogrammering med makron som teckensträngskompletteringar, och förlitar sig på antingen automatisk eller manuell koppling av funktioner och metoder, utan istället andra sätt för abstraktion som mallar , generiska funktioner eller parametrisk polymorfism . Speciellt inline-funktioner en av de stora bristerna med makron i moderna versioner av C och C++, eftersom en inline-funktion ger fördelen med makron för att reducera overheaden för ett funktionsanrop, men dess adress kan skickas i en pekare för indirekt anrop eller används som en parameter. På samma sätt är problemet med flera utvärderingar som nämns ovan i maxmakrot irrelevant för inbyggda funktioner.

Du kan ersätta #define-konstanter med enums och makron med funktioner inline.

Operatörer # och ##

Dessa operatorer används när man skapar makron. Operatorn # före en makroparameter omsluter den med dubbla citattecken, till exempel:

#define make_str( bar ) # bar printf ( make_str ( 42 ) );

förprocessor konverterar till:

printf ( "42" );

Operatorn ## i makron sammanfogar två tokens, till exempel:

#define MakePosition( x ) x##X, x##Y, x##Width, x##Height int MakePosition ( Object );

förprocessor konverterar till:

int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; Formell beskrivning av makrosubstitutioner

1) Kontrollraden i följande formulär tvingar förprocessorn att ersätta identifieraren med en sekvens av tokens genom resten av programtexten:

#define identifier token_sequence

I det här fallet slängs blanktecken i början och slutet av sekvensen av tokens. En upprepad #define-rad med samma identifierare anses vara ett fel om sekvenserna av tokens inte är identiska (felmatchningar i blanktecken spelar ingen roll).

2) En sträng av följande form, där det inte får finnas några blanktecken mellan den första identifieraren och den inledande parentesen, är en makrodefinition med parametrar specificerade av identifierare-listan.

#define identifier(list_of_identifiers) sequence_of_tokens

Som i den första formen kasseras blankstegstecken i början och slutet av tokensekvensen, och makrot kan bara omdefinieras med samma nummer och namnparameterlista och samma tokensekvens.

En kontrollrad som denna säger till förprocessorn att "glömma" definitionen som ges till identifieraren:

#undef identifierare

Att tillämpa #undef-direktivet på en tidigare odefinierad identifierare anses inte vara ett fel.

{

  • Om makrodefinitionen specificerades i den andra formen, då eventuell ytterligare teckensträng i programtexten, bestående av en makroidentifierare (eventuellt följt av blanktecken), en öppningsparentes, en kommaseparerad lista med tokens och en avslutande parentes, utgör en makroanrop.
  • Makroanropsargument är kommaseparerade tokens, och kommatecken inom citattecken eller kapslade parenteser deltar inte i argumentseparation.
  • (!) Vid gruppering av argument utförs inte makroexpansion i dem.
  • Antalet argument i makroanropet måste matcha antalet makrodefinitionsparametrar.
  • Efter att ha extraherat argumenten från texten kasseras blanksteg som omger dem.
  • Sedan, i ersättningssekvensen av makrotokens, ersätts varje identifieringsparameter utan citattecken av motsvarande faktiska argument från texten.
  • (!)Om parametern inte föregås av #-tecknet i ersättningssekvensen, och varken före eller efter det är ##-tecknet, kontrolleras argumenttecknet för närvaron av makroanrop i dem; om det finns några, så utförs expansionen av motsvarande makron i den innan argumentet ersätts.

Ersättningsprocessen påverkas av två speciella operatörstecken.

  • Först, om en parameter i en ersättningssträng av tokens föregås av ett #-tecken, placeras citattecken (") runt motsvarande argument, och sedan ersätts parameteridentifieraren, tillsammans med #-tecknet, av den resulterande strängen literal. .
    • Ett omvänt snedstreck infogas automatiskt före varje " eller \ tecken som förekommer runt eller inuti en sträng eller teckenkonstant.
  • För det andra, om en sekvens av tokens i en makrodefinition av något slag innehåller ##-tecknet, så kasseras det omedelbart efter parameterbyte, tillsammans med blanktecken som omger den, på grund av vilka intilliggande tokens sammanlänkas, vilket bildar en ny token.
    • Resultatet är odefinierat när ogiltiga språktokens genereras på detta sätt, eller när den resulterande texten beror på i vilken ordning ##-operationen tillämpas.
    • Dessutom kan ##-tecknet inte visas varken i början eller slutet av en ersättningssekvens av tokens.

}

  • (!) I makron av båda typerna skannas ersättningssekvensen av tokens om i jakt på nya definiera-identifierare.
  • (!) Men om någon identifierare redan har ersatts i den aktuella expansionsprocessen, kommer återuppkomsten av en sådan identifierare inte att leda till att den ersätts; den kommer att förbli orörd.
  • (!) Även om den utökade makroanropslinjen börjar med #-tecknet, kommer den inte att tas som ett förbehandlardirektiv.

Ett utropstecken (!) markerar reglerna som ansvarar för rekursiv anrop och definitioner.

Exempel på makroexpansion #define cat( x, y ) x ## y

Makroanropet "cat(var, 123)" kommer att ersättas med "var123". Men att anropa "cat(cat(1, 2), 3)" ger inte det önskade resultatet. Tänk på stegen för förprocessorn:

0: cat( cat( 1, 2 ), 3 ) 1: cat( 1, 2 ) ## 3 2: katt( 1, 2)3

Operationen "##" förhindrade korrekt expansion av argumenten för det andra "cat"-anropet. Resultatet är följande sträng av tokens:

katt ( 1 , 2 ) 3

där ")3" är resultatet av att sammanfoga den sista token i det första argumentet med den första token i det andra argumentet, är inte en giltig token.

Du kan ange den andra makronivån enligt följande:

#define xcat( x, y ) cat( x, y )

Anropet "xcat(xcat(1, 2), 3)" kommer att ersättas med "123". Tänk på stegen för förprocessorn:

0: xcat( xcat( 1, 2 ), 3 ) 1: cat( xcat( 1, 2 ), 3 ) 2: cat( cat( 1, 2 ), 3 ) 3: katt( 1 ## 2, 3 ) 4: katt ( 12, 3 ) 5:12##3 6:123

Allt gick bra, eftersom "##"-operatören inte deltog i expansionen av "xcat"-makrot.

Många statiska analysatorer kan inte behandla makron korrekt, så kvaliteten på statisk analys minskar .

Fördefinierade konstanter #define

Konstanter som genereras automatiskt av förprocessorn:

  • __LINE__ersätts med nuvarande radnummer; det aktuella linjenumret kan åsidosättas av direktivet #line; används för felsökning ;
  • __FILE__ersätts av filnamnet; filnamnet kan också åsidosättas med #line;
  • __FUNCTION__ersätts med namnet på den aktuella funktionen;
  • __DATE__ersätts av det aktuella datumet (vid den tidpunkt då koden bearbetas av förbehandlaren);
  • __TIME__ersätts av den aktuella tiden (vid den tidpunkt då koden bearbetades av förprocessorn);
  • __TIMESTAMP__ersätts av aktuellt datum och tid (vid den tidpunkt då koden bearbetades av förbehandlaren);
  • __COUNTER__ersätts av ett unikt nummer som börjar från 0; efter varje utbyte ökar antalet med en;
  • __STDC__ersätts med 1 om sammanställningen är i enlighet med språkstandarden C;
  • __STDC_HOSTED__definieras i C99 och ovan; ersätts med 1 om exekveringen är under OS -kontroll ;
  • __STDC_VERSION__definieras i C99 och ovan; för C99 ersätts det med numret 199901, och för C11 ersätts det med numret 201112;
  • __STDC_IEC_559__definieras i C99 och ovan; konstanten existerar om kompilatorn stöder IEC 60559 flyttalsoperationer;
  • __STDC_IEC_559_COMPLEX__definieras i C99 och ovan; konstanten existerar om kompilatorn stöder IEC 60559 komplexa taloperationer; C99-standarden förpliktar att stödja operationer med komplexa tal;
  • __STDC_NO_COMPLEX__definieras i C11; ersätts med 1 om operationer med komplexa tal inte stöds;
  • __STDC_NO_VLA__definieras i C11; ersättas med 1 om arrayer med variabel längd inte stöds; arrayer med variabel längd måste stödjas i C99;
  • __VA_ARGS__definieras i C99 och låter dig skapa makron med ett variabelt antal argument.

Villkorlig kompilering

C-förprocessorn ger möjlighet att kompilera med villkor. Detta ger möjlighet till olika versioner av samma kod. Vanligtvis används detta tillvägagångssätt för att anpassa programmet för kompilatorplattformen, tillstånd (den felsökta koden kan markeras i den resulterande koden) eller möjligheten att kontrollera filanslutningen exakt en gång.

I allmänhet måste programmeraren använda en konstruktion som:

# ifndef FOO_H # definiera FOO_H ...( header file code )... # endif

Detta "makroskydd" förhindrar att en rubrikfil dubbelinkluderas genom att kontrollera om det finns makrot, som har samma namn som rubrikfilen. Definitionen av makrot FOO_H inträffar när rubrikfilen först bearbetas av förprocessorn. Sedan, om denna rubrikfil återinkluderas, är FOO_H redan definierad, vilket gör att förprocessorn hoppar över hela texten i denna rubrikfil.

Detsamma kan göras genom att inkludera följande direktiv i rubrikfilen:

# pragma en gång

Förprocessorvillkor kan specificeras på flera sätt, till exempel:

# ifdef x ... #annat ... # endif

eller

# ifx ... #annat ... # endif

Denna metod används ofta i systemhuvudfiler för att testa olika funktioner, vars definition kan variera beroende på plattformen; till exempel använder Glibc- biblioteket funktionskontrollmakron för att verifiera att operativsystemet och hårdvaran stöder dem (makron) korrekt samtidigt som de bibehåller samma programmeringsgränssnitt.

De flesta moderna programmeringsspråk drar inte nytta av dessa funktioner, förlitar sig mer på traditionella villkorliga uttalanden if...then...else..., vilket lämnar kompilatorn med uppgiften att extrahera värdelös kod från programmet som kompileras.

Digrafer och trigrafer

Se digrafer och trigrafer på C/C++-språk.

Förprocessorn bearbetar digraferna “ %:” (“ #”), “ %:%:” (“ ##”) och trigraferna “ ??=” (“ #”), “ ??/” (“ \”).

Förprocessorn anser att sekvensen " %:%: " är två tokens vid bearbetning av C-kod och en token vid bearbetning av C++-kod.

Se även

Anteckningar

Länkar