printf("registering callback2\n"); atexit(callback2);
printf("registering callback3\n"); atexit(callback3);
printf("exiting now\n");
exit(0);
}
Вот что происходит при запуске:
$ ch09-atexit
registering callback1 /* Запуск главной программы */
registering callback2
registering callback3
exiting now
callback3 called /* Функции обратного вызова запускаются в обратном
порядке */
callback2 called
callback1 called
Как показывает пример, функции, зарегистрированные с помощью atexit()
POSIX определяет функцию _exit()
exit()
, которая вызывает функции обратного вызова и выполняет
-очистку, _exit()
является «сразу заканчивающейся» функцией:#include
void _exit(int status);
Системе передается status
exit()
, но процесс завершается немедленно. Ядро все еще делает обычную очистку: все открытые файлы закрываются, использованная адресным пространством память освобождается, любые другие ресурсы, использованные процессом, также освобождаются.На практике функция _Exit()
_exit()
. Стандарт С говорит, что от реализации функции зависит, вызывает ли _Exit()
зарегистрированные atexit()
функции и закрывает ли открытые файлы. Для систем GLIBC это не так, и функция ведет себя подобно _exit()
.Время использовать _exit()
exec
в порожденном процессе завершается неудачей. В этом случае вам exit()
, поскольку это сбрасывает на диск данные буферов, хранящиеся в потоках FILE*
. Когда позже родительский процесс сбрасывает на диск свои копии буферов, данные буфера оказываются записанными Например, предположим, что вы хотите запустить команду оболочки и хотите сами выполнить fork
exec
. Такой код выглядел бы следующим образом:char *shellcommand = "...";
pid_t child;
if ((child = fork()) == 0) { /* порожденный процесс */
execl("/bin/sh", "sh", "-c", shellcommand, NULL);
_exit(errno == ENOENT ? 127 : 126);
}
/* родитель продолжает */
Проверка значения errno
ENOENT
— нет для неё элемента в каталоге), завершающее значение равно 127. В противном случае, файл существует, но exec
не могла быть выполнена по какой-то другой причине, поэтому статус завершения равен 126. Хорошая мысль следовать этим соглашениям также и в ваших программах. Вкратце, чтобы хорошо использовать exit()
и atexit()
, следует делать следующее:• Определить небольшой набор значений статуса завершения, которые ваша программа будет использовать для сообщения этой информации вызывающему. Используйте для них в своем коде константы #define
enum
.• Решить, имеет ли смысл наличие функций обратного вызова для использования с atexit()
main()
в соответствующий момент; например, после анализа опций и инициализации всех структур данных, которые функция обратного вызова должна очищать. Помните, что функции должны вызываться в порядке LIFO (последняя вызывается первой).• Использовать exit()
• Исключением является main()
return
. Наш собственный стиль заключается обычно в использовании exit()
при наличии проблем и 'return 0
' в конце main()
, если все прошло хорошо.• Использовать _exit()
_Exit()
в порожденном процессе, если exec() завершается неудачей.9.1.6. Использование статуса завершения порожденного процесса
Когда процесс заканчивается, нормальным ходом событий для ядра является освобождение всех его ресурсов. Ядро сохраняет статус завершения законченного процесса, также, как сведения о ресурсах, которые он использовал в своей работе, a PID продолжает считаться используемым. Такой завершившийся процесс называется