Preprocesszor¶
A preprocesszornak köszönhető a rugalmas konfiguráció-kezelés, a forrásfájlok hierarchiába rendezése include-ok segítségével, illetve a szöveg-alapú makrók hasznossága is.
Az alapvető probléma a preprocesszorral az, hogy a fordító a feldolgozott forráskódot kapja meg, és nem az eredeti forráskódot, amit a fejlesztő lát. Sok esetben a két forráskód nagy mértékben eltér. Ezek az eltérések megnehezítik a program megértését a fejlesztők számára, emellett gondot okoznak a program megértést támogató eszközöknél is, pl. debugger. Sok mindent túlzásba lehet vinni, és ezt a fejlesztők gyakran meg is teszik.
A preprocesszor nyelve más, mint a C/C++ nyelv. A preprocesszor alapvetően egy szövegmanipuláló eszköz, nem "érti" mik azok a típusok, osztályok stb, nem ismeri a C nyelv elemeit, szintaktikáját. Ez nagyon rugalmassá teszi a működését, de gondokat is okozhat, pl a makrók nem biztos hogy értelmes kifejezésekké alakulnak C/C++ nyelven. Megjegyzés: más nyelvekhez kapcsolódva is használnak a nagyvilágban preprocesszort (pl. php-hez előfeldolgozásként).
A preprocesszor három legfontosabb eszköze a fájl beszúrás (include), a makrók és a feltételes fordítás. A továbiakban ezeket röviden bemutatjuk és megnézünk néhány gyakorlati módszert, amelyek általában ezek együttes használatával működnek jól.
Az include direktíva¶
Segítségével tudunk fájlokat beszúrni, a megjelölt fájl tartalmát a direktíva sora helyett szúrja be.
1 2 3 |
|
A preprocesszálás során a fordító/preprocesszor konfigurációja tartalmaz egy listát a standard headerekről, include esetén ezeket a könyvtárakat nézi a preprocesszor és úgy keresi meg az adott fáljt. Ha speciálisan mi szeretnénk megadni egy-egy include útvonalat a fordítás során, akkor ezt megtehetjük a -I
kapcsoló használatával, amely után meg kell adnunk a kívánt útvonalat.
A következő parancs kiírja a gcc preprocesszora által használt útvonalakat az adott rendszeren:
1 |
|
Makrók¶
A makrók szöveges helyettesítést tesznek lehetővé a preprocesszálási fázisban.
1 |
|
A makrók értéke a hívás helyére kerül:
1 2 3 |
|
Preprocesszálás után ezen kód a következőképp néz ki:
1 2 |
|
A makrók definícióját visszavonhatjuk és újra definiálhatjuk őket:
1 2 3 4 5 |
|
Ekkor a preprocesszált állomány:
1 2 3 4 |
|
A makrók hatóköre nem egyezik meg a C/C++ hatókörökkel, az include direktívával behúzott fájlok újradefiniálhatják őket, akár sokadik include mélységben is.
Paraméteres makrók hívása esetén először a paraméterek helyettesítése történik meg és csak ezután kerül a hívás helyére az eredmény.
1 |
|
A makró nem tudja figyelembe venni a C/C++ típusokat, ezáltal rugalmasabb kódot írhatunk, mely több típusra is alkalmazható, lásd MULT makró. Azonban a makróknál különösen figyelni kell arra, hogy a helyettesítés szöveg alapon működik, az új környezetben nem mindig a kívánt hatást érjük el:
1 2 |
|
Javítva:
1 2 |
|
Paraméteres makrók esetén az argumentumot konkatenálhatjuk és sztringgé alakíthatjuk:
1 2 |
|
1 2 |
|
1 2 |
|
Változó paraméterlista is megadható (variadic macros):
1 2 |
|
Léteznek előre definiált makrók, melyeket a preprocesszor biztosít, és akár dinamikusan változhatnak is futás közben (pl. LINE aktuális programsor sorszámát helyettesíti be híváskor).
- DATE The current date as a character literal in "MMM DD YYYY" format.
- TIME The current time as a character literal in "HH:MM:SS" format.
- FILE This contains the current filename as a string literal.
- LINE This contains the current line number as a decimal constant.
- STDC Defined as 1 when the compiler complies with the ANSI standard.
Feltételes fordítás¶
A fordítási egység összeállításánál nem csak fájlonként, hanem soronként is szabályozni tudjuk, mit kapjon meg a fordító: #if #ifdef #ifndef #else #elif #endif. Adott esetben egyes programrészleteket így ki tudunk egyszerűen venni a forráskódból, és ezzel már a fordítónak nem is kell foglalkoznia.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Egyéb direktívák¶
A pragma direktíva implementáció függő, adatokat szolgáltat a fordítási folyamathoz. Ha a preprocesszor/fordító ismeri, akkor végrehajtja, ha nem ismeri akkor figyelmen kívül hagyja.
1 2 3 |
|
Az error direktíva hatására megáll a folyamat egy hibaüzenettel. Általában konfigurációk ellenőrzéséhez használják, pl:
1 2 3 |
|
Gyakorlati alkalmazások¶
Kommentelés¶
Ha többsoros kommentekkel együtt szeretnénk nagyobb részt ideiglenesen kikommentezni, akkor feltételes fordítással megtehető. (Csak ideiglenesen, verziókezelőbe ne kerüljön ilyen kód...)
1 2 3 4 5 6 7 8 |
|
Konfigurációk¶
Az adott fordítási környezet is definiál makrókat, melyeket konfigurációkhoz használhatunk. A linux kernel kódja rengeteg makrót és feltételes direktívát tartlamaz.
1 2 3 4 5 6 7 |
|
Például a GCC verzió szerint is el tudunk ágazni ezzel a frappáns megoldással:
1 2 3 4 5 |
|
Include védelem¶
Header fájlok többszörös include-ja azonnal fordítási hibához szokott vezetni, hiszen ekkor újradefiniálhatunk programelemeket, ezért eleve #ifndef - #define - #endif védelemmel látjuk el a header fájlokat:
1 2 3 4 |
|
Egy teljes fordítási folyamat során a sokszor használt standard headerek nagyon sokszor felhasználásra kerülhetnek, ezért még a fájl megnyitási és preprocesszálási idő is számíthat, ezért több preprocesszor is speciálisan kezeli ezt a jelenséget. Ha a fájl elején szerepel a
1 |
|
direktíva, akkor a headert többet nem is nyitja meg.
Megjegyzés: a gcc pragma once nélkül is optimalizál, elkerülve a felesleges munkát.
Bónusz kérdés: ha egy headert egy preprocesszálási folyamat során 1000-szer is include-olunk, előfordulhat-e hogy nem mindig ugyanaz lesz a tartalma? Igen, előfordul, a standard headerekben is van erre példa, a #undef FILE_NAME_H direktíva hatására a következő include már ismételten behúzhatja az adott headert.
Mellékhatás
Tegyük fel, hogy annyira vagány kódolók vagyunk, hogy az abszolút értéket is makróval implementáljuk:
1 |
|
1 2 |
|
Összegzés¶
A preprocesszor nélkül nehéz lenne az élet, de ha eltúlozzuk a használatát akkor lesz igazán nehéz. Az utóbbi években egyre több munka jelenik meg arról, hogy lehetne minél jobban visszaszorítani a használatát. Egy-egy tömör makró használatával kisebb lehet a kód, de sokszor csak az látja át, aki az egészet kitalálta. Mivel a makrók esetén nincsenek típusok, nyelvi elemek, így könnyen félrecsúszhat. Maga Bjarne Stroustrup, a C++ nyelv megalkotója is azon dolgozik, hogy a felesleges makróhasználatot a C++11-től felfelé visszaszorítsa .