Většina programátorů ví o tom, že některé konstrukce mají tzv. nedefinované chování a že by se jim měli vyhýbat. Méně se ale už ví, že kromě nedefinovaného chování norma definuje další typy chování: "specifikované", implementačně závislé a nespecifikované. Po přečtení tohoto příspěvku byste měli mít jasno, jaký je mezi těmito chováními rozdíl a kterým z nich se vyhýbat.
V rámci jednoduchosti se v následujícím textu budu zabývat pouze jazykem C++ a normou ISO C++98. Pro jazyk C je ale situace obdobná.
Definované typy chování
Jaké jsou tedy ony typy chování?
"Specifikované"
Toto chování jsem dal do uvozovek, protože není normou explicitně definované. Patří zde (překvapivě) vše, co je definováno normou a nepatří mezi zbývající tři typy chování.
Příklad:
int b = 24; int main() { int a = b, b; b = 7; }
Norma definuje (sekce 3.3), že v tomto případě se proměnná a
inicializuje hodnotou globální proměnné b
a že se vytvoří lokální proměnná stejného jména (opět b
), jejíž deklarace způsobí to, že přiřazovací příkaz na čtvrtém řádku přiřadí hodnotu 7 do lokální proměnné b
, nikoliv do globální proměnné. Standardní příklad na rozsah platnosti (scope) proměnných.
Implementačně závislé
Definováno v sekci 1.3.5. Konstrukce, které mají implementačně závislé chování, jsou platnými konstrukcemi, ale výsledné chování takovýchto konstrukcí je závislé na překladači. Toto chování ale musí být specifikováno v dokumentaci k danému překladači.
Příklad:
int main() { char c = -1; if (c > 0) { // ... } }
Norma definuje (sekce 3.9.1), že je implementačně závislé, zda datový typ char
je se znaménkem (signed), nebo bez znaménka (unsigned). Takže to, zda se podmínka v příkladu vyhodnotí jako pravdivá či nepravdivá, záleží na překladači.
Nespecifikované
Definováno v sekci 1.3.13. Toto chování je obdobné implementačně závislému, ale s tím rozdílem, že zvolené chování nemusí být specifikováno v dokumentaci k překladači. Většinou bývá rozsah možností vymezen normou (viz poznámka v sekci 1.3.13).
Příklad:
int main() { int i = 1; i = i++; }
Sekce 5 říká, že pořadí vyhodnocování výrazů mezi sekvenčními body je nespecifikované (pokud nevíte, co to jsou sekvenční body, pak doporučuji tento článek). Tudíž proměnná i
po vyhodnocení příkazu i = i++
může mít nejen hodnotu 2 nebo 3, ale i libovolnou jinou hodnotu (záleží na překladači).
Nedefinované
Definováno v sekci 1.3.12 (zajímavé spojení - "norma definuje nedefinované chování" :)). Jak vtipně poznačil dr. Peringer od nás z fakulty, tak pokud má některá konstrukce nedefinované chování, tak vám na hlavu může klidně spadnout měsíc :). Použitím konstrukcí s tímto chováním se program stává neplatným (z hlediska normy). Toto chování mají jak konstrukce, u kterých je to v normě explicitně uvedeno, tak konstrukce, jejichž význam není v normě specifikován. Výsledek takovýchto konstrukcí může být prakticky libovolný - může dojít např. k ignorování situace a ponechání běhu programu "náhodě", ukončení programu (a případnému vytištění chybové zprávy) a další možná chování.
Příklad:
int main() { int *p = 0; *p = 5; }
Nedefinované chování má např. dereference nulového ukazatele (null pointer). Výsledkem bývá často porušení ochrany paměti a násilné ukončení programu operačním systémem, ale rozhodně to není jediné možné chování!
Závěr
Pokud usilujete o přenositelný kód (což bývá většinou žádané), tak byste se v žádném případě neměli spoléhat na konstrukce, které mají implementačně závislé, nespecifikované či nedefinované chování. Spoléhání na takovéto konstrukce často vede k problémům.