4. PREPROCESOR 4.1. Definícia makier. Symbolické konštanty Makrá 4.2. Štandardné preddefinované makrá. Operátory # a ##. Podmienený preklad. Ostatné direktívy. Názov kapitoly napovedá, že sa budeme venovať prostriedku, ktorý predchádza prekladač. Preprocesor spracováva vstupný text ako text, prevádza v ňom textové zmeny a jeho výstupom je opäť text. Od preprocesora teda nemôžeme čakať kontrolu syntaxu, a ani typovú kontrolu. Preprocesor spracováva hlavičkové súbory, rozvíja makrá, neprepúšťa komentáre a umožňuje prevádzať podmienený preklad zdrojového textu. Pri spustení prekladu je najprv prevedený rozvoj makier, až výstup spracováva prekladač. Pokiaľ chceme získať výstup, ktorý preprocesor produkuje, môžeme ho zavolať samostatne príkazom cpp (od C Pre Processor). Preprocesor neprevádza rozvoj makier tam, kde nemôžu byť umiestnené ani príkazy jazyka C (napríklad v komentároch a reťazcoch). C preprocesor prijíma tieto direktívy: #define #elif #else #error #if #ifdef #ifndef #include #line #pragma #undef Jednotlivé direktívy popíšeme v rámci nasledujúcich podkapitol. Direktíva preprocesora musí byť vždy uvedená znakom #. # naviac musí byť v riadku prvým znakom. Od direktívy samotnej ho opäť môžu oddeľovať oddelovače. Zdôraznime ešte jednu dôležitú skutočnosť. Direktíva preprocesoru nie je príkaz jazyka C. Neukončujme ju preto bodkočiarkou. 4.1. Definícia makier. Definícia makier vo význame rozsahov polí je typickým príkladom použitia preprocesoru. V zdrojovom texte sa neodvolávame na magické čísla, ale na vhodne symbolicky pomenované makrá. Program to nielen sprehľadní, ale prípadnú zmenu hodnoty makra prevedieme na jednom mieste.
Pomocou preprocesoru a makier môžeme vytvárať konštrukcie, ktoré zvýšia čitateľnosť programu. Môžeme napríklad označiť začiatok a koniec bloku práve identifikátormi začiatok a koniec, ktoré pomocou preprocesoru správne prevedieme na znaky { a }. Uveďme však, že sme skôr popísali možnosť nie nástroj. Makrá majú zvýšiť čitateľnosť programu, nemajú za úlohu urobiť s programu ťažko zrozumiteľný rébus. Pokiaľ sa text makra nevojde na jeden riadok, môžeme ho rozdeliť na viac nasledujúcich riadkov. Skutočnosť, že makro pokračuje na nasledujúcom riadku, sa určí umiestnením znaku \ ako posledného znaku na riadku. Pre väčšiu prehľadnosť si makrá rozdeľme na symbolické konštanty a makrá. Kľúčom nech je skutočnosť, že makro na rozdiel od symbolickej konštanty má argumenty. Symbolické konštanty Ich definovanie a oddefinovanie môžeme syntakticky popísať takto: #define macro_id [token_sequence] #undef macro_id kde macro_id predstavuje meno (identifikátor) makra token_sequence je nepovinný súvislý reťazec Pri svojej činnosti prehľadáva preprocesor vstupný text a pri výskyte reťazca macro_id prevádza jeho nahradenie reťazcom token_sequence. Tejto činnosti sa hovorí rozvoj (expanze) makra. Z tohoto popisu je jasné, prečo sa preprocesoru niekedy zjednodušene hovorí makroprocesor. #define START 2 #define PRIRASTOK 1 #define DLZKA_RIADKU 100 int main() { int pocet = 0, alokovane = START, prirastok = PRIRASTOK; pole_retezca p_ret = NULL; Makrá Makrá už podľa nášho delenia majú argumenty. Definujeme ich takto: #define macro_id([arg_list]) [token_sequence] kde (ostatné položky sú rovnaké, ako sme už uviedli pri symbolických konštantách): arg_list predstavuje zoznam argumentov navzájom oddelených len čiarkou.
Ako klasický príklad makra si uveďme vrátenie maximálnej hodnoty z dvoch: #define max(a,b) ((a>b)?a:b) Výhodou aj nevýhodou je, že nepracuje s typmi. Výhodou preto, že pokiaľ by sme chceli definovať podobnú funkciu, museli by sme napísať toľko ich verzií, koľko by bolo navzájom nezlučiteľných variant dátových typov argumentov. Nevýhodou je netypovosť makra teda, ak uvedieme napríklad omylom ako argumenty reťazca (potom by sa porovnávali adresy ich prvých znakov) alebo dva argumenty neporovnateľných typov (štruktúra a číslo, ). Také chyby potom (niekedy) odhalí až prekladač. Pri definícii makra max nás možno prekvapia zdanlivo nadbytočné zátvorky oddeľujúce token_sequence. Musíme len pripomenúť, že makrá nie sú príkazy jazyka C. Ich rozvoj prebieha na textovej úrovni. Preprocesor teda nemôže v závislosti na kontexte raz nadbytočné zátvorky vypustiť, inokedy chýbajúce pridať. Preto radšej sami nadbytočné zátvorky nevypúšťame. Z textovosti rozvoja makra môžu plynúť aj nečakané problémy. Porovnajme makro a funkciu, počítajúcu druhú mocninu argumentu: #define SQR(x) int sqr(int x) { return x * x; } (x*x) a predstavme si ich použitie: int x, y, n = 3; x = sqr(n+1); /* sqr(4) -> 4*4 = 16 */ y = SQR(n+1); /* (n+1*n+1) t.j. (4+1*4+1) = 9 */ čo nám v prípade makra dáva úplne iný (nesprávny) výsledok, než sme očakávali. Pokiaľ opravíme (x*x) na správnejší ((x)*(x)), dostaneme síce výsledok správny, ale opäť nájdeme príklad, kedy správny nebude. Ide o skutočnosť, že pri volaní funkcie sa argument vyhodnotí len raz. Pri makre to tak byť nemusí. Pozrime sa (s lepším variantom makra): int x, y, n = 3; x = sqr(++n); /* sqr(4) -> 4*4 = 16 */ y = SQR(++n); /* ((++n)*(++n)) t.j. ((4)*(5)) = 20 */ Opäť dostaneme chybný výsledok pri použití makra SQR(). Práve z dôvodov popísaných vedľajších efektov a netypovosti makier sa nedoporučuje používať makrá ako náhradu funkcií. Doporučuje sa použitie funkcií s prípadným modifikátorom inline.
4.2. Štandardné preddefinované makrá. Podľa ANSI štandardu musí preprocesor C identifikovať a v uvedenom význame vyhodnocovať nasledujúce makrá (identifikátory makier sú obklopené dvoma podtržníkmi): DATE dátum prekladu, mmm dd yyyy, pr. Nov 14 1993 FILE meno zdrojového súboru LINE práve spracovávaný riadok v zdrojovom súbore STDC definuje typ (úroveň) prekladu (STanDard C) TIME čas prekladu, hh:mm:ss, (hh 00-24), pr. 16:02:59 Aj výrobcovia prekladačov však vybavujú svoje produkty radou preddefinovaných makier. Najmä takých, ktoré nám umožňujú použiť špeciálne vlastnosti ich produktu. Z dôvodu ľahkého prenosu sa im radšej vyhneme. Na druhé strane sú preddefinované makrá popisujúce operačný systém, prípadne jeho verziu. Pokiaľ píšeme program pre viac OS (obvykle sa hovorí o platformách), zrejme sa odvoláme na tieto symbolické preddefinované konštanty na miestach, kde sú volané funkcie závislé na OS. Túto možnosť popíšeme ďalej v podkapitole venovanej podmienenému prekladu. Uveďme si aspoň niektoré neštandardné makrodefinície: _DECVAX, IAPX286, MWC, COHERENT, _IEEE, _I386 z produktu Coherent, a niektoré z BC z prostredia MS-DOS: CDECL, cplusplus, MSDOS, OVERLAY, PASCAL, a ešte MS-DOSovské makrá pre použitý pamäťový model TINY, SMALL_, COMPACT, MEDIUM, LARGE, HUGE. Operátory # a ##. ANSI definuje tieto dva operátory a určuje ich vyhodnotenie takto: Operátor # prevádza prevod argumentu na reťazec (umiestni argument medzi pár úvodzoviek. Napríklad ak definujeme #define display(x) show((long)(x), #x) potom preprocesor rozvinie riadok display(abs(-5)); na riadok show((long)(abs(-5)), "abs(-5)");
Operátor ## prevádza spojovanie tokenov tak, že argumenty oddelené týmto operátorom po rozvoji makra vytvoria jeden celok (reťazec). Opäť si ukážme činnosť popisovaného operátoru. Ak definujeme #define printvar(x) printf("%d\n", variable ## x) potom nasledujúci riadok printvar(3); preloží preprocesor na printf("%d\n", variable3); Ako vidíme na ukážke, môžu byť medzi argumentmi a operátorom ## medzery. Podmienený preklad. Preprocesor môže behom svojej činnosti vyhodnocovať, či je nejaké makro definované alebo nie. Pri použití kľúčového slova preprocesoru defined potom môže spájať také vyhodnotenia do rozsiahlejších logických výrazov. Argument defined nemusí byť uzavretý do zátvoriek. Môže sa však vyskytnúť len za #if nebo #elif. Napríklad si ukážme zložitejšiu podmienku: #if defined LIMIT && defined OSTRA && LIMIT==10 V závislosti na splnení či nesplnení podmienky môžeme určiť, či bude ohraničený úsek programu ďalej spracovaný, alebo či bude odfiltrovaný a tak teda nebude preložený. Tejto možnosti použitia preprocesoru hovoríme podmienený preklad. Vždy musí byť jasné, kde podmienená časť zdrojového textu začína a kde končí. Preto nesmieme zabúdať na či #elif. Podmienené časti musia byť ukončené a obmedzené v rámci jedného zdrojového textu. Inak oznámi preprocesor chybu. Podmienky veľmi pripomínajú konštrukcie jazyka C. Naviac je oproti C zavedená i podmienka #elif. Nenechajme sa však mýliť. Vyhodnotenie podmienok prevádza už preprocesor. Ukážka neúplného programu s jednoduchým podmieneným prekladom. #define LADENIE #include <conio.h> void volno(void) void uvolni(pole_retezcov *p_r, int pocet) int main(void) volno(); if (alokacia(&p_ret, alokovane)) uvolni(&p_ret, pocet); volno();
Ostatné direktívy. Doposiaľ sme nepopísali štyri direktívy preprocesora. Teda postupne. #include je direktívou úplne nepostrádateľnou. Používame ju na včlenenie zdrojového textu iného súboru. Tento súbor môže byť určený viacerými spôsobmi. Preto má direktíva #include tri možné formy (pre ľahšie odkazy ich očíslujme): 1. #include <header_name> 2. #include "header_name" 3. #include macro_identifier ktoré postupne znamenajú: Κ Κ súbor header_name je hľadaný v štandardnom adresári pre include. Takto sa obyčajne začleňujú štandardné hlavičkové súbory. Ak nie je súbor nájdený, je ohlásená chyba. súbor header_name je hľadaný v aktívnom (pracovnom) adresári. Ak tam nie je, postupuje sa podľa prvej možnosti. Takto sa obyčajne začleňujú naše (užívateľské) hlavičkové súbory. Κ macro_identifier je nahradený. Ďalšia činnosť podľa 1. alebo 2. varianty. Poznamenajme, že pri práci na veľkom projekte sa aj vlastné hlavičkové súbory umiestňujú do zvláštneho adresára. Potom sa pochopiteľne pripájajú podľa 1. varianty. Pretože 2. variant pri neúspechu prechádza do 1., môžeme aj v tomto prípade popísaným spôsobom odlíšiť vlastné a štandardné hlavičkové súbory. Nesmieme však zabudnúť definovať viac než jeden štandardní adresár. #error je direktívou, ktorou môžeme zaistiť výstup nami zadaného chybového hlásenia. Najčastejšie sa používa v súvislosti s podmieneným prekladom. Má formát: #error chybové hlásenie kde chybové hlásenie bude súčasťou protokolu o preklade. #line umožňuje nastaviť hodnotu štandardného makra LINE a prípadne aj FILE. Používa sa najmä pri strojovo generovaných zdrojových textoch. Má formát #line číslo ["meno"] kde číslo udáva hodnotu uloženú do LINE a platnú pre nasledujúci zdrojový riadok. meno udáva nepovinnú hodnotu uloženú do makra FILE. #pragma je špeciálnou direktívou, ktorá má uvádzať všetky implementačne závislé direktívy. Pokiaľ iný prekladač špeciálnu direktívu nepozná, proste ju bez chybového stavu ignoruje. Predchádzajúca kapitola Začiatok Nasledujúca kapitola