Существует одно обстоятельство, при котором вызов exit() и возвращение из функции main() не являются эквивалентными. Если во время завершения предпринимаются какие-либо действия с использованием локальных переменных функции main(), то возвращаемое значение этой функции будет неопределенным. Так, например, случается, когда локальная переменная функции main() указывается в вызовах setvbuf() или setbuf() (см. раздел 13.2).
Возвращение из функции без указывания значения или просто завершение главной функции тоже приводит к тому, что программа, вызвавшая функцию main(), выполняет вызов exit(), однако результат при этом зависит от поддерживаемого стандарта языка С и использованных параметров компиляции:
В стандарте C89 поведение при таких обстоятельствах не определено; программа может завершиться с произвольным статусом. Так, например, происходит на платформе Linux в сочетании с компилятором gcc: код завершения программы берется из какого-то случайного значения в стеке или определенном регистре ЦПУ. Следует избегать завершения программ таким образом.
Стандарт C99 требует, чтобы окончание главной функции было эквивалентно вызову exit(0). Для того чтобы программа в системе Linux вела себя именно так, нужно скомпилировать ее с использованием параметра gcc — std=c99.
Во время нормального и аварийного завершения процесса выполняются следующие действия.
• Закрываются дескрипторы открытых файлов, потоки каталогов (см. раздел 18.8), дескрипторы каталога сообщений (см. справочные страницы вызовов catopen(3) и catgets(3)) и дескрипторы преобразования (см. справочную страницу вызова iconv_open(3)).
• В результате закрытия дескрипторов снимаются все блокировки файлов, которые удерживал данный процесс (см. главу 51).
• Если это контролирующий процесс в контролирующем терминале, каждому из процессов в активной группе терминала посылается сигнал SIGHUP, а сам терминал отключается от сессии. Данный этап будет рассмотрен подробнее в разделе 34.6.
• Закрываются любые именованные семафоры POSIX, открытые в вызывающем процессе, как будто при вызове sem_close().
• Закрываются любые очереди сообщений POSIX, открытые в вызывающем процессе, как будто при вызове mq_close().
• Если в результате завершения процесса его группа становится «осиротевшей», то всем остановленным процессам, которые в ней все еще находятся, по очереди высылаются сигналы SIGHUP и SIGCONT. Данный этап будет рассмотрен подробнее в подразделе 34.7.4.
• Снимаются любые блокировки памяти, установленные с помощью вызовов mlock() или mlockall() (см. раздел 46.2).
• Сбрасываются любые отображения в память, созданные с помощью вызова mmap().
Иногда нужно, чтобы приложение автоматически выполняло некоторые операции перед завершением. Представьте себе программную библиотеку, которой необходимо автоматически освобождать определенные ресурсы перед закрытием приложения. Поскольку библиотека не влияет на то, когда и как процесс завершает свою работу, и не может обязать главную программу вызывать свои функции перед закрытием, она не в состоянии гарантировать освобождение ресурсов. Одним из способов решения этой проблемы является использование
Обработчик выхода — это функция, предоставляемая программистом, которая регистрируется на каком-то этапе жизненного цикла процесса и затем автоматически вызывается во время его
Факт того, что обработчики выхода не вызываются, если процесс завершается посредством сигнала, в некоторой степени ограничивает их применение. Лучшее, что мы можем сделать, — предусмотреть обработчики для сигналов, которые могут быть посланы процессу, и заставить их устанавливать флаг, который приводит к вызову exit() главной программой. (Поскольку exit() не является одной из безопасной для асинхронных сигналов функций, перечисленных в табл. 21.1, ее, как правило, нельзя вызывать из обработчика сигнала.) Но даже в этом случае обработчик не сможет среагировать на сигнал SIGKILL, для которого нельзя изменить действие по умолчанию. Это одна из причин, по которой мы должны избегать применения этого сигнала для завершения процессов (как отмечено в разделе 20.2); вместо него следует использовать сигнал SIGTERM, который передается по умолчанию командой kill.
В GNU библиотеке C предусмотрено два способа регистрации обработчиков выхода. Первый из них описан в стандарте SUSv3 и заключается в использовании функции atexit().
#include
int atexit(void (*
Возвращает 0 при успешном завершении и ненулевое значение при ошибке.