KURZUS: Programozás alapjai
MODUL: III. modul
7. lecke: Előfeldolgozó (preprocessor)
Már eddig is jó hasznát vettük az előfeldolgozó #define és #include direktíváinak, de ennél valójában sokkal többre is képes. A teljesség igénye nélkül végignézzük az előfeldolgozó legfontosabb képességeit, megvizsgáljuk a direktívák alkalmazásának szabályait és az általuk nyújtott, érdekesebb lehetőségeket. Ezek közül az egyik a paraméteres #define, vagy más néven függvényszerű makrók készítésének lehetősége. Ezek ugyanúgy használhatóak egy programban, mint a függvények, de ha a kód egyszerű, ám sok hívásra kerül sor, akkor hatékonyabb megoldást kínálnak a függvényekhez képest. | |||||||||||||||||||
A lecke végére a hallgatónak tisztában kell lennie | |||||||||||||||||||
| |||||||||||||||||||
A lecke kulcsfogalmai: egyszerű és paraméteres makró, makródefiníció, makróazonosító, makrótest, makróhelyettesítés, feltételes fordítás. | |||||||||||||||||||
A direktívák használatának általános szabályai | |||||||||||||||||||
A fordító első menete során mindig meghívja az előfeldolgozót a forrásfájlra. | |||||||||||||||||||
Ha szeretnénk tudni, hogy fest az előfeldolgozáson átesett forrásfájl (a fordítási egység), akkor a programfejlesztő rendszerben utána kell nézni, hogy tehetjük láthatóvá az előfeldolgozás eredményét. Az előfeldolgozott forrásfájlban aztán megtekinthetjük: | |||||||||||||||||||
| |||||||||||||||||||
A nem karakter, vagy karakterlánc konstansban előforduló, egymást követő, több fehér karaktert mindig eggyel helyettesíti az előfeldolgozó. | |||||||||||||||||||
Az előfeldolgozó direktívák írásszabálya, mely független a C nyelv többi részétől, a következő: | |||||||||||||||||||
| |||||||||||||||||||
A teljes szintaktika az előfeldolgozó direktívákra a feltételes fordítástól eltekintve úgy, hogy a már megismert fogalmakat újra nem definiáljuk, a következő: | |||||||||||||||||||
csoport: | |||||||||||||||||||
csoport-rész: | |||||||||||||||||||
előfeldolgozó-szimbólumok: | |||||||||||||||||||
előfeldolgozó-szimbólum: | |||||||||||||||||||
vezérlő-sor: | |||||||||||||||||||
újsor: | |||||||||||||||||||
Az előfeldolgozó-szimbólum definíciójában a fájlazonosító körüli <> a szintaktika része, és nem az elhagyhatóságot jelöli, mint máskor. Látszik, hogy a feldolgozásban bármely karakter, ami nem foglalt az előfeldolgozónak, szintén szintaktikai egységet képez. | |||||||||||||||||||
Üres (null) direktíva | |||||||||||||||||||
A csak egy # karaktert tartalmazó sor. Ezt a direktívát elhagyja az előfeldolgozó, azaz hatására nem történik semmi. | |||||||||||||||||||
#include direktíva | |||||||||||||||||||
Az #include direktíva lehetséges alakjait: | |||||||||||||||||||
#include <fájlazonosító> újsor | |||||||||||||||||||
már az Alapismeretek 1/2 leckében részleteztük. Tudjuk, hogy a vezérlő-sort az előfeldolgozó a megadott fájl teljes tartalmával helyettesíti. Magát a fájlt "fájlazonosító" esetén előbb az aktuális könyvtárban (abban, ahonnét azt a fájlt töltötte, amelyikben ez az #include direktíva volt), majd és <fájlazonosító>-s esetben a programfejlesztő rendszerben előírt utakon keresi. | |||||||||||||||||||
A <> és az idézőjelek között nincs makróhelyettesítés. Ha a fájlazonosító-ban >, ", ', \, vagy /* karakterek vannak, az előfeldolgozás eredménye meghatározatlan. Ha macskakörmös esetben a fájlazonosító elérési utat is tartalmaz, akkor a fájlt a preprocesszor csak abban a könyvtárban keresi, és sehol másutt. | |||||||||||||||||||
A direktíva | |||||||||||||||||||
#include előfeldolgozó-szimbólum újsor | |||||||||||||||||||
formáját előbb feldolgozza az előfeldolgozó, de a helyettesítésnek itt is <>, vagy "" alakot kell eredményeznie, s a hatás ennek megfelelő. Például: | |||||||||||||||||||
#define ENYIM "\Cfajlok\Munka16\Pipo.h" | |||||||||||||||||||
Az #include direktívák egymásba ágyazhatók, azaz a behozott fájl újabb #include-okat tartalmazhat, s azok megint újabbakat, és így tovább. Az egymásba ágyazgatásokkal azonban vigyázni kell, mert egyes programfejlesztő rendszerek ezek szintjét korlátozhatják! | |||||||||||||||||||
Egyszerű #define makró | |||||||||||||||||||
A #define direktíva makrót definiál (makródefiníció). A makró szimbólumhelyettesítő mechanizmus függvényszerű formális paraméterlistával vagy anélkül. | |||||||||||||||||||
Előbb a paraméter nélküli esettel foglalkozunk! Ilyenkor a direktíva alakja: | |||||||||||||||||||
#define azonosító <előfeldolgozó-szimbólumok> újsor | |||||||||||||||||||
Hatására az előfeldolgozó a forráskódban ez után következő minden makróazonosító előfordulást helyettesít a lehet, hogy üres előfeldolgozó-szimbólumokkal. Ha üres előfeldolgozó-szimbólumokkal történik a helyettesítés, a makróazonosító akkor is definiált, azaz #if defined vagy #ifdef direktívával "rákérdezhetünk" az azonosítóra, de a makróazonosító minden forrásszövegbeli előfordulásának eltávolítását jelenti tulajdonképp. Nem történik meg a helyettesítés, ha a makróazonosító karakter vagy karakterlánc konstansban, vagy megjegyzésben, vagy más makróazonosító részeként található meg. Ezt a folyamatot makrókifejtésnek (expansion) nevezik. Az előfeldolgozó-szimbólumokat szokás makrótest névvel is illetni. Például: | |||||||||||||||||||
#define HI "Jó napot!" | |||||||||||||||||||
A makrókifejtés utáni makróazonosítókat is helyettesíti a preprocesszor, azaz a makrók is egymásba ágyazhatók. | |||||||||||||||||||
A makróazonosító újradefiniálása csak akkor nem hiba, ha az előfeldolgozó-szimbólumok tökéletesen, pozíció-helyesen azonosak. Ettől eltérő újradefiniálás csak a rávonatkozó #undef direktíva után lehetséges. | |||||||||||||||||||
A nyelv kulcsszavait is alkalmazhatjuk makródefiníciókban, legfeljebb kissé értelmetlennek tekinthető az eljárásunk: | |||||||||||||||||||
#define LACI for | |||||||||||||||||||
de a következő fejezetben ismertetett, szabványos, előredefiniált makrók nem jelenhetnek meg sem #define, sem #undef direktívákban. | |||||||||||||||||||
Előredefiniált makrók | |||||||||||||||||||
Néhány makró előredefiniált az előfeldolgozó rendszerben, s kifejtetésükkel speciális információ képezhető. Ezek a makrók mind defined típusúak. Nem tehetők definiálatlanná, és nem definiálhatók újra. A szabványos, előredefiniált makrók és jelentésük a következő: | |||||||||||||||||||
| |||||||||||||||||||
#undef direktíva | |||||||||||||||||||
#undef azonosító újsor | |||||||||||||||||||
A direktíva definiálatlanná teszi a makróazonosítót, azaz a továbbiakban nem vesz részt a makrókifejtésben. | |||||||||||||||||||
Azt, hogy egy makróazonosító definiált-e vagy sem a forráskódban, megtudhatjuk a | |||||||||||||||||||
#ifdef azonosító | |||||||||||||||||||
direktívák segítségével, azaz a makródefiníciónál ajánlható a következő stratégia: | |||||||||||||||||||
#ifndef MAKROKA | |||||||||||||||||||
Az ismeretlen makróazonosítóra kiadott #undef direktívát nem tekinti hibának az előfeldolgozó. | |||||||||||||||||||
A definiálatlanná tett makróazonosító később újradefiniálható akár más előfeldolgozó-szimbólumokkal. A #define-nal definiált és közben #undef-fel definiálatlanná nem tett makróazonosító definiált marad a forrásfájl végéig. Például: | |||||||||||||||||||
#define BLOKK_MERET 512 | |||||||||||||||||||
Paraméteres #define direktíva | |||||||||||||||||||
A direktíva alakja ilyenkor: | |||||||||||||||||||
#define azonosító(azonosítólista) előfeldolgozó-szimbólumok újsor | |||||||||||||||||||
Az azonosítólista egymástól vesszővel elválasztott formális paraméterazonosítókból áll. A makrót hívó aktuális paraméterlistában ugyanannyi paraméternek kell lennie, mint amennyi a formális paraméterlistában volt, mert különben hibaüzenetet kapunk. | |||||||||||||||||||
Ugyan a makróra is a függvénnyel kapcsolatos fogalmakat használjuk képszerűségük végett, de ki kell hangsúlyozni, hogy a makró nem függvény! A makróazonosító és a paraméterlistát nyitó kerek zárójel közé pl. semmilyen karakter sem írható, hisz rögtön egyszerű #define-ná tenné a paraméteres direktívát! | |||||||||||||||||||
Az előfeldolgozó előbb a makróazonosítót helyettesíti, s csak aztán a zárójelbe tett paramétereket: | |||||||||||||||||||
Definíció: #define KOB(x) ((x)*(x)*(x)) | |||||||||||||||||||
A látszólag redundáns zárójeleknek nagyon fontos szerepük van: | |||||||||||||||||||
Definíció: #define KOB(x) (x*x*x) | |||||||||||||||||||
A külső zárójelpár a kifejezésekben való felhasználhatóságot biztosítja: | |||||||||||||||||||
Definíció: #define SUM(a,b) (a)+(b) | |||||||||||||||||||
A zárójelbe, vagy aposztrófok, idézőjelek közé tett vesszők nem minősülnek a listában azonosító elválasztónak: | |||||||||||||||||||
Definíció: #define SUM(a,b) ((a)+(b)) | |||||||||||||||||||
Folytatássor most is sorvégi \ jellel képezhető: | |||||||||||||||||||
Definíció: #define FIGYU "Ez igazából egyetlen \ | |||||||||||||||||||
A makró nem függvény, tehát semmilyen ellenőrzés sincs a paraméterek adattípusára! Ha az aktuális paraméter kifejezés, akkor kiértékelése többször is megtörténik: | |||||||||||||||||||
int kob(int x){ return x*x*x;} | |||||||||||||||||||
Karaktervizsgáló függvények (makrók) | |||||||||||||||||||
Megismerkedtünk az előző fejezetekben a makrók előnyös, és persze hátrányos tulajdonságaival. Mindezek dacára a makrók használata a C-ben elég széleskörű. (Nézzük csak meg a szabványos stdio.h fejfájlt, s látni fogjuk, hogy a szabvány bemenetet és kimenetet kezelő getchar és putchar rutinok makrók!) | |||||||||||||||||||
A szabványos ctype.h fejfájlban deklarált függvények karakter osztályozást végeznek. A rutinok c paramétere int ugyan, de az értékének unsigned char típusban ábrázolhatónak, vagy EOF-nak kell lennie. (Egyes fordítók olyan futtatható állományt generálhatnak, melyek diagnosztikai üzenettel (assertion) leállítják a futást, ha ez a feltétel nem teljesül. Ékezetes betűk kódjai nagy valószínűséggel kívül fognak esni az engedélyezett intervallumon.) A visszatérési értékük ugyancsak int logikai jelleggel, azaz nem zérus (igaz), ha a feltett kérdésre adott válasz igen, ill. zérus (hamis), ha nem. | |||||||||||||||||||
A teljesség igénye nélkül felsorolunk néhány karakterosztályozó függvényt! | |||||||||||||||||||
| |||||||||||||||||||
Meg kell még említeni két konverziós rutint is: | |||||||||||||||||||
int tolower(c); | |||||||||||||||||||
melyek c értékét kisbetűvé (tolower), ill. nagybetűvé (toupper) alakítva szolgáltatják, ha c betű karakter, de változatlanul adják vissza, ha nem az. | |||||||||||||||||||
Írjuk át pelda17.c- t úgy, hogy karaktervizsgáló függvényeket használjon! | |||||||||||||||||||
/* PELDA19.C: A bemenet karaktereinek leszamlalasa kategoriankent | |||||||||||||||||||
Írjuk még át ugyanebben a szellemben a pelda18.c egesze függvényét is! | |||||||||||||||||||
#include <ctype.h> | |||||||||||||||||||
A pelda13.c-beli strup rutin így módosulna: | |||||||||||||||||||
#include <ctype.h> | |||||||||||||||||||
Milyen előnyei vannak a karakterosztályozó függvények használatának? | |||||||||||||||||||
| |||||||||||||||||||
Az olvasó utolsó logikus kérdése már csak az lehet, hogy miért pont a makrók között tárgyaljuk a karaktervizsgáló rutinokat? Több C implementáció makróként valósítja meg ezeket a függvényeket. Erre mutatunk itt be egy szintén nem teljes körű példát azzal a feltétellel, hogy a CHAR_BIT (bitek száma a char típusban - lásd limits.h- t!) makró értéke 8. | |||||||||||||||||||
/* Bitmaszk értékek a lehetséges karaktertípusokra: */ | |||||||||||||||||||
Ha az olvasóban felmerült volna az a gondolat, hogy mi van akkor, ha ugyanolyan nevű makró és függvény is létezik, akkor arra szeretnénk emlékeztetni, hogy: | |||||||||||||||||||
| |||||||||||||||||||
Feltételes fordítás | |||||||||||||||||||
feltételes-fordítás: | |||||||||||||||||||
if-csoport: | |||||||||||||||||||
elif-csoportok: | |||||||||||||||||||
elif-csoport: | |||||||||||||||||||
else-csoport: | |||||||||||||||||||
endif-sor: | |||||||||||||||||||
újsor: | |||||||||||||||||||
A feltételes direktívák szerint kihagyandó forrássorokat az előfeldolgozó törli a forrásszövegből, s a feltételes direktívák sorai maguk pedig kimaradnak az eredmény fordítási egységből. A feltételes direktívák által képzett konstrukciót - melyet rögtön bemutatunk egy általános példán - mindenképpen be kell fejezni abban a forrásfájlban, amelyben elkezdték. | |||||||||||||||||||
#if konstans-kifejezés1 | |||||||||||||||||||
Lássuk a kiértékelést! | |||||||||||||||||||
| |||||||||||||||||||
Az #if . . . #endif konstrukciók tetszőleges mélységben egymásba ágyazhatók. | |||||||||||||||||||
Az #if . . . #endif szerkezetbeli konstans-kifejezéseknek korlátozott, egész típusúaknak kell lenniük! Konkrétabban egész konstanst, karakter állandót tartalmazhat a kifejezés, és benne lehet a defined operátor is. Tilos használni viszont benne explicit típuskonverziót, sizeof kifejezést, enumerátort és lebegőpontos konstanst, mint normál egész típusú konstans kifejezésekben! Az előfeldolgozó közönséges makróhelyettesítési menettel dolgozza fel a konstans-kifejezéseket. | |||||||||||||||||||
A feltételes fordítás jó szolgálatot tehet, ha olyan programrészeket kell készíteni, melyek portábilis módon nem valósíthatóak meg, csak pl. operációs rendszerenként eltérő módon. Ekkor minden támogatott rendszer kódját belefoglalják a forrásszövegekbe, de a ténylegesen a programba belefordítandó részt egy makró létezésével, vagy megfelelő értékűre állításával választják ki. | |||||||||||||||||||
A defined operátor | |||||||||||||||||||
Makróazonosítók definiáltságának ellenőrzésére való, s csak #if és #elif konstans-kifejezéseiben szerepelhet. A | |||||||||||||||||||
defined(azonosító) | |||||||||||||||||||
vagy a | |||||||||||||||||||
defined azonosító | |||||||||||||||||||
alak a makróazonosító definiáltságára kérdez rá. Miután a válasz logikai érték a defined szerkezetek logikai műveletekkel is kombinálhatók a konstans-kifejezésekben. Például: | |||||||||||||||||||
#if defined(makro1) && !defined(makro2) | |||||||||||||||||||
Ha biztosítani szeretnénk azt, hogy a fordítási egységbe egy bizonyos fejfájl (legyen header.h) csak egyszer épüljön be, akkor a fejfájl szövegét következőképp kell direktívákba foglalni: | |||||||||||||||||||
#if !defined(_HEADERH) | |||||||||||||||||||
Ilyenkor akárhány #include is jön a forrásfájlban a header.h fejfájlra, a behozatala csak először történik meg, mert a további bekapcsolásokat a _HEADERH makró definiáltsága megakadályozza. | |||||||||||||||||||
Az #ifdef és az #ifndef direktívák | |||||||||||||||||||
Az #ifdef direktíva egy makróazonosító definiáltságára, s az #ifndef viszont a definiálatlanságára kérdez rá, azaz: | |||||||||||||||||||
#ifdef azonosító ? #if defined(azonosító) | |||||||||||||||||||
#line sorvezérlő direktíva | |||||||||||||||||||
#line egész-konstans <"fájlazonosító"> újsor | |||||||||||||||||||
Jelzi az előfeldolgozónak, hogy a következő forrássor egész-konstans sorszámú, és a fájlazonosító nevű fájlból származik. Miután az aktuálisan feldolgozás alatt álló forrásfájlnak is van azonosítója a fájlazonosító paraméter elhagyásakor a #line az aktuális fájlra vonatkozik. | |||||||||||||||||||
A makrókifejtés a #line paramétereiben is megtörténik. | |||||||||||||||||||
Vegyünk egy példát! | |||||||||||||||||||
/* PELDA.C: a #line direktívára: */ | |||||||||||||||||||
Az előállított standard kimenet a következő lehet: | |||||||||||||||||||
#line 1 "pelda.c" | |||||||||||||||||||
A #line direktíva tulajdonképpen a __FILE__ és a __LINE__ előredefiniált makrók értékét állítja. Ezek a makróértékek a fordító hibaüzeneteiben jelennek meg. Szóval a direktíva diagnosztikai célokat szolgál. | |||||||||||||||||||
#error direktíva | |||||||||||||||||||
#error <hibaüzenet> újsor | |||||||||||||||||||
direktíva üzenetet generál, és befejeződik a fordítás. Az üzenet alakja lehet a következő: | |||||||||||||||||||
Error: fájlazonosító sorszám: Error directive: hibaüzenet | |||||||||||||||||||
Rendszerint #if direktívában használatos. Például: | |||||||||||||||||||
#if (SAJAT!=0 && SAJAT!=1) | |||||||||||||||||||
#pragma direktívák | |||||||||||||||||||
#pragma <előfeldolgozó-szimbólumok> újsor | |||||||||||||||||||
A direktívák gép és operációs rendszerfüggők. Bennük a #pragma kulcsszót követő szimbólumok mindig objektumai a makrókifejtésnek, és tulajdonképpen speciális fordítói utasítások, s ezek paraméterei. Az előfeldolgozó a fel nem ismert #pragma direktívát figyelmen kívül hagyja. |
Feladatok | |||||||||||||
1. Létre szerettünk volna hozni egy olyan void atalakit(char forras[], char cel[]) függvényt, ami a forras-ban adott mondat szavaiból Camel Case formázású azonosítót állít elő, és ezt elhelyezni a cel-ban. Az eredmény lánc az eredetiből úgy keletkezik, hogy a fehér karaktereket elhagyják, minden szó első betűjét nagyra, a többit kicsire cserélik, kivéve a legelső szót, melynek kezdőbetűje is kicsi. Pl. "Nagyon hosszu mondat" -> "nagyonHosszuMondat". | |||||||||||||
/*01*/#include <ctype.h> | |||||||||||||
Jelölje meg, mely sorok felhasználásával lehetne működőképessé tenni hibás függvényünket!
![]() | |||||||||||||
2. Olyan függvényt szerettünk volna létrehozni, amely eldönti a paraméterként kapott karakterláncról, hogy formailag Neptun kódnak tekinthető-e, és logikai értékkel válaszol. A Neptun kódtól elvárjuk, hogy pontosan hat jelből álljon, csak nagybetűket és számjegyeket tartalmazzon. Sajnos nem jártunk sikerrel. | |||||||||||||
/*1*/#include <ctype.h> | |||||||||||||
Jelölje meg azokat a sorokat, amellyel a függvény működőképessé tehető!
![]() | |||||||||||||
3. Készítsen olyan függvényt, amely megállapítja a paraméterként átadott karakterláncról, hogy érvényes nevet tartalmaz-e! A névtől azt várjuk, hogy 2 vagy 3 szóból (vezeték és keresztnevek) álljon, a szavak nagybetűkkel kezdődjenek, majd kisbetűkkel folytatódjanak, egymás mellett ne álljon több szóköz, és ne is kezdődjön, ne is fejeződjön be a név szóközzel! | |||||||||||||
#include <ctype.h> | |||||||||||||
4. Készítsen olyan paraméteres makrót, ami a paraméter karakterláncról eldönti, hogy egy sakktábla mezőjét tartalmazza-e! A karakterlánc akkor formailag helyes, ha pontosan két karakterből áll, első karaktere az a és h közé eső (ezeket is beleértve) kis- vagy nagybetű, a második karaktere pedig az 1 és 8 közé eső számjegy karakter. | |||||||||||||
#define sakke(s) (((s)[0]>='A'&&(s)[0]<='H'||(s)[0]>='a'&&(s)[0]<='h')\ |