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

  • Egyszerű és paraméteres makrók létrehozásának módjával.
  • Fejfájlok többszöri bekapcsolásának elkerülésére feltételes fordítással.
  • Ismernie kell a legfontosabb karakter osztályozó és konverziós makrókat.
  • A makrókifejtés szabályaival, módjával.

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 makrók kifejtését,
  • a behozott (include) fájlokat,
  • a feltételes fordítást,
  • a szomszédos karakterlánc konstansok egyesítését,
  • a direktívák elhagyását és
  • a megjegyzések kimaradását (helyettesítését egyetlen szóköz karakterrel).

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 sor első, nem fehér karakterének #-nek kell lennie.
  • A #-et követheti aztán fehér karakter a soremelést kivéve. A sorokra tördelés nagyon lényeges elem, mert az előfeldolgozó sorokra bontva elemzi a forrásszöveget.
  • A karakter konstansban, a karakterlánc konstansban és a megjegyzésben levő # karakter nem minősül előfeldolgozó direktíva kezdetének.
  • A direktívákat - miután nem C utasítások - tilos pontosvesszővel lezárni!
  • Ha a direktívában a soremelést \ karakter előzi meg, akkor a következő sor folytatássornak minősül, azaz az előfeldolgozó elhagyja a \-t és a soremelést, s egyesíti a két sort.
  • Az előfeldolgozó direktívákba megjegyzés is írható.
  • Az előfeldolgozó direktívák bárhol elhelyezkedhetnek a forrásfájlban, de csak a forrásfájl ezt követő részére hatnak, egészen a fájl végéig.

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
  csoport csoport-rész

csoport-rész:
  előfeldolgozó-szimbólumok újsor
  feltételes-fordítás
  vezérlő-sor

előfeldolgozó-szimbólumok:
  előfeldolgozó-szimbólum
  előfeldolgozó-szimbólumok előfeldolgozó-szimbólum

előfeldolgozó-szimbólum:
  <fájlazonosító> (csak #include direktívában)
  "fájlazonosító" (csak #include direktívában)
   azonosító (nincs kulcsszó megkülönböztetés)
  konstans
  karakterlánc-konstans
  operátor
  elválasztó-jel
  bármilyen nem fehér karakter, mely az előzőek egyike sem

vezérlő-sor:
  #include előfeldolgozó-szimbólumok újsor
  #define azonosító <előfeldolgozó-szimbólumok> újsor
  #define azonosító(azonosítólista) előfeldolgozó-szimbólumok újsor
  #undef azonosító újsor
  #line előfeldolgozó-szimbólumok újsor
  #error <előfeldolgozó-szimbólumok> újsor
  #pragma <előfeldolgozó-szimbólumok> újsor
  # újsor

újsor:
  soremelés

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
#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"
/* . . . */
#include ENYIM

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!"
#define begin {
#define end }
#define then
#define NIL ""
#define EGY 1
int main(void)
  begin /* Helyettesítés {-re. */
  int i=8, k=i+EGY;/* Csere k=i+1-re.*/
  puts(HI); /* puts("Jó napot!"); lesz belőle */
  puts(NIL); /* puts(""); lesz a sorból. */
  puts("then"); /* Nincs helyettesítés, mert a makróazonosító
                  karakterlánc konstansban van. */
  if(++i<k)
  /* A sor eleji then semmire helyettesítése, de a */
    then puts("Ez a then-ág!\n"); /* másik marad.*/
  else puts("Ez az else-ág!\n");
  return 0;
end /* Csere }-re. */

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
#define PAPI while
#define int long

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ő:

__DATE__HHH NN ÉÉÉÉ alakú karakterlánc, s az aktuális forrásfájl előfeldolgozása kezdetének dátuma. A HHH hárombetűs hónapnév rövidítés (Jan, Feb stb.). Az NN 1 és 31 közötti napszám, s így tovább.
__FILE__Karakterláncként a feldolgozás alatt álló forrásfájl azonosítóját tartalmazza. A makró változik #include és #line direktíva hatására, valamint ha a forrásfájl fordítása befejeződik.
__LINE__Decimális értékként a feldolgozás alatti forrásfájl aktuális sorának sorszáma. A sorszámozás 1-től indul. Módosíthatja például a #line direktíva is.
__STDC__Definiált és 1, ha ANSI kompatibilis fordítás van, máskülönben definiálatlan.
__TIME__OO:PP:MM alakú karakterlánc, s a forrásfájl feldolgozása megkezdésének idejét tartalmazza.
#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ó
#ifndef 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
  #define MAKROKA 162
#endif

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
/* . . . */
puff = BLOKK_MERET*blkszam; /* Kifejtés: 512*blkszam. */
/* . . . */
#undef BLOKK_MERET
/* Innét a BLOKK_MERET ismeretlen makróazonosító. */
/* . . . */
#define BLOKK_MERET 128
/* . . . */
puff = BLOKK_MERET*blkszam; /* Kifejtés: 128*blkszam. */
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))
Forrássor: n = KOB(y);
Kifejtve:  n = ((y)*(y)*(y));

A látszólag redundáns zárójeleknek nagyon fontos szerepük van:

Definíció: #define KOB(x) (x*x*x)
Forrássor: n = KOB(y + 1);
Kifejtve:  n = (y + 1*y + 1*y + 1);

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)
Forrássor: n = 14.5 * SUM(x*y, z-8);
Kifejtve:  n = 14.5 * (x*y)+(z-8);

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))
Forrássor: return SUM(f(i,j), g(k,l));
Kifejtve:  return ((f(i,j))+(g(k,l)));
Definíció: #define HIBA(x,lanc) hibaki("Hiba: ",x,lanc)
Forrássor: HIBA(2,"Üssön Enter-t, s Esc-t!");
Kifejtve:  hibaki("Hiba: ",2,"Üssön Enter-t, s Esc-t!");

Folytatássor most is sorvégi \ jellel képezhető:

Definíció: #define FIGYU "Ez igazából egyetlen \
sornak minősül!"
Forrássor: puts(FIGYU);
Kifejtve: puts("Ez igazából egyetlen sornak minősül!");

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;}
#define KOB(x) ((x)*(x)*(x))
/* . . . */
int b = 0, a = 3;
b = kob(a++); /* b == 27 és a == 4. */
a = 3;
b = KOB(a++); /* Kifejtve: ((a++)*(a++)*(a++)),
azaz b == 60 és a == 6. */
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!

Függvény Kérdés
islower(c)c kisbetű-e?
isupper(c)c nagybetű-e?
isalpha(c)islower(c) | isupper(c)
isdigit(c)c decimális számjegy-e?
isalnum(c)isalpha(c) | isdigit(c)
isxdigit(c)c hexadecimális számjegy-e?
isspace(c)c fehér karakter-e? (szóköz, soremelés, lapemelés, kocsi vissza, függőleges vagy vízszintes tabulátor)
isprint(c)c nyomtatható karakter-e?

Meg kell még említeni két konverziós rutint is:

int tolower(c);
int toupper(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
              az is... fuggvenyek segitsegevel. */
#include <stdio.h>
#include <ctype.h>
void main(void){
  short k, num=0, feher=0, egyeb=0;
  printf("Bemeneti karakterek leszamlalasa\n"
        "kategoriankent EOF-ig, vagy Ctrl+Z-ig.\n");
  while((k=getchar())!=EOF)
    if(isdigit(k)) ++num;
    else if (isspace(k)) ++feher;
    else ++egyeb;
  printf("Karakter szamok:\n----------------\n"
        "numerikus: %5hd\nfeher:    %5hd\n"
        "egyeb:    %5hd\n----------------\n"
        "ossz: %10ld\n", num, feher, egyeb,
        (long)num+feher+egyeb); }

Írjuk még át ugyanebben a szellemben a pelda18.c egesze függvényét is!

#include <ctype.h>
#define HSZ sizeof(int)/sizeof(short)*5
int egesze(char s[]){
  int i = 0, kezd;
  while(isspace(s[i])) ++i;
  if(s[i]=='+' || s[i]=='-') ++i;
  kezd=i; /* A számjegyek itt kezdődnek. */
  while(isdigit(s[i]) && i-kezd<HSZ) ++i;
  if(kezd==i || !isspace(s[i]) && s[i]!=0) return 0;
  else return 1; }

A pelda13.c-beli strup rutin így módosulna:

#include <ctype.h>
void strup(char s[]){
  int i;
  for(i=0; s[i]; ++i) s[i]=toupper(s[i]); }

Milyen előnyei vannak a karakterosztályozó függvények használatának?

  • A kód rövidebb, és ez által gyorsabb is.
  • A program portábilis lesz, hisz függetlenedik az ASCII (vagy más) kódtábla sajátosságaitól.

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: */
#define _UPPER 0x1 /* Nagybetű. */
#define _LOWER 0x2 /* Kisbetű. */
#define _DIGIT 0x4 /* Decimális számjegy. */
#define _SPACE 0x8 /* '\t','\r','\n','\v','\f' */
#define _PUNCT 0x10 /* Elválasztó-jel. */
#define _CONTROL 0x20 /* Vezérlő karakter. */
#define _BLANK 0x40 /* Szóköz. */
#define _HEX 0x80 /* Hexadecimális számjegy. */
/* Globális tömb, melyben a rendszer mindenegyes kódtábla
  pozícióra beállította ezeket a biteket: */
extern unsigned char _ctype[];
/* Néhány makró: */
#define islower(_c) (_ctype[_c]&_LOWER)
#define isupper(_c) (_ctype[_c]&_UPPER)
#define isalpha(_c) (_ctype[_c]&(_UPPER|_LOWER))
#define isdigit(_c) (_ctype[_c]&_DIGIT)
#define isalnum(_c) (_ctype[_c]&(_UPPER|_LOWER|_DIGIT))
#define isxdigit(_c) (_ctype[_c]&_HEX)
#define isspace(_c) (_ctype[_c]&(_SPACE|_BLANK))
#define isprint(_c) (_ctype[_c]&(_BLANK|_PUNCT|_UPPER|_LOWER|_DIGIT))

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:

  • Az előfeldolgozás mindig a fordítás előtt történik meg, s így mindenből makró lesz.
  • Ha #undef direktívával definiálatlanná tesszük a makrót, akkor attól kezdve csak függvény lesz a forrásszövegben.
  • Ha a hívásban redundáns zárójelbe zárjuk a makró vagy a függvény nevét, akkor az előfeldolgozó ezt nem fejti ki, tehát bizonyosan függvényhívás lesz belőle.

    ...(makrónév)(paraméterek)...
Feltételes fordítás

feltételes-fordítás:
  if-csoport <elif-csoportok> <else-csoport> endif-sor

if-csoport:
  #if konstans-kifejezés újsor <csoport>
  #ifdef azonosító újsor <csoport>
  #ifndef azonosító újsor <csoport>

elif-csoportok:
  elif-csoport
  elif-csoportok elif-csoport

elif-csoport:
  #elif konstans-kifejezés újsor <csoport>

else-csoport:
  #else újsor <csoport>

endif-sor:
  #endif újsor

újsor:
  soremelés

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
  <szekció1>
<#elif konstans-kifejezés2
  <szekció2>>
/* . . . */
<#elif konstans-kifejezésN
  <szekcióN>>
<#else
  <végső-szekció>>
#endif

Lássuk a kiértékelést!

1.Ha a konstans-kifejezés1 értéke nem zérus (igaz), akkor a preprocesszor a szekció1 sorait feldolgozza, és az eredményt átadja a fordítónak. A szekció1 természetesen üres is lehet. Ezután az ezen #if-hez tartozó összes többi sort a vonatkozó #endif-fel bezárólag kihagyja, s az #endif-et követő sorral folytatja a munkát az előfeldolgozó.
2.Ha a konstans-kifejezés1 értéke zérus (hamis), akkor a preprocesszor a szekció1-et teljes egészében elhagyja. Tehát nincs makrókifejtés, és nem adja át a feldolgozott darabot a fordítónak! Ezután viszont a következő #elif konstans-kifejezése kiértékelésébe fog, s így tovább.
3.Összesítve az #if-en és az #elif-eken lefelé haladva az a szekció kerül előfeldolgozásra, s ennek eredménye fordításra, melynek konstans-kifejezése igaznak bizonyul. Ha egyik ilyen konstans-kifejezés sem igaz, akkor az #elsevégső-szekciójára vonatkoznak az előbbiekben mondottak.

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)
  #define _HEADERH
  /* Itt van a fejfájl szövege. */
#endif

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ó)
#ifndef 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: */
#include <stdio.h>
#line 4 "PIPI.C"
void main(void) {
  printf("\nA(z) %s fájl %d sorában vagyunk!", __FILE__,
        __LINE__);
  #line 12 "PELDA.C"
  printf("\n");
  printf("A(z) %s fájl %d sorában vagyunk!", __FILE__,
        __LINE__);
  #line 8
  printf("\n");
  printf("A(z) %s fájl %d sorában vagyunk!\n", __FILE__,
        __LINE__); }

Az előállított standard kimenet a következő lehet:

#line 1 "pelda.c"
#line 1 "c:\\msdev\\include\\stdio.h"
. . .
#line 524 "c:\\msdev\\include\\stdio.h"
#line 3 "pelda.c"
#line 4 "PIPI.C"
void main(void) {
  printf("\nA(z) %s fájl %d sorában vagyunk!", "PIPI.C",
        6);
  #line 12 "PELDA.C"
  printf("\n");
  printf("A(z) %s fájl %d sorában vagyunk!", "PELDA.C",
        13);
  #line 8 "PELDA.C"
  printf("\n");
  printf("A(z) %s fájl %d sorában vagyunk!\n","PELDA.C",
        9); }

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)
  #error A SAJAT 0-nak vagy 1-nek definiálandó!
#endif
#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>
/*02*/void atalakit(char forras[], char cel[]) {
/*03*/  int i, j, feher = 1;
/*04*/  for(i=0; isspace(forras[i]); i++);
/*05*/  for(; forras[i]; i++) {
/*06*/    if(!isspace(forras[i])) {
/*07*/      if(feher) cel[j] = toupper(forras[i]);
/*08*/      else cel[j] = tolower(forras[i]);
/*09*/      j++;
/*10*/      feher = 0;
/*11*/    } else {
/*12*/      feher = 1;
/*13*/    }
/*14*/  }
/*15*/  cel[j] = 0;
/*16*/}
Jelölje meg, mely sorok felhasználásával lehetne működőképessé tenni hibás függvényünket!
/*01*/#include <stdio.h>
/*03*/  int i, j, feher = 0;
/*03*/  int i, j = 0, feher = 0;
/*03*/  int i, j = 1, feher = 1;
/*05*/  for(i=0; forras[i]; i++) {
/*15*/  cel[j] = 1;

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>
/*2*/int neptune(char s[]) {
/*3*/  int i;
/*4*/  for(i=0; i<6; i++)
/*5*/    if(!isdigit(s[i]) && !isalpha(s[i])) return 0;
/*6*/  if(s[i]) return 0; else return 1; }
Jelölje meg azokat a sorokat, amellyel a függvény működőképessé tehető!
/*5*/    if(!isdigit(s[i]) || !isalpha(s[i])) return 0;
/*5*/    if(isdigit(s[i]) && isalpha(s[i])) return 0;
/*5*/    if(!isdigit(s[i]) && !isupper(s[i])) return 0;
/*5*/    if(!isalnum(s[i])) return 0;
/*6*/  if(!s[i]) return 0; else return 1; }
/*6*/  if(s[i+1]) return 0; else return 1; }

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>
int neve(char s[]) {
  int i, db;
  if(!isupper(s[0])) return 0; /* Nagybetuvel kezdodjon */
  for(i=1, db=0; s[i]; i++) {
    if(s[i]==' ') db++; /* Szamoljuk a szokozoket */
    if(s[i]==' ' && s[i-1]==' ') return 0; /* Nem lehet ket szokoz egymas utan */
    if(isupper(s[i]) && s[i-1]!=' ') return 0; /* Szokoz utan nagybetu */
    if(islower(s[i]) && !isalpha(s[i-1])) return 0; /* Nagybetu elott betu */
  }
  if(s[i-1]==' ') return 0; /* Nem fejezodhet be szokozzel */
  if(db<1 || db>2) return 0; /* Tul sok vagy keves szo? */
  return 1; /* Minden OK */
}

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')\
                  &&((s)[1]>='1'&&(s)[1]<='8')&&!(s)[2])