S
sergey_kovtunenko
Привет! Пришла идея написать эмуляцию обработки исключительных ситуаций для ANSI C. Это должна быть высокопортируемая библиотека макроопределений, использующая достоинства (и недостатки нелокальных переходов в С -- setjmp()\longjmp()). Насколько мне известно, готовых решений на pure ANSI C на данный момент нет. По крайней мере гугление ни к чему не привело.
При написании библиотеки столкнулся с рядом проблем, которые мне трудно решить. Поэтому публикую исходный код тестовой версии библиотеки. Я использую ANSI C'99 совместимый компилятор пока (в конце вычищу код для C'89), поэтому по ходу могут встречаться комментарии в стиле С++.
В тестовой версии библиотеки все глобальные переменные прямо лежат внутри заголовочного файла. Это сделано для упрощения отладки. Т.е. грубо говоря, эта версия всего лишь модель. После получения работоспособной модели все глобальные переменные будут локализованы в исходном файле библиотеки, будут статическими и доступ к ним будет осуществляться только через функции.
* Получить высокопортируемую библиотеку, которую можно будет использовать на любой платформе, где есть стандартная библиотека С. Классно иметь средство, которое будет компилироваться на любом ANSI C совместимом компиляторе!
* Незащищённость от многопоточночти из-за применения статических переменных. В планах, после завершения варианта для однопоточных переменных приступить к реализации версии, корректно работающей с многопоточными приложениями
Общая Концепция библиотеки состоит в следующем:
Для тех, кто дочитал до этого момента и не закрыл окно браузера, пояснение алгоритма и подводные камни.
Пояснение и различия с С++ подходом:
В либе реализован массив значений {буффер_сохранения_состояния, код возвращаемого значения} размером в максимальную глубину вложенности. При заходе в новый блок TRY индекс массива увеличивается и запоминается новое состояние. При выходе из блока TRY индекс массива декрементируется.
Неинформативные глобальные имена индексов i, j выбраны чисто для наглядности и удобства набора.![Smile :) :)](https://cdn.jsdelivr.net/joypixels/assets/8.0/png/unicode/64/1f642.png)
Переменная j была нужна для защиты от локальных переходов и возбуждения сигналов goto\return()\raise(). Она работала так:
Прошу помощи в реализации манипуляций с индексами массива и, возможно, логикой работы. Цель: сделать библиотеку "пуленепробиваемой" с некоторыми ограничениями.
При написании библиотеки столкнулся с рядом проблем, которые мне трудно решить. Поэтому публикую исходный код тестовой версии библиотеки. Я использую ANSI C'99 совместимый компилятор пока (в конце вычищу код для C'89), поэтому по ходу могут встречаться комментарии в стиле С++.
В тестовой версии библиотеки все глобальные переменные прямо лежат внутри заголовочного файла. Это сделано для упрощения отладки. Т.е. грубо говоря, эта версия всего лишь модель. После получения работоспособной модели все глобальные переменные будут локализованы в исходном файле библиотеки, будут статическими и доступ к ним будет осуществляться только через функции.
Цель создания библиотеки:
* Получить преимущества обработки исключений в языке С. О различиях между SEH и обычным подходом проверки возвращаемых значений функцией можно почитать у Б. Страуструпа в 14 главе русского "Язык программирования С++"* Получить высокопортируемую библиотеку, которую можно будет использовать на любой платформе, где есть стандартная библиотека С. Классно иметь средство, которое будет компилироваться на любом ANSI C совместимом компиляторе!
Из недостатков можно выделить:
* _Синтаксическую_ (но не идеологическую) несовместимость с С++ ;(* Незащищённость от многопоточночти из-за применения статических переменных. В планах, после завершения варианта для однопоточных переменных приступить к реализации версии, корректно работающей с многопоточными приложениями
Общая Концепция библиотеки состоит в следующем:
Код:
//..............
TRY {
// тестируемый код1
TRY {
// вложенный тестируемый код2
THROW (100);
// продолжение вложенного кода, который не выполнится
}
CATCH (200) {
// обработчик 1
}
CATCH (100) {
// обработчик 2, в который мы попадём
THROW (10);
// код, который никогда не выполнится
}
CATCHALL {
// код, обрабатывающий все остальные ситуации
}
TRYEND; // конец вложенного обработчика
}
CATCHALL {
// обработка всех исключительных ситуаций. Сюда мы попадём по строчке THROW (10);
}
TRYEND;
// ................
Код:
/* excpt.h -- тестовая версия заголовочного файла */
#ifndef N0PEXCEPT_H_
# define N0PEXCEPT_H_
# ifdef __cplusplus
extern "C" {
# endif
# include <stdio.h>
# include <setjmp.h>
/* Включение или выключение кода обработки исключений. Для выключения
необходимо закоментировать строчку */
# define N0PEXCEPT_ON_
/* Максимальный уровень вложений обработчика исключений */
# define MAX_NESTED_LEVEL_ 40
//------------------------------------------------------------------------------
/* Состояние текущего прерывания */
typedef struct EXCPT_T EXCPT_T;
struct EXCPT_T {
/* Буффер для сохранения состояния */
jmp_buf jbuf;
/* Код возврата из setjmp() */
int ret;
}; /* EXCPT_T */
/* Текущий глобальный уровень вложенности обработчиков исключений */
int i = -1;
/* Текущий локальный уровень вложенности обработчиков исключений */
int j = -1;
/* Массив структур состояний */
EXCPT_T jbuf[MAX_NESTED_LEVEL_];
//------------------------------------------------------------------------------
# ifdef N0PEXCEPT_ON_
#define DBGTRYIN(func, num) \
printf("i=%d, j = %d >>> " #func ".TRY." #num " >>>\n", i, j)
#define DBGTRYOUT(func, num) \
printf("i=%d, j = %d <<< " #func ".TRY." #num " <<<\n", i, j)
#define DBGCATCHIN(func, num, val) \
printf("\ti=%d, j = %d >>> " #func ".CATCH(" #val ")." #num " >>>\n", i, j)
#define DBGCATCHOUT(func, num, val) \
printf("\ti=%d, j = %d <<< " #func ".CATCH(" #val ")." #num " <<<\n", i, j)
#define DBGCATCHALLIN(func, num) \
printf("\ti=%d, j = %d >>> " #func ".CATCHALL." #num " >>>\n", i, j)
#define DBGCATCHALLOUT(func, num) \
printf("\ti=%d, j = %d <<< " #func ".CATCHALL." #num " <<<\n", i, j)
/* Попытка выполнить кусок кода */
# define TRY \
do { \
j = i; \
j++; \
jbuf[j].ret = setjmp(jbuf[j].jbuf); \
if (0 == jbuf[j].ret) {
# define TRYEND \
i = j; \
} \
} while (0)
/* Перехват исключительной ситуации */
# define CATCH(x) \
i = j; \
} else if ((x) == jbuf[j].ret) {
# define CATCHALL \
i = j; \
} else {
/* Выброс исключительной ситуации */
# define THROW(x) \
longjmp(jbuf[j].jbuf, (x))
# else
# define TRY
# define TRYEND
# define CATCH(x) if (0)
# define CATCHALL if (0)
# define THROW(x)
# endif /* N0PEXCEPT_ON_ */
# ifdef __cplusplus
}
# endif
#endif /* N0PEXCEPT_H_ */
Код:
/* main.c -- тестирующий библиотеку файл */
#include <stdio.h>
#include <setjmp.h>
#include "excpt.h"
void
foo(void);
void
func1(void);
void
foo(void)
{
printf("foo()\n");
// THROW(1);
printf("end foo()\n");
}
int
main(void)
{
printf("start main() : i=%d, j=%d\n", i, j);
/* TRY {
DBGTRYIN(main, 1);
TRY {
DBGTRYIN(main, 2);
THROW (100);
DBGTRYOUT(main, 2);
}
CATCH (100) {
DBGCATCHIN(main, 2, 100);
DBGCATCHOUT(main, 2, 100);
}
CATCHALL {
DBGCATCHALLIN(main, 2);
DBGCATCHALLOUT(main, 1);
}
TRYEND;
// THROW (10);
DBGTRYOUT(main, 1);
}
CATCH (10) {
DBGCATCHIN(main, 1, 10);
// THROW (5);
DBGCATCHOUT(main, 1, 10);
}
CATCHALL {
DBGCATCHALLIN(main, 1);
DBGCATCHALLOUT(main, 1);
}
TRYEND; */
func1();
printf("end main() : i=%d, j=%d\n", i, j);
return 0;
}
// Функция, в которой используются уже развёрнутые макроопределения
void
func1(void)
{
printf("start func1() : i=%d, j=%d\n", i, j);
do {
// j = i; // Это логическая ошибка
j++;
jbuf[j].ret = setjmp(jbuf[j].jbuf);
if (0 == jbuf[j].ret) {
{
DBGTRYIN(func1, 1);
// вложенный TRY
do {
j = i;
j++;
jbuf[j].ret = setjmp(jbuf[j].jbuf);
if (0 == jbuf[j].ret) {
{
DBGTRYIN(func1, 2);
longjmp(jbuf[j].jbuf, 10);
DBGTRYOUT(func1, 2);
}
// i = j; // если ты дошел до этой строчки, значит успешно (без THROW'ов, return'ов и raise()) выполнил TRY
} else if (10 == jbuf[j].ret) {
{
DBGCATCHIN(func1, 2, 10);
DBGCATCHOUT(func1, 2, 10);
}
// i = j;
// j = i;
} else {
{
DBGCATCHALLIN(func1, 2);
DBGCATCHALLOUT(func1, 2);
}
// i = j;
// j = i;
}
} while (0);
// конец вложенному TRY
longjmp(jbuf[j].jbuf, 100);
DBGTRYOUT(func1, 1);
}
i = j;
} else if (100 == jbuf[j].ret) {
{
DBGCATCHIN(func1, 1, 100);
DBGCATCHOUT(func1, 1, 100);
}
// i = j;
// j = i;
} else {
{
DBGCATCHALLIN(func1, 1);
DBGCATCHALLOUT(func1, 1);
}
// i = j;
// j = i;
}
} while (0);
printf("end func1() : i=%d, j=%d\n", i, j);
}
Для тех, кто дочитал до этого момента и не закрыл окно браузера, пояснение алгоритма и подводные камни.
Пояснение и различия с С++ подходом:
- Блоки обработчиков могут быть вложенными один в один. Аналогично с С++
- Если THROW
никто не обрабатывает, то программа вылетает с ошибкой. В либе реализуется за счёт передачи неверного значения в longjmp(), что крэшит программу на большинстве платформ. Полная аналогия с С++, за исключением невозможности (пока) зарегистрировать свой обработчик подобной ситуации.
- Если внутри блока выбрасывается THROW() исключение, то оно передаётся внешнему обработчику. Почти полная аналогия с С++, кроме того, что в либе передаётся аргумент, а в С++ тот же самый аргумент передаётся во внешний блок.
- Либа должна корректно обрабатывать ситуации, когда внутри блоков TRY\CATCH()\CATCHALL встречаются локальные переходы: goto\return()\raise(). И это одна из самых больших проблем при реализации алгоритма ;(((
В либе реализован массив значений {буффер_сохранения_состояния, код возвращаемого значения} размером в максимальную глубину вложенности. При заходе в новый блок TRY индекс массива увеличивается и запоминается новое состояние. При выходе из блока TRY индекс массива декрементируется.
Неинформативные глобальные имена индексов i, j выбраны чисто для наглядности и удобства набора.
![Smile :) :)](https://cdn.jsdelivr.net/joypixels/assets/8.0/png/unicode/64/1f642.png)
Переменная j была нужна для защиты от локальных переходов и возбуждения сигналов goto\return()\raise(). Она работала так:
1. Сейчас мы находимся в i-том состоянии.
2. Делаем предположение, что наш блок TRY дойдёт нормально, без goto\return()\raise()\THROW() до самого конца. Для этого j = i+1;
3. Выполняем блок TRY.
3.1 Если блок TRY завершился успешно без исключений и переходов, то мы устойчиво стоим на новом состоянии, которое нужно "застолбить" в i, т.е. i=j
3.2 Если блок TRY завершился неудачей и исключением, то наша попытка окончилась неудачей, но мы всё равно остались стоять на i-том состоянии и переброска i = j не выполняется
Прошу помощи в реализации манипуляций с индексами массива и, возможно, логикой работы. Цель: сделать библиотеку "пуленепробиваемой" с некоторыми ограничениями.