A C rendelkezik egy előfeldolgozóval, amely a tényleges fordítás előtt műveletekez végez a forrásfájlokon. Ezen előfeldolgozónak saját nyelvtana van és saját utasításai. Az előfeldolgozó utasításai kettőskereszt karakterrel (#) kezdődnek.
Az előfeldolgozó egyszerű mintabehelyettesítést végez. Használható konstansok és makró függvények definiálására, valamint header fájlok beimportálására. Ez utóbbival már találkoztunk, minden eddigi C programunkban a #include formájában.
Konstansok definiálása a #define utasítással történik, az alábbi formában:
#define AZONOSITO érték
Az AZONOSITO nevet nagybetűvel szokás megadni, hogy elkülönüljön a név a változók neveitől. Ez nem kötelező, de erősen ajánlott. További fontos részlet, hogy az előfeldolgozó utasítások után nem kell pontosvessző.
A #if, #ifdef, #ifndef, #else, #elif és #endif utasításokkal ellenőrizhető egy azonosító definiáltsága vagy nem definiáltsága. Ezzel megoldható az, hogy bizonyos utasítások csak akkor kerüljenek bele egy programba, ha az azonosítót definiáltuk vagy nem. Ezt nevezik feltételes fordításnak.
Példa:
#define AZONOSITO
#ifdef AZONOSITO
/*ez a blokk belefordítódik a programba, mert az
AZONOSITO definiálva lett.*/
#endif
#ifndef BELA
/*ez az utasítás is belefordul, mivel BELA nincs definiálva.*/
#endif
Az #ifdef utasítás helyettesíthető #if defined(AZONOSÍTÓ) formában.
A makrók nem valódi C függvények, mivel a hívás helyére az előfeldolgozó egyszerű mintaillesztéssel másolja be a makró függvény utasításait. Így a valóságban nem történik függvényhívás, gyorsabb lesz a program, ám a kimeneti kód mérete növekszik (valamit valamiért).
Makrók definiálásakor ügyelni kell a paraméterek zárójelezésére a feldolgozó szöveg illesztéses mivolta miatt. Ez gyakorlatban azt jelenti, hogy az alábbi példarészlet által definiált két makró függvény nem ugyanazt az eredményt fogja visszaadni:
#define MAX(a,b) (a < b ? b : a)
#define MAX(a,b) ((a < b) ? (b) : (a))
A két definíció közül az utóbbi fog helyesen működni. További példa:
#define RADTODEG(x) ((x) * 57.29578)
#define ABS(x) (((x) < 0) ? -(x) : (x))
A makrók használatának akkor van értelme, ha egy egy soros (Max, Min), abszolút nem bonyolult kifejezést szeretnénk definiálni, amit nem szeretnénk kitenni függvénybe. A függvényhívásnak van költsége, de ez a költség elhanyagolható a modern processzorok esetén, ezért a bonyolultabb kódrészleteket ne makróban implementáljuk le, mert egy átláthatatlan és rosszul hibakereshető kódot kapunk.
Makrók nevezéktanában a snake case is tipikusan alkalmazott, mivel javítani tudja az olvashatóságot. Snake case-el leírva a korábbi RADTODEG makró RAD_TO_DEG lenne.
Fordító, operációs rendszer és architektúra detektálása
Ha igazán hordozható kódot szeretnénk írni, akkor előbb-utóbb szükségünk lesz a használt fordító vagy a nyelvi verzió detektálására. Ideális esetben erre semmi szükség nem lenne, de egyes fordítók rendelkeznek nem szabványos megoldásokkal, amiket érdemes lehet kihasználni, vagy éppen nem támogatnak bizonyos nyelvi verzióban megjelent funkciót.
A fordító és nyelvi verzió azonosítás megkönnyítése érdekében minden fordító definiál előfeldolgozó szimbólumokat, amelyek segítségével univerzálisan hordozható kódot írhatunk.
Architektúra definiáló azonosítók:
| Preprocesszor szimbólum | Architektúra neve |
|---|---|
i386 vagy __i386 |
X86 GNU C esetén |
_M_IX86 |
X86 visual studio esetén |
__amd64__ vagy __amd64 |
X64 GNU C esetén |
_M_AMD64 |
X64 visual studio esetén |
__arm__ |
ARM 32 bit GNU C esetén |
_M_ARM |
ARM 32 bit visual studio esetén |
__aarch64__ |
ARM 64 bit GNU C esetén |
_M_ARM64 |
ARM 64 bit visual studio esetén |
Fordító azonosító szimbólumok:
| Preprocesszor szimbólum | Fordító |
|---|---|
_MSC_VER |
Visual Studio |
__GNUC__ |
GCC |
__clang__ |
Clang |
Operációs rendszer szimbólumok:
| Preprocesszor szimbólum | Operációs rendszer |
|---|---|
_WIN32 |
32 és 64 bites Windowson definiált |
_WIN64 |
64 bites Windowson definiált |
__linux__ |
Linux |
__APPLE__ && __MACH__ |
MAC OS X |
Az alábbi példa egy olyan kódrészletet mutat be, ami csak 32 bites ARM-on Linux alatt kerül bele a binárisba:
#if (defined(_linux__) && defined(__arm__))
printf("Linux ARM\r\n");
#endif
Az egyes fordítók által definiált szimbólumok listájáról egy átfogó leírás a https://github.com/cpredef/predef/tree/master címen található.