C-esiprosessori

C / C ++ - esiprosessori ( eng .  preprocesor , preprocessor ) - ohjelma , joka valmistelee ohjelmakoodin C / C ++ -kielellä kääntämistä varten .

Esiprosessorin perustoiminnot

Esiprosessori tekee seuraavaa:

Ehdollisen kääntämisen avulla voit valita, mikä koodi käännetään seuraavien perusteella:

Esikäsittelyvaiheet:

C/C++-esiprosessorikieli ei ole Turingin valmis, jo pelkästään siksi, että esiprosessoria ei voida saada roikkumaan direktiiveillä. Katso rekursiivinen funktio (laskettavuusteoria) .

Direktiivien syntaksi

Esikäsittelyohje (komentorivi) on rivi lähdekoodissa, jonka muoto on seuraava #ключевое_слово параметры:

Avainsanaluettelo:

Ohjeiden kuvaus

Tiedostojen lisääminen (#include)

Kun käskyt #include "..."ja löytyvät #include <...>, jossa "..." on tiedoston nimi, esiprosessori lukee määritetyn tiedoston sisällön, suorittaa käskyt ja korvaukset (korvaukset), korvaa direktiivin #includekäskyllä #line​​ja käsitellyn tiedoston sisällön.

Tiedoston #include "..."etsiminen suoritetaan nykyisessä kansiossa ja kääntäjän komentorivillä määritetyissä kansioissa. Tiedoston #include <...>etsiminen suoritetaan kansioissa, jotka sisältävät vakiokirjastotiedostoja (polut näihin kansioihin riippuvat kääntäjän toteutuksesta).

Jos löytyy käsky, joka #include последовательность-лексем ei vastaa mitään aikaisemmista muodoista, se pitää merkkijonoa tekstinä, jonka tulee kaikkien makrokorvausten seurauksena antaa #include <...>tai #include "...". Näin muodostettua ohjetta tulkitaan edelleen vastaanotetun lomakkeen mukaisesti.

Mukana olevat tiedostot sisältävät yleensä:

Ohje #includeon yleensä määritetty tiedoston alussa (otsikossa), joten mukana olevia tiedostoja kutsutaan otsikkotiedostoiksi .

Esimerkki tiedostojen sisällyttämisestä C - standardikirjastosta .

#include <math.h> // sisällyttää matemaattisten funktioiden määritykset #include <stdio.h> // sisältää I/O-funktion määritykset

Esiprosessorin käyttöä pidetään tehottomana seuraavista syistä:

  • aina kun tiedostot sisällytetään, käskyt ja korvaukset (korvaukset) suoritetaan; kääntäjä voisi tallentaa esikäsittelyn tulokset tulevaa käyttöä varten;
  • saman tiedoston useat sisällyttämiset on estettävä manuaalisesti ehdollisten käännösohjeiden avulla; kääntäjä voisi tehdä tämän tehtävän itse.

1970-luvulta lähtien alkoi ilmaantua menetelmiä, jotka korvasivat tiedostojen sisällyttämisen. Java- ja Common Lisp -kielet käyttävät paketteja (avainsana package) (katso paketti Javassa ),  Pascal käyttää englantia.  yksiköt (avainsanat unitja uses), Modula , OCaml , Haskell ja Python  , moduuleissa. Suunniteltu korvaamaan C- ja C++- kielet, D käyttää avainsanoja ja . moduleimport

Vakiot ja makrot #define

Esiprosessorin vakioita ja makroja käytetään määrittämään pieniä koodinpätkiä .

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

Jokainen vakio ja jokainen makro korvataan sitä vastaavalla määritelmällä. Makroilla on funktion kaltaiset parametrit, ja niitä käytetään vähentämään funktiokutsujen ylimääräisiä kustannuksia tapauksissa, joissa funktiokutsujen pieni määrä koodia riittää aiheuttamaan huomattavan suoritusosuman.

Esimerkki. Makron max määritelmä , jossa on kaksi argumenttia: a ja b .

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

Makroa kutsutaan aivan kuten mitä tahansa funktiota.

z = max ( x , y );

Makron vaihtamisen jälkeen koodi näyttää tältä:

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

Kuitenkin yhdessä C-kielessä olevien makrojen etujen ohella esimerkiksi yleisten tietotyyppien tai virheenkorjaustyökalujen määrittelyssä ne myös vähentävät jonkin verran käytön tehokkuutta ja voivat jopa johtaa virheisiin.

Jos esimerkiksi f ja g  ovat kaksi funktiota, kutsu

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

ei arvioi f() :ta kerran ja g() :tä kerran , ja laita suurimman arvon z :hen , kuten saatat odottaa. Sen sijaan yksi funktioista arvioidaan kahdesti. Jos funktiolla on sivuvaikutuksia, on todennäköistä, että sen käyttäytyminen on erilaista kuin odotettiin.

C-makrot voivat olla funktioita, jotka luovat jossain määrin uutta syntaksia, ja niitä voidaan myös täydentää mielivaltaisella tekstillä (vaikka C-kääntäjä edellyttää tekstin olevan virheetöntä C-koodia tai muotoiltu kommentiksi), mutta niillä on joitain rajoituksia. kuten ohjelmistorakenteet. Esimerkiksi funktion kaltaisia ​​makroja voidaan kutsua "oikeiksi" funktioiksi, mutta makroa ei voi siirtää toiselle funktiolle osoittimen avulla, koska makrolla itsessään ei ole osoitetta.

Jotkut modernit kielet eivät tyypillisesti käytä tällaista metaohjelmointia käyttämällä makroja merkkijonojen täydennyksinä, vaan luottavat joko automaattiseen tai manuaaliseen toimintojen ja menetelmien johdotukseen, vaan sen sijaan muihin abstraktiotapoihin, kuten malleihin , yleisiin funktioihin tai parametriseen polymorfismiin . Erityisesti inline-funktiot yhden makrojen suurimmista puutteista nykyaikaisissa C- ja C++-versioissa, koska inline-funktio tarjoaa makrojen edun funktiokutsun ylikuormituksen vähentämisessä, mutta sen osoite voidaan välittää osoittimessa epäsuoraan. kutsuja tai käytetään parametrina. Samoin edellä max -makrossa mainittu useiden arvioiden ongelma ei ole merkityksellinen sisäänrakennetuille funktioille.

Voit korvata #define vakiot enumilla ja makrot funktioilla inline.

Operaattorit # ja ##

Näitä operaattoreita käytetään luotaessa makroja. #-operaattori ennen makroparametria sulkee sen lainausmerkkeihin, esimerkiksi:

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

esiprosessori muuntaa muotoon:

printf ( "42" );

##-operaattori makroissa ketjuttaa kaksi merkkiä, esimerkiksi:

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

esiprosessori muuntaa muotoon:

int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; Makrokorvausten muodollinen kuvaus

1) Seuraavan lomakkeen ohjausrivi pakottaa esiprosessorin korvaamaan tunnisteen merkkijonolla koko muussa ohjelmatekstissä:

#define identifier token_sequence

Tässä tapauksessa merkkijonon alussa ja lopussa olevat välilyönnit hylätään. Toistuva #define rivi, jolla on sama tunniste, katsotaan virheeksi, jos merkkijonot eivät ole identtisiä (välilyöntien eroilla ei ole merkitystä).

2) Seuraavan muotoinen merkkijono, jossa ensimmäisen tunnisteen ja avaussulkujen välissä ei saa olla välilyöntejä, on makromääritelmä, jonka parametrit määrittävät tunniste-listan.

#define identifier(tunnisteiden_luettelo) merkkien_sekvenssi

Kuten ensimmäisessä muodossa, merkkijonon alussa ja lopussa olevat välilyönnit hylätään, ja makro voidaan määrittää uudelleen vain samalla numero- ja nimiparametriluettelolla ja samalla merkkijonolla.

Tällainen ohjausrivi käskee esiprosessorin "unohtamaan" tunnukselle annetun määritelmän:

#undef tunniste

#undef-direktiivin soveltamista aiemmin määrittelemättömään tunnisteeseen ei pidetä virheenä.

{

  • Jos makromäärittely on määritetty toisessa muodossa, ohjelmatekstin mikä tahansa lisämerkkijono, joka koostuu makrotunnisteesta (jota voi seurata välilyönnillä), aloitussuluista, pilkuilla erotetusta merkkiluettelosta ja sulkeva sulku, muodostaa makrokutsun.
  • Makrokutsun argumentit ovat pilkuilla erotettuja merkkejä, ja lainausmerkeissä tai sisäkkäisissä suluissa olevat pilkut eivät osallistu argumenttien erotteluun.
  • (!) Kun argumentteja ryhmitellään, niissä ei suoriteta makrolaajennusta.
  • Makrokutsun argumenttien lukumäärän on vastattava makromäärittelyparametrien määrää.
  • Kun argumentit on poimittu tekstistä, niitä ympäröivät välilyönnit hylätään.
  • Sitten makrotunnisteiden korvaussekvenssissä jokainen lainaamaton tunnisteparametri korvataan vastaavalla todellisella argumentilla tekstistä.
  • (!)Jos parametrin edessä ei ole #-merkkiä korvaussekvenssissä, eikä sen edessä tai jälkeen ole ##-merkki, argumenttitunnisteista tarkistetaan makrokutsujen esiintyminen; jos niitä on, siinä suoritetaan vastaavien makrojen laajennus ennen argumentin korvaamista.

Korvausprosessiin vaikuttaa kaksi erityistä käyttäjäkylttiä.

  • Ensinnäkin, jos korvaavan merkkijonon parametria edeltää #-merkki, sitten merkkijonolainausmerkit (") sijoitetaan vastaavan argumentin ympärille ja sitten parametrin tunniste ja #-merkki korvataan tuloksena olevalla merkkijono-literaalilla. .
    • Kenoviiva lisätään automaattisesti jokaisen "- tai \-merkin eteen, joka esiintyy merkkijonon tai merkkivakion ympärillä tai sisällä.
  • Toiseksi, jos minkä tahansa makromääritelmän merkkijono sisältää ##-merkin, heti parametrin korvaamisen jälkeen se sekä sitä ympäröivät välilyönnit hylätään, minkä vuoksi vierekkäiset merkit ketjutetaan, jolloin muodostuu uusi merkki.
    • Tulos on määrittelemätön, kun virheellisiä kielitunnuksia luodaan tällä tavalla tai kun tuloksena oleva teksti riippuu ##-toiminnon käyttöjärjestyksestä.
    • Lisäksi ##-merkki ei voi esiintyä merkkien korvaavan sarjan alussa tai lopussa.

}

  • (!) Kummankin tyyppisissä makroissa tokenien korvaussekvenssi skannataan uudelleen uusien määrittelytunnisteiden etsimiseksi.
  • (!) Kuitenkin, jos jokin tunniste on jo korvattu nykyisessä laajennusprosessissa, tällaisen tunnisteen uudelleen ilmestyminen ei aiheuta sen korvaamista; se pysyy koskemattomana.
  • (!)Vaikka laajennettu makrokutsulinja alkaa #-merkillä, sitä ei oteta esiprosessoriohjeeksi.

Huutomerkki (!) merkitsee säännöt, jotka vastaavat rekursiivisesta kutsumisesta ja määritelmistä.

Esimerkki makrolaajennuksesta #define cat( x, y ) x ## y

Makrokutsu "cat(var, 123)" korvataan nimellä "var123". Kuitenkaan "kissa(kissa(1, 2), 3)" kutsuminen ei tuota toivottua tulosta. Harkitse esiprosessorin vaiheita:

0: kissa( kissa( 1, 2 ), 3 ) 1: kissa( 1, 2 ) ## 3 2: kissa( 1, 2 )3

Toiminto "##" esti toisen "kissa"-kutsun argumenttien oikean laajentamisen. Tuloksena on seuraava merkkijono:

kissa ( 1 , 2 ) 3

jossa ")3" on tulos ensimmäisen argumentin viimeisen merkkien ketjuttamisesta toisen argumentin ensimmäiseen merkkiin, ei ole kelvollinen merkki.

Voit määrittää toisen makrotason seuraavasti:

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

Kutsu "xcat(xcat(1, 2), 3)" korvataan nimellä "123". Harkitse esiprosessorin vaiheita:

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

Kaikki meni hyvin, koska "##"-operaattori ei osallistunut "xcat"-makron laajentamiseen.

Monet staattiset analysaattorit eivät pysty käsittelemään makroja oikein, joten staattisen analyysin laatu heikkenee .

Ennalta määritetyt vakiot #define

Esiprosessorin automaattisesti luomat vakiot:

  • __LINE__korvataan nykyisellä rivinumerolla; nykyinen rivinumero voidaan ohittaa direktiivillä #line; käytetään virheenkorjaukseen ;
  • __FILE__korvataan tiedoston nimellä; tiedostonimi voidaan myös ohittaa #line;
  • __FUNCTION__korvataan nykyisen funktion nimellä;
  • __DATE__korvataan nykyisellä päivämäärällä (silloin, kun esikäsittelijä käsittelee koodin);
  • __TIME__korvataan nykyisellä ajalla (silloin, kun esiprosessori käsitteli koodin);
  • __TIMESTAMP__korvataan nykyisellä päivämäärällä ja kellonajalla (silloin, kun esiprosessori käsitteli koodin);
  • __COUNTER__korvataan yksilöllisellä numerolla, joka alkaa 0:sta; jokaisen vaihdon jälkeen määrä kasvaa yhdellä;
  • __STDC__korvataan luvulla 1, jos käännös on C-kielistandardin mukainen;
  • __STDC_HOSTED__määritelty C99:ssä ja edellä; korvataan luvulla 1, jos suoritus on käyttöjärjestelmän hallinnassa ;
  • __STDC_VERSION__määritelty C99:ssä ja edellä; C99:ssä se korvataan numerolla 199901 ja C11:ssä numerolla 201112;
  • __STDC_IEC_559__määritelty C99:ssä ja edellä; vakio on olemassa, jos kääntäjä tukee IEC 60559 -liukulukuoperaatioita;
  • __STDC_IEC_559_COMPLEX__määritelty C99:ssä ja edellä; vakio on olemassa, jos kääntäjä tukee IEC 60559 kompleksilukuoperaatioita; C99-standardi velvoittaa tukemaan operaatioita kompleksiluvuilla;
  • __STDC_NO_COMPLEX__määritelty C11:ssä; korvataan luvulla 1, jos operaatioita kompleksiluvuilla ei tueta;
  • __STDC_NO_VLA__määritelty C11:ssä; korvataan 1:llä, jos vaihtuvapituisia taulukoita ei tueta; vaihtuvapituisia taulukoita on tuettava C99:ssä;
  • __VA_ARGS__määritellään C99:ssä ja voit luoda makroja, joissa on vaihteleva määrä argumentteja.

Ehdollinen kokoelma

C-esiprosessori tarjoaa mahdollisuuden kääntää ehtoja. Tämä mahdollistaa saman koodin eri versioiden mahdollisuuden. Tyypillisesti tätä lähestymistapaa käytetään ohjelman mukauttamiseen kääntäjäalustalle, tilalle (virheenkorjattu koodi voidaan korostaa tuloksena olevassa koodissa) tai kykyä tarkistaa tiedostoyhteys täsmälleen kerran.

Yleensä ohjelmoijan on käytettävä rakennetta, kuten:

# ifndef FOO_H # määritä FOO_H ... ( otsikkotiedoston koodi )... # loppu Jos

Tämä "makrosuojaus" estää otsikkotiedoston sisällyttämisen kahteen kertaan tarkistamalla kyseisen makron olemassaolon, jolla on sama nimi kuin otsikkotiedostolla. FOO_H-makron määritelmä tapahtuu, kun esiprosessori käsittelee ensin otsikkotiedoston. Sitten, jos tämä otsikkotiedosto sisällytetään uudelleen, FOO_H on jo määritetty, jolloin esiprosessori ohittaa tämän otsikkotiedoston koko tekstin.

Sama voidaan tehdä sisällyttämällä seuraava direktiivi otsikkotiedostoon:

#pragma kerran

Esiprosessorin ehdot voidaan määrittää useilla tavoilla, esimerkiksi:

# ifdef x ... #muuta ... # loppu Jos

tai

# ifx ... #muuta ... # loppu Jos

Tätä menetelmää käytetään usein järjestelmän otsikkotiedostoissa erilaisten ominaisuuksien testaamiseen, joiden määritelmä voi vaihdella alustasta riippuen. Esimerkiksi Glibc - kirjasto käyttää ominaisuudentarkistusmakroja varmistaakseen, että käyttöjärjestelmä ja laitteisto tukevat niitä (makroja) oikein, samalla kun ne säilyttävät saman ohjelmointirajapinnan.

Useimmat nykyaikaiset ohjelmointikielet eivät hyödynnä näitä ominaisuuksia, vaan luottavat enemmän perinteisiin ehdollisiin lausekkeisiin if...then...else...ja jättävät kääntäjän tehtäväksi poimia turhaa koodia käännettävästä ohjelmasta.

Digrafit ja trigrafit

Katso digrafit ja trigrafit C/C++-kielillä.

Esiprosessori käsittelee digrafit “ %:” (“ #”), “ %:%:” (“ ##”) ja trigrafit “ ??=” (“ #”), “ ??/” (“ \”).

Esiprosessori pitää sekvenssiä " %:%: " kahdeksi tunnukseksi C- koodia käsiteltäessä ja yhden merkin käsitellessä C++-koodia.

Katso myös

Muistiinpanot

Linkit