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.
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.
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 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
.
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__.
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.
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
...
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 <header_name>
#include "header_name"
#include macro_identifier
které postupně znamenají:
header_name
je hledán ve standardním
adresáři pro include
. Takto se zpravidla začleňují
standardní hlavičkové soubory. Není-li soubor
nalezen, je ohlášena chyba. 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 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í
Název: | Programování v jazyce C |
Autor: | Petr Šaloun |
Do HTML převedl: | Kristian Wiglasz |
Poslední úprava: | 19.11.1996 |