4.1. Definice maker
4.2. Standardní předdefinovaná makra
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.
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 }. Poznamenejme 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é konstanty
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_id
3 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; ...
Makra
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é, jak 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.
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 ##.
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("%dn", variable ## x)
pak následující řádek
printvar(3);
přeloží preprocesor na
printf("%dn", variable3);
Jak je z ukázky patrné, mohou být mezi argumenty a operátorem ##
mezery.
Podmíněný překlad
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
#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
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):
#include
#include "header_name"
#include macro_identifier
které postupně znamenají:
- soubor
header_name
je hledán ve standardním adresáři proinclude
. Takto se zpravidla začleňují standardní hlavičkové soubory. Není-li soubor nalezen, je ohlášena chyba. - soubor
header_name
je hledán v aktivním (pracovním) adresáři. Není-li tam nalezen, postupuje se podle první možnosti. Takto se zpravidla začleňují naše (uživatelské) hlavičkové soubory. macro_identifier
je nahrazen. Další činnost podle 1. nebo 2. varianty.
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
Poznamenejme 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í
petr.saloun@ vsb.cz
DOWNLOAD & Odkazy
- Domovská stránka autora - http://www.cs.vsb.cz/saloun/publik/c4zelenace/
- Další užitečné zdroje týkající se tématu - index.html