Az elõfeldolgozónak szintén lehetnek "változói", ezeket szimbólumoknak, illetve makróknak nevezzük, és ugyanaz a képzési szabály vonatkozik rájuk, mint más azonosítókra. Azért, hogy a preprocesszor számára definiált szimbólumok a C forrásnyelvi szövegben élesen különváljanak a programban használt azonosítóktól, szokás szerint a szimbólumokat csupa nagybetûvel képezzük. Szimbólumokat a következõ parancssal hozhatunk létre:
#define szimbólum helyettesítendõ szöveg
A három fõ részt tetszõleges számú, min. 1 db. szóköz és/vagy tabulátor választja el. Az elõfeldolgozó minden beérkezõ sort átvizsgál, tartalmaz-e korábban definiált szimbólumot. Ha igen, akkor azt lecseréli a megfelelõ helyettesítõ karaktersorozatra, és újból átvizsgálja a sort szimbólumokat keresve, amit új helyettesítés követhet, stb. Mindaddig folytatódik ez a folyamat, amíg vagy nem talál a sorban szimbólumot, vagy csak olyat talál, ami már egyszer helyettesítve lett (a végtelen rekurziók elkerülésére). Példák szimbólumdefinícióra: |
#define EOS '\0' #define TRUE 1 #define FALSE 0 #define YES TRUE #define bool int #define MENUCOL 20 #define MENULINE 5 #define BORDER 2 #define MENUROW (MENULINE+BORDER)Az elsõ három példában szereplõ szimbólumokra szinte minden C programban szükség van. Figyeljük meg, hogy a YES-t a TRUE segítségével definiáltuk, ami kettõs helyettesítéssel válik majd 1-gyé. A következõkben egy lehetõséget mutatunk arra, hogyan lehet a C nyelv alapszavait kibõvíteni: bár explicit logikai típus a nyelvben nem létezik, mi létrehozhatunk egy új "alapszót", a bool-t, amit azután természetesen éppúgy használhatunk, mint az eredeti int-et (hiszen úgyis arra cserélõdik le), de használatával az egyes változók szerepét jobban kidomboríthatjuk. Az alapszó jelleg hangsúlyozása érdekében használtunk ennél a szimbólumnál kisbetûket. A további definíciók a szimbólumok leggyakrabban használt területét mutatják be: legfõbb hasznuk az, hogy a különbözõ "bûvkonstansokat" névvel ellátva egy helyre gyûjthetjük össze, világosabbá téve használatukat és megkönnyítve módosításukat. Az utolsó példát annak illusztrálására hoztuk fel, hogy milyen fontos szem elõtt tartani azt, hogy szöveghelyettesítésrõl van szó. Ha a látszólag felesleges zárójelpárt elhagynánk, akkor a következõ sorból
#define MENUSIZE (MENUROW*MENUCOL)az alábbi keletkezne
#define MENUSIZE (MENULINE+BORDER * MENUCOL)ami végeredményben a kívánt 140 helyett 45-öt eredményezne mindenütt, ahol MENUSIZE-t használjuk. Mivel a felesleges zárójelek bajt nem okozhatnak, szokjuk meg, hogy minden definícióban szereplõ kifejezést zárójelezünk.
A makrók lehetõvé teszik azt, hogy a szöveghelyettesítés paraméterezhetõ legyen. A paraméterek számára nincs elvi korlát megadva. Példák makródefinícióra:
#define abs(x) ((x) < 0 ? (-(x)) : (x)) #define min(a, b) ((a) < (b) ? (a) : (b))(Az itt szereplõ, ún. feltételes kifejezéseket a megfelelõ helyen tárgyalni fogjuk. Ha a ? elõtt álló kifejezés logikai értéke igaz, akkor a ? és a : között álló, egyébként a : után álló kifejezés szolgáltatja az eredményt.) Figyeljük meg, hogy - szintén a szöveghelyettesítést szem elõtt tartva - makrók esetében nemcsak a helyettesítõ kifejezést, de a helyettesítendõ paramétereket is zárójelekkel védjük. Az elsõ példában szereplõ makró alábbi alakú hívása
alfa = abs(y + 2);így a következõ sort eredményezi
alfa = ((y + 2) < 0 ? (-(y + 2)) : (y + 2));Ha y értéke -3, akkor alfa-ba 1 kerül, mint az várható. Ha azonban a definíció helyettesítõ részében az x-et védõ zárójeleket elhagytuk volna, akkor alfa 5-öt kapna értékül. Itt hívjuk fel a figyelmet arra is, hogy alfa kiszámításához a fenti esetben az y+2 értékét kétszer kell meghatározni. Egyszer a feltétel kiértékelésekor, aztán az eredmény meghatározásához. Ez komplikáltabb kifejezés esetében a program hatékonyságát jelentõsen ronthatja. Nagyobb bajt okoz azonban az, hogy a C-ben bizonyos kifejezéseknek ún. mellékhatásuk is lehet (például a kifejezés tartalmazhatja olyan függvény meghívását, ami a kívánt érték kiszámítása mellett, azt ki is írja a képernyõre), és a többszöri kiértékelés a mellékhatás többszöri jelentkezését vonja maga után (példánknál a részeredmény kétszer kerül kiírásra). Az ilyen makrók használatánál ezért célszerûbb az adott kifejezést egy ideiglenes változóba helyezni, és azzal meghívni a makrót. A makrók használatának szintaxisa, mint majd látni fogjuk, megegyezik a függvények hívásának szintaxisával. Ez nem véletlen, ugyanis több könyvtári "függvényt" például sok C rendszer makróként valósít meg (ilyen "függvény" a pelda.c-ben használt getchar() is), ami a használatát nem befolyásolja - a fent említett ritka kivételektõl eltekintve, mint például toupper(), tolower(), min(), max(), stb. - ugyanakkor a rutinhívások elmaradása miatt gyorsabb kódot eredményez. Felhasználhatók a makrók arra is, hogy a program egy új telepítésénél az adott rendszerben esetleg eltérõ nevû, vagy paraméterezésû könyvtári függvényeket segítségükkel a korábbi alakban használhassuk.
Ha egy szimbólum vagy makró által lefoglalt azonosítót fel szeretnénk szabadítani, azt az
#undef szimbólum
utasítással tehetjük meg. Ezt követõen
az elõfeldolgozó a szimbólum (makró) további
elõfordulásait nem kíséri figyelemmel. Természetesen
egy újabb #define utasítással újradefiniálhatjuk
ezt a szimbólumot is.