Функция atexit() добавляет аргумент func в список функций, которые вызываются при завершении процесса. В определении функции func не должно быть аргументов и возвращаемого значения, то есть она должна иметь примерно такой вид:
void
func(void)
{
/* Выполняем какие-то действия */
}
Стоит отметить, что при возникновении ошибки функция atexit() возвращает ненулевое значение (не обязательно –1).
Можно зарегистрировать сразу несколько обработчиков выхода (и даже один и тот же обработчик несколько раз). При вызове программой exit() эти функции выполняются в
Внутри обработчика выхода можно выполнить практически любое действие на ваш выбор, включая регистрацию дополнительных обработчиков, которые помещаются в начало списка еще не вызванных обработчиков. Однако если один из обработчиков выхода не сможет вернуть управление (либо из-за вызова _exit(), либо в результате завершения процесса с помощью сигнала — например, когда обработчик сделал вызов raise()), остальные обработчики не будут вызваны. Кроме того, будут проигнорированы действия, которые обычно выполняются вызовом exit() (то есть сброс буферов stdio).
SUSv3 гласит, что в случае, если обработчик выхода сам вызывает exit(), результат становится неопределенным. В Linux оставшиеся обработчики выхода вызываются как обычно. Однако в некоторых системах это приводит к повторному выполнению всех обработчиков, что может вылиться в бесконечную рекурсию (пока переполнение стека не остановит процесс). В переносимых приложениях следует избегать вызова exit() внутри обработчиков выхода.
SUSv3 требует, чтобы процесс мог зарегистрировать как минимум 32 обработчика выхода. Максимально возможное количество обработчиков, доступных для регистрации в данной реализации, можно узнать с помощью вызова sysconf(_SC_ATEXIT_MAX) (однако не существует способа узнать, сколько обработчиков уже было зарегистрировано). Библиотека glibc может почти полностью снять это ограничение, если поместить обработчики выхода в динамически выделяемый связанной список. В системе Linux вызов sysconf(_SC_ATEXIT_MAX) возвращает 2147483647 (то есть максимальное целое 32-битное число со знаком). Иными словами, прежде, чем будет достигнуто это ограничение, мы исчерпаем какой-то другой ресурс (например, память).
Дочерний процесс, созданный с помощью вызова fork(), наследует копию списка зарегистрированных обработчиков выхода своего родителя. Когда процесс выполняет функцию exec(), все обработчики удаляются (это необходимо, потому что функция exec() заменяет код обработчиков выхода вместе с остальным программным кодом).
Если регистрация обработчика выхода была выполнена с помощью вызова atexit() (или on_exit(), описанного ниже), ее уже нельзя отменить. Однако внутри обработчиков перед выполнением каких-либо действий можно проверять, установлен ли глобальный флаг, и отключать их путем сбрасывания этого флага.
У обработчиков выхода, зарегистрированных с помощью вызова atexit(), есть два ограничения. Во-первых, вызывающийся обработчик выхода не знает, какой код был передан в функцию exit(). Иногда осведомленность об этом коде может оказаться полезной; например, можно выполнять разные действия в зависимости от того, завершается ли процесс удачно или неудачно. Второе ограничение заключается в том, что мы не можем указать аргумент для вызываемого обработчика выхода. Это могло бы пригодиться для изменения поведения обработчика или для многократной регистрации одной и той же функции, но с разными аргументами.
Для обхода этих ограничений библиотека glibc предоставляет альтернативный (нестандартный) способ регистрации обработчиков выхода: вызов on_exit().
#define _BSD_SOURCE /* Или: #define _SVID_SOURCE */
#include
int on_exit(void (*
Возвращает 0 при успешном завершении или ненулевое значение при ошибке
Аргумент func вызова on_exit() является указателем на функцию следующего вида:
void
func(int status, void *arg)
{
/* Выполняем освобождение ресурсов */
}
При вызове функции func() передаются два аргумента: status, который был передан при вызове exit(), и копия аргумента arg, который был указан во время регистрации функции. И хотя arg объявлен в качестве указателя, он открыт для различных интерпретаций со стороны программиста. Он может указывать на некую структуру данных, но с тем же успехом его можно применять в качестве целого числа или другого скалярного значения, если аккуратно выполнить приведение типов.
Как и atexit(), вызов on_exit() в случае ошибки возвращает ненулевое значение (не обязательно –1).