Se standardní funkcí exit()
, která ukončí běh programu, se již asi setkal každý, kdo alespoň trochu programoval v jazyce C. Tato funkce spadá do kategorie funkcí, které se po zavolání nevracejí na místo, odkud byly volány. Méně se však ví, že kromě zmiňované funkce exit()
je v jazyce C řada dalších funkcí, které vykazují podobné chování. A právě o nich bude tento příspěvek. V závěru se také podíváme na nové klíčové slovo _Noreturn
, které je v souvislosti s tímto tématem k dispozici od ISO C11 (nejnovější standard jazyka C).
Které funkce se nevrací na místo volání?
ISO C11 definuje následujících šest funkcí, které se nevrací na místo volání (v hranatých závorkách je odkaz do standardu).
exit()
[7.22.4.4], od ISO C90 (ANSI C). Normální ukončení programu, které provede zavolání funkcí registrovaných pomocíatexit()
, zapsání nezapsaných dat do otevřených proudů (angl. streams), jejich uzavření, zrušení dočasných souborů vytvořených volánímtmpfile()
a navrácení předané hodnoty systému (int status
).abort()
[7.22.4.1], od ISO C90 (ANSI C). Násilné ukončení programu. To, zda se provede zapsání nezapsaných dat, uzavření proudů apod. je závislé na implementaci. Systému se pak vrátí hodnota signalizující neúspěšný běh programu. Implementačně je to řešené tak, že zavoláním této funkce pošlete svému programu signálSIGABRT
, jehož implicitní obsluha ukončí program.quick_exit()
[7.22.4.7], od ISO C11. Opět normální ukončení programu, které provede zavolání funkcí registrovaných pomocíat_quick_exit()
. Do systému se pak vrátí předaná hodnota (int status
). Motivace za zavedením této funkce je popsána zde (korektní ukončení programu, který používá vlákna, zavedená taktéž od C11)._Exit
[7.22.4.5], od ISO C99. Opět normální ukončení programu. Žádné funkce registrované pomocíat_exit()
aat_quick_exit()
se nevolají. To, zda se provede zapsání nezapsaných dat, uzavření proudů apod. je závislé na implementaci (stejně jako uabort()
). Do systému se pak vrátí předaná hodnota (int status
).longjmp()
[7.13.2.1], od ISO C90 (ANSI C). Nelokální skoky. Velmi hrubě řečeno se jedná ogoto
, kterým můžete skákat i mezi funkcemi. Jedno z možných využití je pro implementaci výjimek v Céčku (detaily).thrd_exit()
[7.26.5.5], od ISO C11. Ukončení běhu vlákna a nastavení výsledku jeho běhu na předanou hodnotu (int res
).
Co se týče použití funkcí ukončujících program v C++, tak tam přichází do hry ještě to, zda funkce volá destruktory statických objektů či nikoliv. Pokud by vás zajímaly detaily, nahlédněte do standardu.
Je pro toto chování podpora na úrovni jazyka?
Ano. Přesněji řečeno od ISO C11, které zavedlo klíčové slovo _Noreturn
[6.7.4 §8-12]. Jedná se o specifikátor funkce. Pokud byste přemýšleli nad smyslem toho podtržítka na začátku a velkým "N", tak to je z důvodu toho, aby nedošlo ke kolizi s existujícími identifikátory (identifikátor noreturn
nebyl dříve rezervovaný). Podobně je např. řešen typ bool
, kdy klíčové slovo pro tento typ je _Bool
, ale při #includování <stdbool.h>
máte k dispozici makro bool
, které se expanduje na _Bool
. Jeho použití má pozitivní vliv na čitelnost kódu. Pokud byste chtěli místo _Noreturn
používat noreturn
, stačí #includovat <stdnoreturn.h>
.
Ukázka použití klíčového slova _Noreturn
:
_Noreturn void clean_and_exit() { // Cleanup // ... exit(EXIT_SUCCESS); }
Výhoda použití tohoto klíčového slova je v tom, že překladač nyní může kód volající takovou funkci optimalizovat (není třeba před voláním vkládat na zásobník návratovou adresu, ukládat obsah registrů atd.). Můžete mrknout na popis atributu noreturn
u GCC, který má podobné chování.
Atributy
Pro GNU C90/99 jde tedy využít atributů __attribute__((noreturn)) nebo __attribute__((volatile)) (přestože ten druhý jsem v praxi nikdy neviděl; jde o to, že uvnitř GCC se u dané funkce jen nastavuje TREE_THIS_VOLATILE).
BTW, existují naopak i funkce, které se vrací dvakrát, pro ty pak existuje __attribute__((returns_twice)), např. vfork nebo setjmp. U těchto nejde provést třeba sibcall optimalizace.