Za₧il to asi ka₧d² programßtor: n∞kdy jeho dφlko, jakkoli se v n∞m zdß vÜechno logicky i syntakticky v po°ßdku, p°i b∞hu produkuje zcela neΦekanΘ v²sledky. Pßtrßnφ po p°φΦinßch b²vß zdlouhavΘ a zejmΘna zaΦßteΦnφk∙m m∙₧e p°inΘst nejednu bezesnou noc. Snad vßm tedy p°ijde vhod upozorn∞nφ na n∞kterΘ ΦastΘ d∙vody podivnΘho chovßnφ program∙ - zde si je p°edvedeme v prost°edφ C++, ale podobnß ·skalφ Φekajφ snad v ka₧dΘm vyÜÜφm jazyku...
VedlejÜφ efekty
Pro v∞tÜinu operßtor∙ v C++ platφ, ₧e po°adφ, ve kterΘm se vyhodnocujφ jednotlivΘ operandy, nenφ stanoveno. To nemß nic spoleΦnΘho s prioritou Φi asociativitou operßtor∙. Abychom snßze pochopili, oΦ jde, podφvejme se na jednoduch² p°φklad:
a = f() + g() + h();
Jak znßmo, operßtor + je asociativnφ zleva doprava, tak₧e se uveden² v²raz vyhodnotφ, jako kdyby byl uzßvorkovßn:
a = (f() + g()) + h();
To sice znamenß, ₧e se nejprve seΦtou v²sledky volßnφ f() a g() a k nim se p°iΦte v²sledek volßnφ h(), ale u₧ z toho nijak nevypl²vß, ₧e se bude nejprve volat funkce f(), pak g() a nakonec h(). Po°adφ volßnφ, tedy vyhodnocenφ operand∙, je ponechßno zcela na v∙li p°ekladaΦe; tφm se mu toti₧ otevφrajφ r∙znΘ mo₧nosti optimalizace.
Nemil²m d∙sledkem ovÜem je, ₧e pokud majφ n∞kterΘ operace vedlejÜφ efekty, m∙₧e se stßt, ₧e stejn² v²raz p°elo₧en² r∙zn²mi p°ekladaΦi, nebo dokonce t²m₧ p°ekladaΦem na r∙zn²ch mφstech programu, dß se stejn²mi operandy r∙znΘ v²sledky.
P°edstavme si t°eba, ₧e funkce f() a g() z p°edchozφho p°φkladu zvyÜujφ o 1 hodnotu globßlnφ prom∞nnΘ x a funkce h() tuto hodnotu vracφ:
int x = 0;
int f(){++x; return 0;}
int g(){++x; return 0;}
int h(){return x;}
Jestli₧e pak napφÜeme
x = 0;
a = f() + g() + h();
bude hodnota prom∞nnΘ a rovna 0, 1 nebo 2 v zßvislosti na po°adφ, v jakΘm se operandy vyhodnotφ.
M∙₧ete namφtnout, ₧e p°edchozφ p°φklad je samo·Φeln∞ vykonstruovanΘ programßtorskΘ "zv∞rstvo" a postrßdß jak²koli smysl. To je samoz°ejm∞ pravda; skuteΦnΘ p°φklady chyb tohoto druhu b²vajφ podstatn∞ slo₧it∞jÜφ, princip je ale podobn².
Nejen funkce
VedlejÜφ efekty funkcφ jsou nejnßpadn∞jÜφm p°φkladem, do problΘm∙ se ale m∙₧eme dostat i s operßtory ++ a --. Podφvejme se op∞t na jednoduch² p°φklad:
int x = 5;
int y = (x--)*(x--);
Bude y obsahovat 20, nebo 25?
Odpov∞∩ je tristnφ, ale u₧ ji nepochybn∞ uhodnete: Zßle₧φ na p°ekladaΦi. Nap°φklad archaick², ale tu a tam stßle jeÜt∞ pou₧φvan² p°ekladaΦ Borland C++ 3.1 vytvo°φ program, v n∞m₧ bude v²sledkem hodnota 20, ale nov∞jÜφ p°ekladaΦe Borland C++ Builder nebo MS Visual C++ 6 vygenerujφ k≤d, kter² dß jako v²sledek 25. (Ve vÜech p°φpadech bude ovÜem x nakonec obsahovat hodnotu 3.)
SekvenΦnφ body
V uvedenΘm p°φkladu by se mohlo zdßt, ₧e pravdu mß starÜφ p°ekladaΦ: nejprve vezme x - lhostejno, zda prvnφ, nebo druhΘ, nebo¥ jde o tou₧ prom∞nnou -, pou₧ije jeho aktußlnφ hodnotu a pak hodnotu ulo₧enou v tΘto prom∞nnΘ zmenÜφ o 1. Pak ud∞lß jeÜt∞ jednou totΘ₧. To znamenß, ₧e by m∞l pou₧φt jednou hodnotu 5, podruhΘ 4, a dostat tedy 20. Jen₧e nic takovΘho nenφ nikde p°edepsßno.
Standard jazyka toti₧ pouze stanovφ, ₧e v jist²ch mφstech programu jsou definovßny tzv. sekvenΦnφ body, ve kter²ch musφ b²t dokonΦeno vyhodnocenφ p°edchßzejφcφ Φßsti v²poΦtu vΦetn∞ vÜech vedlejÜφch efekt∙ a ₧ßdn² z vedlejÜφch efekt∙ nßsledujφcφch v²poΦt∙ jeÜt∞ nesmφ nastat. Takov²m sekvenΦnφm bodem je nap°. konec celΘho v²razu, vyhodnocenφ vÜech parametr∙ p°i volßnφ funkce p°ed vstupem do jejφho t∞la, okopφrovßnφ hodnoty vracenΘ funkcφ p°i nßvratu atd. V obecnΘm p°φpad∞ vÜak sekvenΦnφm bodem nenφ vyhodnocenφ souΦßsti v²razu.
Standard takΘ v²slovn∞ uvßdφ, ₧e po°adφ, ve kterΘm nastanou vedlejÜφ efekty, nenφ specifikovßno. P°edepisuje jen, ₧e musφ nastat nejpozd∞ji p°i pr∙chodu sekvenΦnφm bodem, nic vφce. To znamenß, ₧e vedlejÜφ efekty - v naÜem p°φpad∞ zm∞ny hodnot prom∞nn²ch x a y - musφ nastat nejpozd∞ji po vyhodnocenφ celΘho v²razu. Nikde nenφ ale °eΦeno, zda nap°. zm∞na hodnoty x nastane v₧dy po vyhodnocenφ uzßvorkovanΘho podv²razu (a dostaneme v²sledek 20), nebo zda nastane dvakrßt za sebou a₧ po vyhodnocenφ celΘho v²razu (a dostaneme v²sledek 25).
Ani jeden z p°ekladaΦ∙ tedy sv²m chovßnφm neodporuje standardu.
Makra
Zßpis (x--)*(x--) vypadß na prvnφ pohled podivn∞ a nepravd∞podobn∞. ProΦ bychom n∞co takovΘho psali? ╚eho tφm vlastn∞ chceme dosßhnout?
Je asi jasnΘ, ₧e podobnΘ v∞ci programßtor b∞₧n∞ nenapφÜe. P°esto jejich v²skyt v programu nenφ tak nepravd∞podobn², jak by se mohlo zdßt; mohou toti₧ snadno vzniknout jako v²sledek vyhodnocovßnφ maker. StaΦφ, kdy₧ definujeme makro SQR(), kterΘ bude poΦφtat druhou mocninu:
#define SQR(x) ((x)*(x))
a pozd∞ji ho pou₧ijeme naprosto "logick²m" zp∙sobem
int y = SQR(x--);
Kdyby SQR() byla funkce, bylo by vÜe v po°ßdku - a prßv∞ podobnost pou₧itφ parametrickΘho makra s volßnφm funkce je Φastou p°φΦinou podobn²ch chyb.
V²jimky
╪ekli jsme, ₧e pro v∞tÜinu operßtor∙ nenφ po°adφ vyhodnocenφ operand∙ specifikovßno. Toto pravidlo vÜak mß svΘ v²jimky, kterΘ jist∞ dob°e znßte. Jde o operßtory ||, &&, ?: a Φßrka. Mezi vyhodnocenφm jednotliv²ch operand∙ ve v²razech a||b, a&&b, a?b:c a a,b je v₧dy sekvenΦnφ bod. To znamenß, ₧e v₧dy se nejprve vyhodnotφ prvnφ operand (vΦetn∞ mo₧n²ch vedlejÜφch efekt∙) a teprve pak druh².
OvÜem pozor: vztahuje se to pouze na vestav∞nΘ operßtory, nikoli na p°etφ₧enΘ operßtory ||, && a Φßrka. (Operßtor ?: nelze, jak znßmo, p°et∞₧ovat.) Pou₧itφ p°etφ₧enΘho operßtoru p°edstavuje volßnφ funkce, ve kterΘm operandy vystupujφ jako parametry - a to znamenß, ₧e sekvenΦnφ bod nßsleduje a₧ po vyhodnocenφ vÜech parametr∙.
NedefinovanΘ v²razy
Z p°edchozφho povφdßnφ plyne, ₧e hodnota n∞kter²ch v²raz∙ (nebo chovßnφ p°ekladaΦe p°i jejich vyhodnocovßnφ) nenφ definovßna; takov²m konstrukcφm je pochopiteln∞ t°eba se v programu vyhnout. JinΘ mo₧nΘ p°φklady v²raz∙, pro n∞₧ nenφ chovßnφ programu definovßno, jsou:
i = v[i++];
i = ++i+1;
Nezapomφnejme, ₧e "·pln² v²raz" zahrnuje i p°i°azenφ a prom∞nnou na jeho levΘ stran∞ a ₧e mezi vyhodnocenφm podv²razu na pravΘ stran∞ a jeho p°i°azenφm levΘ stran∞ nenφ sekvenΦnφ bod. V prvnφm p°φpad∞ tedy nenφ jasnΘ, zda se bude nejprve inkrementovat hodnota i a pak se i p°epφÜe hodnotou danΘho prvku pole v nebo naopak. Inkrementace i pomocφ operßtoru ++ a zm∞na jeho hodnoty pomocφ operßtoru p°i°azenφ jsou toti₧ dva r∙znΘ vedlejÜφ efekty a ty mohou nastat v libovolnΘm po°adφ. PodobnΘ je to i ve druhΘm p°φkazu. (Poznamenejme, ₧e oba tyto p°φklady uvßdφ standard ISO 14882-1998 jazyka C++.)
Na druhΘ stran∞ p°φkaz
i = i+5;
je naprosto v po°ßdku, nebo¥ zde nastßvß jedin² vedlejÜφ efekt - zm∞na hodnoty i v d∙sledku p°i°azenφ.