УДК 004.45

ЭМУЛЯЦИЯ ОБРАБОТКИ ИСКЛЮЧЕНИЙ

В ЯЗЫКЕ ПРОГРАММИРОВАНИЯ С стандарта С99

 

Ковтуненко С.В., Шевченко О.Г.

Донецький національний технічний університет

 

В докладе рассматривается возможность эмуляции обработки исключений (exception handling) в языке системного программирования С99. Данная задача является нетривиальной и попытки к реализации системы обработки исключений предпринимались с 80х годов ХХ века. В работах того времени [1, 2] и конференциях Usenet подчеркивалось, что полностью эмулировать обработку исключений как в современных объектно-ориентированных языках программирования, таких как С++, Java и др. не удасться. Но используя языковые средства языка С и его препроцессора добиться реализации идей обработки исключений, при этом реализация по ряду технических причин будет отличаться от реализации в объектно-ориентированных языках. В дальнейшем, в 90-е годы интерес к эмуляции исключений постепенно угасает, поскольку роль языка С, как языка общего назначения, ослабевает и на первое место выходит С++, который имеет встроенные средства поддержки исключений.

В качестве языковых средств языка С, используемых для реализации системы обработки исключений используются функции из заголовка “setjmp.h”:

int setjmp( jmp_buf env );

void longjmp( jmp_buf env, int value );

Первая функция заполняет буфер состояния, сохраняя текущее стековое окружение в массиве env, переданный в качестве первого параметра, для последующего возврата. После нормального вызова функции setjmp(), она возвращает 0. Вторая функция longjmp() восстанавливает окружение, сохранённое при последнем вызове setjmp() с соответствующим аргументом env, переданным в качестве первого параметра. После отработки longjmp() выполнение программы продолжается так, как будто соответствующий вызов setjmp() только что вернул значение val отличное от нуля. Однако в промежутке между вызовами функций setjmp() и longjmp() не должно быть возврата из функции, вызвавшей setjmp(). Тем самым всегда есть возможность отличить естественный вызов функции setjmp() от искуственного, ставшего результатом вызова longjmp().

Именно вышеперечисленный механизм нелокальных переходов вместе с препроцессором языка С является основой системы обработки исключений. Они представляют собой своеобразную «машину времени», позволяющую вернуться в прошлое состояние.

Система обработки исключений разработанная для языка С, должна корректно работать и в С++, т.е. использоваться стандартные решения предоставляемые языком С и его стандартной библиотекой и не использовать средства, отсутствующие в С++. Главной проблемой реализации своей собственной системы обработки исключений и совместимости её с С++ является отсутствие корректного вызова деструкторов локальных объектов, что является нарушением основной концепции исключений С++: деструкторы локальных объектов вызываются всегда, независимо от способа возврата из функции ( с помощью return или в связи с выбросом исключения). Надо заметить, что при компиляции кода, использующего нелокальные переходы setjmp()\longjmp() в среде Microsoft Visual Studio будут также в случае необходимости вызваны локальные деструкторы. Это частная инициатива MS CRT library. Т.е. при компиляции кода с ключом /EHsc (Enable C++ exceptions), при вызове longjmp() деструкторы локальных объектов всё-таки вызываются, но выдаётся сообщение о том, что совмещение setjmp() и исключений С++ является непереносимым.

В разработанной системе исключений под исключениями понимаются константные указатели на строковые литералы, которые могут быть напечатаны обычными функциями потокового вывода. Поддерживается модель TRY-CATCH-FINALLY аналогичная языкам Java, C#. Назначение каждого из блоков совпадает с таковым в языках высокого уровня.

При написании кода, используя разработанную систему обработки исключений необходимо придерживаться следующих правил безопасности:

- Использовать return, break, continue, goto, longjmp() для перехода в или за пределы блоков запрещено. Данная логическая ошибка не проверяется компилятором.

- Корректным является возбуждение (повторное возбуждение) исключений внутри блоков CATCH или FINALLY.

- Комбинирование блоков CATCH и FINALLY в одном TRY-ENDTRY блоке эквивалентно следующему коду:

TRY                                                        TRY

                                                                         TRY

….                                                                       ….

CATCH (E1)                                                    CATCH (E1)

  ….                                                                     ….

CATCH (E2)                                                    CATCH (E2)

  ….                                                                     ….

CATCH_ANY ()                                             CATCH_ANY ()

  ….                                                                     ….

                                                                         ENDTRY

FINALLY ()                                           FINALLY ()

  ….                                                            ….

ENDTRY                                               ENDTRY

Однако первый код является более эффективным. Блок FINALLY () во втором случае не имеет доступа ко вложенному блоку TRY-ENDTRY.

- Блоки CATCH и FINALLY являются опциональными, но имеет смысл иметь хотя бы один блок CATCH. Внутри этих блоков можно свободно пользоваться THROW( E ) и RETHROW().

- Если необработанное исключение не обрабатывается до конца блока ENDTRY, оно будет автоматически повторно возбуждено. Если же во всех блоках обработки исключений исключение не будет обработано, то автоматически будет вызван обработчик по-умолчанию ex_terminate().

- THROWLOC() может специфицировать место исключения (автоматически по-умолчанию) после аргумента исключения: THROWLOC( ex, file, line ).

- Конструкции PRT(ptr, del)/UNPRT(ptr) работают как обычный стек (имеющий функциональность PUSH()/POP()) защищённых объектов.

- Конструкция PRT( ptr, del ) позволяет защитить указатель ptr (но не объект в памяти, на который он указывает) от возбуждения исключения, поэтому если объект, на который указывает ptr изменяется до вызова UNPRT(), новый объект автоматически защищается. Если ptr != NULL, в процессе раскрутки стека будет вызвана функция del(ptr).

- Конструкция UNPRT(ptr) прекращает защищать указатель ptr и все объекты защищённые после него.

- С помощью макроопределений с префиксом EX_ можно запрещать работу системы.

- Для конвертирования сигналов в исключения нужно пользоваться конструкциями:

extern const char EX_NAME( sig_val )[];

ex_signal( SIGVAL, EX_NAME( sig_val ));

или просто:

ex_signal_std(), которая конвертирует некоторые общие стандартные сигналы (смотреть “exception.c”) в исключения. Перечень стандартных сигналов находится в файле “signal.h”.

Предложенный вариант системы обработки исключений позволяет в программах использовать модель обработки исключений, аналогичную Java и C#, с поддержкой  защищённых указателей и регистрацией обработчиков сигналов.

Код библиотеки был протестирован на следующих системах:

- Debian Etch x86-32, gcc 3.x и 4.x

- Fedora x86-64, gcc 3.x и 4.x

- MS Windows XP, PellesC 4.5, LCC-win32, MS Visual Studio 6

На всех вышеперечисленных системах результаты тестирования совпали с эталонными.

Разработанная система исключений, благодаря тому, что является платформенно-независимой может применяться при программировании на С или С++ в системах с отсутствующей поддержкой обработки исключений. Примером такой системы является SymbianOS 8.x,9.x.

 

 

 

Литература

[1]       Eric S. Roberts “Implementing exceptions in C”, Digital Equipment Corporation, 1989

[2]       John B. Goodenough “Exception handling: issues and a proposed notation”, Communication of the ACM. Vol.18 no.12, December 1975


© Ковтуненко С.В., Шевченко О.Г., 2008