Функция 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 (*func)(int, void *), void *arg);

Возвращает 0 при успешном завершении или ненулевое значение при ошибке

Аргумент func вызова on_exit() является указателем на функцию следующего вида:

void

func(int status, void *arg)

{

/* Выполняем освобождение ресурсов */

}

При вызове функции func() передаются два аргумента: status, который был передан при вызове exit(), и копия аргумента arg, который был указан во время регистрации функции. И хотя arg объявлен в качестве указателя, он открыт для различных интерпретаций со стороны программиста. Он может указывать на некую структуру данных, но с тем же успехом его можно применять в качестве целого числа или другого скалярного значения, если аккуратно выполнить приведение типов.

Как и atexit(), вызов on_exit() в случае ошибки возвращает ненулевое значение (не обязательно –1).

Перейти на страницу:

Похожие книги