C előfeldolgozó

C / C ++ előfeldolgozó ( eng.  pre processor , preprocessor ) - olyan program , amely előkészíti a programkódot C / C ++ nyelven fordításhoz .

Az előfeldolgozó alapvető funkciói

Az előfeldolgozó a következőket teszi:

A feltételes fordítás lehetővé teszi, hogy kiválassza, hogy melyik kódot fordítsa le:

Az előfeldolgozó lépései:

A C/C++ előfeldolgozó nyelv nem Turing-teljes, már csak azért is, mert az előfeldolgozót nem lehet lefagyni direktívák segítségével. Lásd rekurzív függvény (számítási elmélet) .

Irányelvek szintaxisa

Az előfeldolgozó direktíva (parancssor) egy sor a forráskódban, amelynek formátuma a következő #ключевое_слово параметры:

Kulcsszólista:

Az irányelvek leírása

Fájlok beszúrása (#include)

#include "..."Az és direktívák megtalálásakor #include <...>, ahol a "..." egy fájlnév, az előfeldolgozó beolvassa a megadott fájl tartalmát, végrehajtja a direktívákat és helyettesítéseket (helyettesítéseket), lecseréli az direktívát #includeegy direktívára #lineés a feldolgozott fájl tartalmát.

A #include "..."fájl kereséséhez a fordító parancssorában megadott aktuális mappában és mappákban kell végrehajtani. A #include <...>fájl keresése szabványos könyvtári fájlokat tartalmazó mappákban történik (a mappák elérési útja a fordító megvalósításától függ).

Ha olyan direktívát találunk, #include последовательность-лексем amely nem egyezik az előző formák egyikével sem, akkor a tokenek sorozatát szövegnek tekinti, amelynek minden makróhelyettesítés eredményeként vagy -t kell #include <...>adnia #include "...". Az így generált direktíva a beérkezett forma szerint kerül további értelmezésre.

A mellékelt fájlok általában a következőket tartalmazzák:

Az irányelvet általában a fájl elején (a fejlécben) adják meg, ezért a benne foglalt fájlokat fejlécfájloknak#include nevezzük .

Példa a C szabványos könyvtárból származó fájlok felvételére .

#include <math.h> // matematikai függvények deklarációi #include <stdio.h> // tartalmazza az I/O függvény deklarációit

Az előfeldolgozó használata a következő okok miatt nem hatékony:

  • minden alkalommal, amikor fájlokat tartalmaznak, az utasítások és a helyettesítések (helyettesítések) végrehajtásra kerülnek; a fordító tárolhatja az előfeldolgozás eredményeit későbbi felhasználás céljából;
  • ugyanazon fájl többszöri felvételét manuálisan meg kell akadályozni feltételes fordítási direktívák használatával; a fordító maga is meg tudja csinálni ezt a feladatot.

Az 1970-es évektől kezdődően olyan módszerek kezdtek megjelenni, amelyek felváltották a fájlok felvételét. A Java és a Common Lisp nyelvek csomagokat használnak (kulcsszó package) (lásd a Java csomagot ), a  Pascal pedig az angolt.  egységek (kulcsszavak unités uses), a Modula , OCaml , Haskell és Python  modulokban. A C és C++ nyelvek helyettesítésére tervezett D a és kulcsszavakat használja . moduleimport

Konstansok és makrók #define

Az előfeldolgozó konstansokat és makrókat kis kódrészletek meghatározására használják .

// állandó #define BUFFER_SIZE ( 1024 ) // makró #define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )

Minden állandót és makrót a megfelelő definícióval helyettesítenek. A makrók függvényszerű paraméterekkel rendelkeznek, és a függvényhívások többletterhelésének csökkentésére szolgálnak olyan esetekben, amikor a függvényhívás által meghívott kód kis mennyisége elegendő ahhoz, hogy észrevehető teljesítménycsökkenést okozzon.

Példa. A max makró definíciója , amely két argumentumból áll: a és b .

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

A makrót ugyanúgy hívják, mint minden függvényt.

z = max ( x , y );

A makró cseréje után a kód így fog kinézni:

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

A makrók C nyelvben való használatának előnyei mellett, például általános adattípusok vagy hibakereső eszközök meghatározásához, némileg csökkentik a használatuk hatékonyságát, és akár hibákhoz is vezethetnek.

Például, ha f és g  két függvény, akkor a hívás

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

nem fogja kiértékelni az f() -t egyszer és a g() -t egyszer , és a legnagyobb értéket adja z -be , ahogyan azt várhatnánk. Ehelyett az egyik függvény kétszer kerül kiértékelésre. Ha egy függvénynek vannak mellékhatásai, akkor valószínű, hogy viselkedése eltér a várttól.

A C makrók lehetnek függvények, bizonyos mértékig új szintaxist hoznak létre, és tetszőleges szöveggel is kiegészíthetők (bár a C fordító megköveteli, hogy a szöveg hibamentes C kódban legyen, vagy megjegyzésként formázza), de vannak korlátai. mint a szoftverstruktúrák. A függvényszerű makrókat például úgy hívhatjuk, mint "igazi" függvényeket, de egy makrót nem lehet mutató segítségével átadni egy másik függvénynek, mert magának a makrónak nincs címe.

Egyes modern nyelvek jellemzően nem használják ezt a fajta metaprogramozást , makrókat használva karaktersorozat-kiegészítésként, és a funkciók és módszerek automatikus vagy kézi bekötésére támaszkodnak, hanem az absztrakció más módjaira, például sablonokra , általános függvényekre vagy parametrikus polimorfizmusra . Az inline függvények különösen a makrók egyik fő hiányosságát a C és C++ modern verzióiban, mivel a beágyazott függvény a makrók előnyét biztosítja a függvényhívás többletköltségének csökkentésében, de a címe átadható egy mutatóban közvetett célokra. hívásokat, vagy paraméterként használják. Hasonlóképpen, a többszörös kiértékelés fent említett problémája a max makróban irreleváns a beépített függvények szempontjából.

A #define konstansokat enumokkal, a makrókat pedig függvényekkel helyettesítheti inline.

# és ## operátor

Ezeket az operátorokat makrók létrehozásakor használják. A makróparaméter előtti # operátor dupla idézőjelek közé teszi, például:

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

Az előfeldolgozó a következőkre konvertál:

printf ( "42" );

A makrók ## operátora két tokent köt össze, például:

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

Az előfeldolgozó a következőkre konvertál:

int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; A makróhelyettesítések formális leírása

1) A következő űrlap vezérlősora arra kényszeríti az előfeldolgozót, hogy a programszöveg többi részében az azonosítót egy token sorozatra cserélje:

#define azonosító token_sequence

Ebben az esetben a tokenek sorozat elején és végén lévő szóköz karaktereket el kell dobni. Az ismétlődő #define sor ugyanazzal az azonosítóval hibának minősül, ha a tokenek sorozatai nem azonosak (a szóközök közötti eltérések nem számítanak).

2) A következő formájú karakterlánc, ahol nem lehetnek szóközök az első azonosító és a nyitó zárójel között, egy makródefiníció az azonosítólista által meghatározott paraméterekkel.

#define identifier(azonosítók_listája) tokenek_sorrendje

Az első formához hasonlóan a token sorozat elején és végén lévő szóköz karakterek el lesznek vetve, és a makró csak ugyanazzal a szám- és névparaméter-listával és ugyanazzal a token sorozattal definiálható újra.

Az ehhez hasonló vezérlősor arra utasítja az előfeldolgozót, hogy "felejtse el" az azonosítónak adott definíciót:

#undef azonosító

Az #undef direktíva alkalmazása egy korábban nem definiált azonosítóra nem tekinthető hibának.

{

  • Ha a makródefiníciót a második formában adták meg, akkor a programszöveg bármely további karaktersorozata, amely egy makróazonosítóból (amelyet szóközök követhet), egy nyitó zárójelből, egy tokenek vesszővel elválasztott listájából és egy záró zárójelben, makróhívást jelent.
  • A makróhívási argumentumok vesszővel elválasztott tokenek, és az idézőjelbe vagy beágyazott zárójelbe tett vesszők nem vesznek részt az argumentumok elválasztásában.
  • (!)Argumentumok csoportosításakor nem történik bennük makróbővítés.
  • A makróhívás argumentumainak számának meg kell egyeznie a makródefiníciós paraméterek számával.
  • Az argumentumok szövegből való kinyerése után az őket körülvevő szóköz karakterek el lesznek vetve.
  • Ezután a makró tokenek csereszekvenciájában minden idézetlen azonosító-paramétert a megfelelő tényleges argumentum helyettesít a szövegből.
  • (!)Ha a paramétert nem előzi meg # jel a helyettesítési szekvenciában, és sem előtte, sem utána nem ## jel, akkor az argumentum tokenek ellenőrzik, hogy vannak-e bennük makróhívások; ha vannak ilyenek, akkor a megfelelő makrók kibővítése megtörténik benne az argumentum behelyettesítése előtt.

A helyettesítési folyamatot két speciális kezelőtábla befolyásolja.

  • Először is, ha egy paramétert a helyettesítő tokenek karakterláncában egy # jel előzi meg, akkor a karakterlánc idézőjelei (") kerülnek a megfelelő argumentum köré, majd a paraméter azonosítóját a # jellel együtt a kapott karakterlánc-literál helyettesíti. .
    • A program automatikusan egy fordított perjelet szúr be minden " vagy \ karakter elé, amely egy karakterlánc vagy karakterkonstans körül vagy belsejében fordul elő.
  • Másodszor, ha egy tetszőleges makródefinícióban egy tokenek sorozata tartalmazza a ## karaktert, akkor közvetlenül a paramétercsere után a rendszer eldobja a körülötte lévő szóköz karakterekkel együtt, aminek következtében a szomszédos tokenek összefűződnek, és ezáltal létrejön. egy új tokent.
    • Az eredmény nem definiálható, ha érvénytelen nyelvi tokeneket generálnak ilyen módon, vagy ha az eredményül kapott szöveg a ## művelet alkalmazási sorrendjétől függ.
    • Ezenkívül a ## karakter nem jelenhet meg sem a tokenek cseresorozatának elején, sem a végén.

}

  • (!) Mindkét típusú makróban a tokenek csereszekvenciája újraellenőrzésre kerül az új define-azonosítók keresése érdekében.
  • (!) Ha azonban a jelenlegi bővítési folyamat során valamelyik azonosítót már lecserélték, egy ilyen azonosító újbóli megjelenése nem okozza a cserét; érintetlen marad.
  • (!)Még ha a kibővített makróhívási vonal # jellel kezdődik is, az nem lesz előfeldolgozó direktíva.

Felkiáltójel (!) jelöli a rekurzív hívásért és definíciókért felelős szabályokat.

Makróbővítési példa #define cat( x, y ) x ## y

A "cat(var, 123)" makróhívás helyére "var123" lép. A "cat(cat(1, 2), 3)" meghívása azonban nem hozza meg a kívánt eredményt. Fontolja meg az előfeldolgozó lépéseit:

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

A "##" művelet megakadályozta a második "cat" hívás argumentumainak megfelelő kiterjesztését. Az eredmény a következő token-sorozat:

macska ( 1 , 2 ) 3

ahol a ")3" az első argumentum utolsó jelzőjének és a második argumentum első jelzőjének összefűzésének eredménye, nem érvényes token.

A második makrószintet a következőképpen adhatja meg:

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

Az "xcat(xcat(1, 2), 3)" hívás helyére "123" lép. Fontolja meg az előfeldolgozó lépéseit:

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

Minden jól ment, mert a "##" operátor nem vett részt az "xcat" makró bővítésében.

Sok statikus analizátor nem képes helyesen feldolgozni a makrókat, így a statikus elemzés minősége csökken .

Előre definiált állandók #define

Az előfeldolgozó által automatikusan generált állandók:

  • __LINE__helyébe az aktuális sorszám lép; az aktuális sorszámot felülírhatja a direktíva #line; hibakeresésre használják ;
  • __FILE__helyébe a fájlnév lép; a fájlnév felülírható a #line;
  • __FUNCTION__helyébe az aktuális függvény neve kerül;
  • __DATE__helyébe az aktuális dátum lép (amikor a kódot az előfeldolgozó feldolgozza);
  • __TIME__az aktuális idő váltja fel (amikor a kódot az előfeldolgozó feldolgozta);
  • __TIMESTAMP__helyébe az aktuális dátum és idő kerül (amikor a kódot az előfeldolgozó feldolgozta);
  • __COUNTER__helyébe egy 0-tól kezdődő egyedi szám lép; minden csere után a szám eggyel nő;
  • __STDC__helyébe 1 lép, ha az összeállítás megfelel a C nyelvi szabványnak;
  • __STDC_HOSTED__a C99 és a fentiekben meghatározottak; helyére 1 kerül, ha a végrehajtás az operációs rendszer vezérlése alatt áll ;
  • __STDC_VERSION__a C99 és a fentiekben meghatározottak; a C99 esetében a 199901 szám, a C11 esetében pedig a 201112 szám lép;
  • __STDC_IEC_559__a C99 és a fentiekben meghatározottak; a konstans létezik, ha a fordító támogatja az IEC 60559 lebegőpontos műveleteket;
  • __STDC_IEC_559_COMPLEX__a C99 és a fentiekben meghatározottak; a konstans létezik, ha a fordító támogatja az IEC 60559 komplex számműveleteket; a C99 szabvány kötelezi a komplex számokkal végzett műveletek támogatását;
  • __STDC_NO_COMPLEX__a C11; helyébe 1 kerül, ha a komplex számokkal végzett műveletek nem támogatottak;
  • __STDC_NO_VLA__a C11; 1-re cseréljük, ha a változó hosszúságú tömbök nem támogatottak; a változó hosszúságú tömböket támogatni kell a C99-ben;
  • __VA_ARGS__a C99-ben definiált, és lehetővé teszi változó számú argumentumú makrók létrehozását.

Feltételes összeállítás

A C előfeldolgozó biztosítja a feltételekkel történő fordítás lehetőségét. Ez lehetővé teszi ugyanazon kód különböző verzióinak lehetőségét. Általában ezt a megközelítést használják a program testreszabására a fordítóplatformhoz, az állapothoz (a hibakereső kód kiemelhető a kapott kódban), vagy a fájlkapcsolat pontos egyszeri ellenőrzésének képességéhez.

Általában a programozónak olyan konstrukciót kell használnia, mint:

# ifndef FOO_H # define FOO_H ...( fejlécfájl kódja ) ... # endif

Ez a „makróvédelem” megakadályozza, hogy egy fejlécfájl duplán kerüljön bele azáltal, hogy ellenőrzi a makró létezését, amelynek neve megegyezik a fejlécfájléval. A FOO_H makró meghatározása akkor következik be, amikor az előfeldolgozó először feldolgozza a fejlécfájlt. Ezután, ha ez a fejlécfájl újra szerepel, a FOO_H már definiálva van, így az előfeldolgozó kihagyja ennek a fejlécfájlnak a teljes szövegét.

Ugyanezt megteheti a következő direktíva beillesztésével a fejlécfájlba:

# pragma egyszer

Az előfeldolgozó feltételei többféleképpen is megadhatók, például:

# ifdef x ... #egyéb ... # endif

vagy

# ifx ... #egyéb ... # endif

Ezt a módszert gyakran használják rendszerfejléc-fájlokban a különféle képességek tesztelésére, amelyek meghatározása platformonként változhat; például a Glibc könyvtár szolgáltatás-ellenőrző makrókat használ annak ellenőrzésére, hogy az operációs rendszer és a hardver megfelelően támogatja-e ezeket (a makrókat), miközben ugyanazt a programozási felületet fenntartja.

A legtöbb modern programozási nyelv nem használja ki ezeket a funkciókat, inkább a hagyományos feltételes utasításokra hagyatkozik if...then...else..., így a fordító feladata, hogy haszontalan kódot vonjon ki a fordítandó programból.

Digráfok és trigráfok

Lásd a digráfokat és trigráfokat C/C++ nyelveken.

Az előfeldolgozó feldolgozza a „ %:” (“ #”), „ %:%:” (“ ##”) digráfusokat és a „ ??=” (“ #”), „ ??/” (“ \” trigráfokat).

Az előfeldolgozó a " %:%: " sorozatot két tokennek tekinti a C kód feldolgozása során, és egy tokennek a C++ kód feldolgozásakor.

Lásd még

Jegyzetek

Linkek