Předchozí kapitola

Obsah

Konec

Následující kapitola

4. PREPROCESOR

4.1. Definice maker.

Symbolické konstanty
Makra

4.2. Standardní předdefinovaná makra.

Operátory # a ##.
Podmíněný překlad.
Zbývající direktivy.


Název kapitoly napovídá, že se budeme věnovat prostředku, který předchází překladač. Preprocesor zpracovává vstupní text jako text, provádí v něm textové změny a jeho výstupem je opět text. Od preprocesoru tedy nemůžeme čekat kontrolu syntaxe, natož pak typovou kontrolu. Preprocesor zpracovává hlavičkové soubory, rozvíjí makra, nepropouští komentáře a umožňuje provádět podmíněný překlad zdrojového textu.

Při spuštění překladu je nejprve proveden rozvoj maker, teprve výstup zpracovává překladač. Pokud chceme získat výstup, který preprocesor produkuje, můžeme jej zavolat samostatně příkazem cpp (od C Pre Processor). Preprocesor neprovádí rozvoj maker tam, kde nemohou být umístěny ani příkazy jazyka C (například v komentářích a řetězcích).

C preprocesor přijímá tyto direktivy:

#define #elif #else #endif
#error #if #ifdef #ifndef
#include #line #pragma #undef

Jednotlivé direktivy popíšeme v rámci následujících podkapitol.

Direktiva preprocesoru musí být vždy uvozena znakem #. # navíc musí být na řádku prvním jiným znakem než jsou oddělovače. Od direktivy samotné jej opět mohou oddělovat oddělovače1.

Zdůrazněme ještě jednu důležitou skutečnost. Direktiva preprocesoru není příkaz jazyka C. Neukončujme ji proto středníkem.

4.1. Definice maker.zpet

Definice maker ve významu rozsahů polí je snad typickým příkladem použití preprocesoru2. Ve zdrojovém textu se neodvoláváme na magická čísla, ale na vhodně symbolicky pojmenovaná makra. Program to nejen zpřehlední, ale případnou změnu hodnoty makra provedeme na jednom místě.

Pomocí preprocesoru a maker můžeme vytvářet konstrukce, které zvýší čitelnost programu. Můžeme například označit začátek a konec bloku právě identifikátory začátek a konec, které pomocí preprocesoru správně převedeme na znaky { a }. Poznamejme ovšem, že jsme popsali spíše možnost. Makra mají zvýšit čitelnost programu, nemají za úkol udělat z programu těžko srozumitelný rébus.

Pokud se text makra nevejde na jeden řádek, můžeme jej rozdělit na více následujících řádků. Skutečnost, že makro pokračuje na následujícím řádku, se určí umístěním znaku \ jako posledního znaku na řádku.

Pro větší přehlednost si makra rozdělme na symbolické konstanty a makra. Klíčem nechť je skutečnost, že makro na rozdíl od symbolické konstanty má argumenty.

Symbolické konstantyzpet

Jejich definování a oddefinování můžeme syntakticky popsat takto:

#define macro_id [token_sequence]
#undef macro_id

kde

macro_id představuje jméno (identifikátor) makra
token_sequence je nepovinný souvislý řetězec

Při své činnosti prohledává preprocesor vstupní text a při výskytu řetězce macro_id3 provádí jeho nahrazení řetězcem token_sequence. Této činnosti se říká rozvoj (expanze) makra. Z tohoto popisu je jasné, proč se preprocesoru někdy zjednodušeně říká makroprocesor.

#define      START            2
#define      PRIRUSTEK        1
#define      DELKA_RADKU      100
...
int main()
{
 int      pocet = 0,
	alokovano = START,
	prirustek = PRIRUSTEK;
 pole_retezcu p_ret = NULL;
 ...

Makrazpet

Makra již podle našeho dělení mají argumenty. Definujeme je takto:

#define macro_id([arg_list]) [token_sequence]

kde (ostatní položky jsou stejné, jako jsme již uvedli u symbolických konstant):

arg_list představuje seznam argumentů navzájem oddělených jen čárkou.

Jako klasický příklad makra si uveďme vrácení maximální hodnoty ze dvou:

#define max(a,b) ((a>b)?a:b)

Výhodou i nevýhodou je, že nepracuje s typy. Výhodou proto, že pokud bychom chtěli definovat podobnou funkci, museli bychom napsat tolik jejích verzí, kolik by bylo navzájem neslučitelných variant datových typů argumentů. Nevýhodou je netypovost makra tehdy, uvedeme-li třebas omylem jako argumenty řetězce (pak by se porovnávaly adresy jejich prvních znaků) nebo dva argumenty neporovnatelných typů (struktura a číslo, ...). Takové chyby pak (někdy) odhalí až překladač.

Při definici makra max nás možná překvapí zdánlivě nadbytečné závorky oddělující token_sequence. Musíme jen připomenout, že makra nejsou příkazy jazyka C. Jejich rozvoj probíhá na textové úrovni. Preprocesor tedy nemůže v závislosti na kontextu jednou nadbytečné závorky vypustit, jindy chybějící přidat. Proto raději sami nadbytečné závorky nevypouštíme.

Z textovosti rozvoje makra mohou plynout i nečekané problémy. Porovnejme makro a funkci, počítající druhou mocninu argumentu:

#define SQR(x)   (x*x)
int sqr(int x)
{
 return x * x;
}

a představme si jejich použití:

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 */

což nám v případě makra dává zcela jiný (nesprávný) výsledek, než jsme očekávali. Pokud opravíme (x*x) na správnější ((x)*(x)), dostaneme tentokrát sice výsledek správný, ale opět najdeme příklad4, kdy správný nebude.

Jde o skutečnost, že při volání funkce se argument vyhodnotí jen jednou. U makra tomu tak být nemusí. Podívejme (s lepší variantou 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ět dostaneme chybný výsledek u makra SQR().

Právě z důvodů popsaných vedlejších efektů a netypovosti maker se nedoporučuje používat makra pro náhradu funkcí. Doporučuje se použití funkcí s případným modifikátorem inline.

4.2. Standardní předdefinovaná makra.zpet

Podle ANSI standardu musí preprocesor C identifikovat a v uvedeném významu vyhodnocovat následující makra (identifikátory maker jsou obklopeny dvěma podtržítky):

__DATE__ datum překladu, mmm dd yyyy, př. Nov 14 1993
__FILE__ jméno zdrojového souboru
__LINE__ právě zpracovávaný řádek ve zdrojovém souboru
__STDC__ definuje typ (úroveň) překladu (STanDard C)
__TIME__ čas překladu, hh:mm:ss, (hh 00-24), př. 16:02:59

Ovšem i výrobci překladačů vybavují své produkty řadou předdefinovaných maker. Zejména takových, která nám umožňují použít speciální vlastnosti jejich produktu. Z důvodů přenositelnosti se jim raději vyhneme.

Na druhé straně jsou předdefinovaná makra popisující operační systém, případně jeho verzi. Pokud píšeme program pro více OS (obvykle se hovoří o platformách), zřejmě se odvoláme na tyto symbolické předdefinované konstanty v místech, kde jsou volané funkce závislé na OS. Tuto možnost popíšeme dále v podkapitole věnované podmíněnému překladu.

Uveďme si alespoň některá nestandardní makrodefinice:

_DECVAX, IAPX286, MWC, COHERENT, _IEEE, _I386

z produktu Coherent, a některé z BC z prostředí MS-DOS:

__CDECL__, __cplusplus, __MSDOS__, __OVERLAY__, __PASCAL__,

a ještě MS-DOSovská makra pro použitý paměťový model

__TINY__, __SMALL_, __COMPACT__, __MEDIUM__, __LARGE__, __HUGE__.

Operátory # a ##.zpet

ANSI definuje tyto dva operátory a určuje jejich vyhodnocení takto: Operátor # provádí převod argumentu na řetězec (jednoduše řečeno umístí argument mezi pár úvozovek.

Například definujeme-li

#define display(x) show((long)(x), #x)

pak preprocesor rozvine řádek

display(abs(-5));

na řádek

show((long)(abs(-5)), "abs(-5)");

Operátor ## provádí spojování tokenů tak, že argumenty oddělené tímto operátorem po rozvoji makra vytvoří jeden celek (řetězec).

Opět si ukažme činnost popisovaného operátoru.

Definujeme-li

#define printvar(x) printf("%d\n", variable ## x)

pak následující řádek

printvar(3);

přeloží preprocesor na

printf("%d\n", variable3);

Jak je z ukázky patrné, mohou být mezi argumenty a operátorem ## mezery.

Podmíněný překlad.zpet

Preprocesor může během své činnosti vyhodnocovat, je-li nějaké makro definováno či nikoliv. Při použití klíčového slova preprocesoru defined pak může spojovat taková vyhodnocení do rozsáhlejších logických výrazů. Argument defined nemusí být uzavřen do závorek. Může se však vyskytnout jen za #if nebo #elif. Například si ukažme složitější podmínku:

#if defined LIMIT && defined OSTRA && LIMIT==10

V závislosti na splnění či nesplnění podmínky můžeme určit, bude-li ohraničený úsek programu dále zpracován, nebo bude-li odfiltrován a tak nebude tedy přeložen. Této možnosti použití preprocesoru říkáme podmíněný překlad.

Vždy musí být jasno, kde podmíněná část zdrojového textu začíná a kde končí. Proto nesmíme zapomínat na #endif či #elif. Podmíněné části musí být ukončeny a omezeny v rámci jednoho zdrojového textu. Jinak oznámí preprocesor chybu. Podmínky velmi připomínají konstrukce jazyka C. Navíc je oproti C zavedena i podmínka #elif. Nenechme se však mýlit. Vyhodnocení podmínek provádí již preprocesor.

Ukázka neúplného programu s jednoduchým podmíněným překladem následuje.

#define LADENI
#if defined(LADENI)
 #include <conio.h>
#endif
#if defined(LADENI)
 void volno(void)
 ...
 void uvolni(pole_retezcu *p_r, int pocet)
 ...
#endif
int main(void)
 ...
#if defined(LADENI)
 volno();
#endif
 if (alokace(&p_ret, alokovano))
 ...
#if defined(LADENI)
 uvolni(&p_ret, pocet);
 volno();
#endif
 ...

Zbývající direktivy.zpet

Dosud jsme nepopsali čtyři direktivy preprocesoru. Tedy postupně.

#include

je direktivou naprosto nepostradatelnou. Používáme ji pro včlenění zdrojového textu jiného souboru. Tento soubor může být určen více způsoby. Proto má direktiva #include tři možné formy (pro snadnější odkazy je očíslujme):

  1. #include <header_name>
  2. #include "header_name"
  3. #include macro_identifier

které postupně znamenají:

Poznamenejme, že při práci nad velkým projektem se i vlastní hlavičkové soubory umisťují do zvláštního adresáře. Pak se pochopitelně připojují podle 1. varianty. Protože ovšem 2. varianta při neúspěchu přechází do 1., můžeme i v tomto případě popsaným způsobem odlišit vlastní a standardní hlavičkové soubory. Nesmíme ovšem zapomenout definovat více než jeden standardní adresář.

#error

je direktivou, kterou můžeme zajistit výstup námi zadaného chybového hlášení. Nejčastěji se používá v souvislosti s podmíněným překladem. Má formát:

#error chybové hlášení

kde chybové hlášení bude součástí protokolu o překladu.

#line

umožňuje nastavit hodnotu standardního makra __LINE__ a případně i __FILE__. Používá se zejména u strojově generovaných zdrojových textů. Má formát

#line číslo ["jméno"]

kde číslo udává hodnotu uloženou do __LINE__ a platnou pro následující zdrojový řádek.

jméno udává nepovinnou hodnotu uloženou do makra __FILE__.

#pragma

je speciální direktivou, která má uvozovat všechny implementačně závislé direktivy. Pokud jiný překladač speciální direktivu nezná, prostě ji bez chybového stavu ignoruje.


Výsvětlivky:

1 Poznamejme však, že tato možnost je používána jen zřídka.
2 Moderní styl C v tomto případě dává přednost konstantám.
3 Symbolické konstanty obvykle píšeme velkými písmeny, abychom je v textu snadno odlišili od ostatních identifikátorů jazyka.
4 Jde o příklad bezprostředně následující


Předchozí kapitola

Obsah

Začátek

Následující kapitola


Název: Programování v jazyce C
Autor: Petr Šaloun
Do HTML převedl: Kristian Wiglasz
Poslední úprava: 19.11.1996