Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Эмуляция обработки исключений в ANSI C'89 
:(
    Опции темы
sergey_kovtunenko
Дата 26.8.2007, 14:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 3
Регистрация: 26.8.2007

Репутация: нет
Всего: нет



Привет! Пришла идея написать эмуляцию обработки исключительных ситуаций для ANSI C. Это должна быть высокопортируемая библиотека макроопределений, использующая достоинства (и недостатки нелокальных переходов в С -- setjmp()\longjmp()). Насколько мне известно, готовых решений на pure ANSI C на данный момент нет. По крайней мере гугление ни к чему не привело.
При написании библиотеки столкнулся с рядом проблем, которые мне трудно решить. Поэтому публикую исходный код тестовой версии библиотеки. Я использую 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(n) никто не обрабатывает, то программа вылетает с ошибкой. В либе реализуется за счёт передачи неверного значения в longjmp(), что крэшит программу на большинстве платформ. Полная аналогия с С++, за исключением невозможности (пока) зарегистрировать свой обработчик подобной ситуации.
  •  Если внутри блока выбрасывается THROW() исключение, то оно передаётся внешнему обработчику. Почти полная аналогия с С++, кроме того, что в либе передаётся аргумент, а в С++ тот же самый аргумент передаётся во внешний блок.
  •  Либа должна корректно обрабатывать ситуации, когда внутри блоков TRY\CATCH()\CATCHALL встречаются локальные переходы: goto\return()\raise(). И это одна из самых больших проблем при реализации алгоритма ;(((

В либе реализован массив значений {буффер_сохранения_состояния, код возвращаемого значения} размером в максимальную глубину вложенности. При заходе в новый блок TRY индекс массива увеличивается и запоминается новое состояние. При выходе из блока TRY индекс массива декрементируется.

Неинформативные глобальные имена индексов i, j выбраны чисто для наглядности и удобства набора. ;)

Переменная 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 не выполняется



Прошу помощи в реализации манипуляций с индексами массива и, возможно, логикой работы. Цель: сделать библиотеку "пуленепробиваемой" с некоторыми ограничениями. 

Это сообщение отредактировал(а) sergey_kovtunenko - 26.8.2007, 14:27
PM MAIL   Вверх
MAKCim
Дата 26.8.2007, 15:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 52
Всего: 207



если у тебя
Цитата(sergey_kovtunenko @  26.8.2007,  14:22 Найти цитируемый пост)
#        define TRY \
            do { \
                j = i; \
                j++; \
                jbuf[j].ret = setjmp(jbuf[j].jbuf); \
                if (0 == jbuf[j].ret) {

а
Цитата(sergey_kovtunenko @  26.8.2007,  14:22 Найти цитируемый пост)

#define CATCH(x) \
                    i = j; \
                } else if ((x) == jbuf[j].ret) {

то
конструкция
Код

TRY {
}
CATCH(100) {
}

будет иметь вид
Код

do {
    j = i;
    j++;
    jbuf[j].ret = setjmp(jbuf[j].jbuf);
    if (0 == jbuf[j].ret) { 
        { }
        i = j;
        else if ((x) == jbuf[j].ret) { 
            { }

т. е неправильный вид
может так
Код

#        define TRY \
            do { \
                j = i; \
                j++; \
                jbuf[j].ret = setjmp(jbuf[j].jbuf); \
                if (0 == jbuf[j].ret)

Код

#define CATCH(x) \
                else if (i = j, (x) == jbuf[j].ret)


Цитата(sergey_kovtunenko @  26.8.2007,  14:22 Найти цитируемый пост)
В либе реализуется за счёт передачи неверного значения в longjmp(), что крэшит программу на большинстве платформ. Полная аналогия с С++, за исключением невозможности (пока) зарегистрировать свой обработчик подобной ситуации.

решение такое
Код

#define TRY(x) \
    limit = x; /* limit определен где-то выше */
...
#define CATCH(x) \
    else if (++count, i = j, (x) == jbuf[j].ret ? 1 : count == limit ? terminate() : 0)

где count номер текущего CATCH (увеличивается на 1 с каждым CATCH пока не достигнут limit (тогда вызывается terminate()))


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
sergey_kovtunenko
Дата 26.8.2007, 17:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 3
Регистрация: 26.8.2007

Репутация: нет
Всего: нет



Цитата(MAKCim @ 26.8.2007,  15:22)
конструкция
Код

TRY {
}
CATCH(100) {
}

будет иметь неправильный вид

Посмотри, я в первом блоке кода писал про синтаксис обработчиков... В конце должен быть TRYEND; который и делает обрамляющий }while(0);
PM MAIL   Вверх
MAKCim
Дата 26.8.2007, 17:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 52
Всего: 207



sergey_kovtunenko, 
нет, я про то, что конструкция
Код

if (...) {
  {}
  else if (...) {

неправильна


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
sergey_kovtunenko
Дата 28.8.2007, 00:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 3
Регистрация: 26.8.2007

Репутация: нет
Всего: нет



Цитата(MAKCim @ 26.8.2007,  17:57)
sergey_kovtunenko, 
нет, я про то, что конструкция
Код

if (...) {
  {}
  else if (...) {

неправильна

Посмотри внимательней на код макросов TRY, TRYEND, CATCH, CATCHALL. Даже форматирование кода позволяет увидеть, что количество открывающихся и закрывающихся скобок сопоставлено.
Код

#        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))

Да и не может быть по другому, ведь при ошибке со '}' код бы просто не скомпилировался.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0499 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.