Jazyk C používá pro vyhodnocení logických operátorů tzv.
lenivé vyhodnocování, což znamená, že se vyhodnotí jen nejnutnější část
logického výrazu.
Pokud máme například logický výraz
výsledek je vždy logická pravda a je jedno co je
uloženo v proměnné a, neboť operátor logického součtu pravda a cokoliv
je vždy pravda. Je tedy zbytečné vyhodnocovat obsah proměnné a, protože na
její hodnotě nezáleží. A to je přesně to, co jazyk C dělá a čemu říkáme
lenivé vyhodnocování.
Podobně se to má s operátorem logického součinu (and):
výsledek je vždy logická nepravda a opět nezáleží
na tom, jakou hodnotu má proměnná a.
Možná se zdá, že znalost chování jazyka C je pro nás
programátory irelevantní, ale není tomu tak (pokud by tomu tak bylo, tak bychom o tom
asi nepsali :-).
Ukažme si nějaký kód:
// Deklarace nějaké struktury, která obsahuje
// jednu členskou proměnnou.
typedef struct
{
int m_x;
} SOME_STRUCT;
void main()
{
// Definujeme proměnnou typu struktura
SOME_STRUCT oStruct;
// Definujeme proměnnou typu ukazatel na strukturu
// a zároveň
inicializujeme ukazatel
SOME_STRUCT *poStruct = &oStruct;
// Pokud je ukazatel inicializován
if (poStruct != NULL)
// a členská proměnná je nastavena na deset
if (poStruct->m_x == 10)
// tak to máme...
printf("Je to tak!\n");
} |
V tomto kódu testujeme, zda je nějaký ukazatel na strukturu
inicializován a pokud ano, tak otestujeme, zda její členská proměnná je rovna
desíti. Tuto podmínku se pokusíme zjednodušit:
// Pokud je ukazatel inicializován
// a členská proměnná je nastavena na deset
if (poStruct != NULL && poStruct->m_x == 10)
// tak to máme...
printf("Je to tak!\n"); |
Zamysleme se nad tím, co by se stalo, kdyby jazyk C poctivě
vyhodnocoval celý logický výraz a ukazatel poStruct byl nulový. První část
podmínky je nepravdivá (z čehož plyne, že celý výraz je nepravdivý) a druhá
část podmínky způsobí pád aplikace. Proč? Protože se pokusíme přistoupit na
adresu nula, kam ani náhodou nesmíme. Jenže! ono to funguje, protože jazyk C není
pitomec a když zjistí, že první část výrazu s operátorem and je
nepravdivá, další vyhodnocování neprovádí. Této vlastnosti se
využívá velmi často, protože to vede k přehlednějším podmínkám (srovnejte si
obě dvě podmínky a doufám, že ta druhá se vám líbí více).
Další ukázka je spíše legrácka a nidky jsem se s ní u
céčkových programů nesetkal, ale běžně se používá (alespoň v dokumentaci) v
jazyce PHP, který je velmi podobný céčku:
// Nějaká funkce vracející 1 nebo 0.
// V našem případě vždy nula, čímž simulujeme chybu.
int Funkce()
{
printf("Vracim chybu...\n");
return 0;
}
// Funkce zapouzdřuje volání funkce exit()
// a je zde kvůli tomu, že bude použita v
// logickém výrazu
int Konec()
{
exit(0); // Provede ukončení běhu programu
return 0; // tento return se již neprovede
}
void main()
{
// Volání Funkce, které pokud selže, vyvolá funkci Konec,
// která ukončí program. Pokud se Funkce povede, vrátí
// hodnotu logická pravda a díky lenivému vyhodnocování se
// funkce Konec volat nebude
a program pokračuje dál.
Funkce() || Konec();
printf("Tak to je konec\n");
} |
Důležitý je logický výraz, který je ve funkci main. Zde
se nejprve volá funkce Funkce, která vrací logickou pravdu při úspěchu (1)
a nepravdu (0) při selhání. Chceme, aby po selhání došlo k okamžitému ukončení
programu, které zajistí funkce Konec. Pokud Funkce vrátí hodnotu
pravda, vyhodnocování se ukončí, neboť výsledek je již dán (logická pravda) a
funkce Konec se nebude volat. Pokud funkce Funkce vrátí nepravdu, je
nutné vyhodnotit i zbytek výrazu, zavolá se funkce Konec, která obsahuje
volání funkce exit, která okamžitě ukončí běh programu.
Bohužel nemůžeme přímo psát následující výraz:
protože funkce exit nevrací hodnotu a nelze ji tedy použít
v logickém výrazu. Z toho důvodu jsme zapouzdřili volání exit do funkce Konec,
která formálně nějakou hodnotu vrací. Formálně proto, protože k předání
návratové hodnoty již nedojde.
Závěr
Možná bychom našli i další fígle, které využívají lenivého
vyhodnocování logických výrazů, a je tedy nutné tuto vlastnost jazyka dokonale
ovládat. |