Главное меню
Мы солидарны с Украиной. Узнайте здесь, как можно поддержать Украину.

Ответ

Обратите внимание: данное сообщение не будет отображаться, пока модератор не одобрит его.
Ограничения: максимум вложений в сообщении — 3 (3 осталось), максимальный размер всех файлов — 300 КБ, максимальный размер одного файла — 100 КБ
Снимите пометку с вложений, которые необходимо удалить
Перетащите файлы сюда или используйте кнопку для добавления файлов
Вложения и другие параметры
Проверка:
Оставьте это поле пустым:
Наберите символы, которые изображены на картинке
Прослушать / Запросить другое изображение

Наберите символы, которые изображены на картинке:

√36:
ALT+S — отправить
ALT+P — предварительный просмотр

Сообщения в этой теме

Автор аноним
 - сентября 27, 2011, 13:58
а я вот поэл и даже почти разобрался. Спасибо Алексей.
Автор Вадимий
 - сентября 23, 2009, 15:51
Не поэл
Автор Алексей Гринь
 - сентября 23, 2009, 15:42
Нет. Я не понимаю «Ээээ...». Ваше Ээээ... может означать от «Не поэл...» до «У вас ошибка».
Автор Вадимий
 - сентября 23, 2009, 15:37
Автор Алексей Гринь
 - сентября 23, 2009, 15:36
Автор Вадимий
 - сентября 23, 2009, 05:06
Ээээ...
Автор Алексей Гринь
 - сентября 23, 2009, 02:26
О! Я забыл упомянуть такой случай, что THROW может быть вызван внутри CATCH-блока (т. н. rethrow). В текущей реализации такой поворот событий приводил бы к зацикливанию.

Поэтому в dotorg_raise_exc (который маскируется by THROW) я добавил совсем ничего:

Цитировать/* Проверяем, есть ли для треда текущее исключение. Если есть, значит мы ещё в CATCH-блоке, ошибка не поймана, а уже вызывается THROW. */
    if(exst->data)
    {
        /* Релизим предыдущий объект исключения. */
        RELEASE(exst->data);

        /* Объявляем текущим исключением новое (которое кинуто в catch-блоке). */
        exst->data = data;

        /* Перескакиваем на высший контекст. Без этой строки зацикливалось бы на текущем контексте. */
        dotorg_pop_env();
    }
Автор Алексей Гринь
 - сентября 19, 2009, 12:46
Когда я поносил С++, единственное, что я на тот момент признавал ущербным в Си, так это отсутствие исключений. Казалось бы, это что-то на уровне ассемблера, генерируемым С++-компилятором, и всё, что можно сделать на голом Си, так это только жалкую пародию. Оказывается, чтобы реализовать полноценную систему исключений на Си, нужно всего лишь 4-5 часов.

Коденг
#include "System.h"

void innerfunc(void)
{
    THROW(Exception_new_void());
}

int main(void)
{
   TRY
   {
       printf("Before.\n");

       innerfunc();

       printf("After.\n");
   }
   CATCH(e)
   {
       if(TYPEOF(e) == typeof_Exception())
       {
           printf("Inside catch block!\n");

           CATCHED;
       }
   }
   END_TRY;

   printf("After try block.\n");

   return 0;
}


Аутпут:
ЦитироватьBefore.
Inside catch block!
After try block.

Process returned 0 (0x0)   execution time : 0.015 s
Press any key to continue.

Как видно, строка printf("After.\n"); внезапно не исполнилась, потому что кинулось исключение, и ход программы перескочил, наплевав на стек, из innerfunc в catch-блок.
Реализация такова, что спокойно обрабатывается любое количество вложенных try'ев (тут я вру, потому что конкретно в моей реализации предел стоит в 32, ибо мне было влом делать растягиваемый контекстный стек (что за стек, опишу ниже), но обычно больше двух-трёх вложенных try'ев никогда и не надо). Также реализация может маппить SEH-исключения, например Access Violation, в свои собственные и, соответственно, их ловить и обрабатывать. Также это всё потоко-безопасно (какой я молодец).

Реализуется это просто:

1. Создаётся один глобальный TLS-индекс (Thread Local Storage). Теперь каждый новый тред создаёт и заносит в этот индекс объект стека (обычная пользовательская структура типа MyStack<jmp_buf>, ничего низкоуровнего), то бишь каждый тред имеет по собственному локальному стеку контекстов (чтобы отличать от стека выполнения программы, буду называть "контекстный стек"). Тип jmp_buf (который в <setjmp.h>) нужен нам дальше.

2. Далее, макрос TRY разворачивается в "if(!setjmp(dotorg_push_env()))"

   Всё, что делает функция dotorg_push_env, это извлекает из TLS записанный туда контекстный стек (который из п. 1), push'ит его, и возвращает ссылку на буфер типа jmp_buf (память под него должна быть выделена уже)
   Функция setjmp (которая в <setjmp.h>) принимает ссылку на возвращённый объект типа jmp_buf, и записывает в этот буфер данные текущего контекста исполнения: каково текущее смещение программного стека (настоящего, не пользовательского), ещё что-то, в зависимости от платформы. Эта функция обычно compiler-intrinsic и реализуется через пару ASM-опкодов (EDIT: в msvcrt.dll это реальный вызов функци...)

3. Макрос THROW(x) разворачивается в "longjmp(dotorg_raise_exc((void*)(x)), 1)"

   Функция dotorg_raise_exc, опять, извлекает из TLS контекстный стек (который из п. 1), и pop'ит jmp_buf. Этот jmp_buf есть самый последний (most recent) контекст-буфер, записанный в макросе TRY.
   Функция longjmp получает этот буфер, и здесь-то и происходит самое интересное — unwind'ится (разматывается) стек. Происходит прыжок с текущего места в место, записанное в jmp_buf, наплевав на ход выполнения программы, и какая функция чего вызывала. Эта же функция сохраняет в TLS ссылку на x (это объект исключения, он нам нужен будет потом)

4. Далее, макрос CATCH(x) разворачивается в "else { System_Object* x = (System_Object*)dotorg_get_exc();".
    System_Object — это мой тип, он тут может быть любым, заострять внимание не надо. Функция dotorg_get_exc извлекает объект исключения, записанный by dotorg_raise_exc.

5. Далее, если ошибка поймана, нужно систему об этом предупредить. Макрос CATCHED разворачивается в "dotorg_exit_catch()". Эта функция просто затирает ссылку на текущий объект исключения в ноль (в моей реализации подсчёта ссылок заодно вызывается RemoveRef, чтобы сразу удалить).

6. Теперь настало время макроса END_TRY. Он у меня разворачивается в "} dotorg_pop_env()".

   Эта функция убивает двух зайцев:
         а) pop'ит контекстный стек. Проверяет текущий объект исключения. Если он затёрт через CATCHED, то ошибка поймана, всё прекрасно, ничего более не делать.
         б) Но если текущий объект исключения не равен нулю —  то это значит, что ошибка до сих пор не поймана! Тогда функция пытается pop'ить контекстный стек ещё раз. Если не получается, т.е. контекстов больше нет (т.е. больше нет TRY-блоков), значит исключение unhandled. Программа завершается. Но, если в контекстном стеке есть ещё один контекст — значит выше по программному стеку есть ещё один TRY-block. Далее происходит магия:

                           void* higher_env = (void*)&(((&exst->items)[--exst->sp]).buf);
                           longjmp(higher_env, 1);


Вуаля. Стек unwind'ится дальше. Здесь что-то типа рекурсии, но такая рекурсия не ест программный стек. Разматывая стек, постоянно будет псевдорекурсивно вызываться dotorg_pop_env в вышележащих stackframe'ах. Когда-то ошибка словится. А когда-то контексты в контекстном стеке кончатся, тогда выведется ошибка "Program encountered unhandled exception of type 'blabla'".

7. Если исключения и вовсе не было, то CATCH-блок попросту проскакивается (а TRY исполняется до конца). Вся магия в том, что макросы в конце концов разворачиваются в такую конструкцию:

Цитироватьif(!setjmp(dotorg_push_env())) /* TRY */
      {
             
      }
      else /* CATCH */
      {
      }
      dotorg_pop_env(); /* END_TRY */

   Функция setjmp возвращает 1, если она вызвалась после перехода по longjmp, и 0 — если в ходе обычного хода программы. В первом случае будет перескок в else-block, во втором случае else-block, напротив, будет проигнорирован.

8. Теперь как мапятся SEH-исключения. При создании TLS-индекса под контекстный стек, мы пишем такую шляпу:

     <..> SetUnhandledExceptionFilter(&m_SEH_handler); <..>

Самая функция примерно такая:

static LONG WINAPI m_SEH_handler(LPEXCEPTION_POINTERS pp)
{
    switch(pp->ExceptionRecord->ExceptionCode)
    {
        case EXCEPTION_ACCESS_VIOLATION:
            /* Тут можно выкидывать какой угодно собственный объект. */
            THROW(System_AccessViolationException_new(S("No-o-o!")));
            break;

        /* ... etc. */

        default:
            return EXCEPTION_CONTINUE_SEARCH;
    }

    return EXCEPTION_CONTINUE_EXECUTION;
}



Пример:
#include "System.h"

void innerfunc(void)
{
    /* Намеренно вызываем SEGFAULT, чтобы получить SEH-исключение. */
    int *a = 0;
    a = *a;
}

int main(void)
{
   TRY
   {
       printf("Before.\n");

       innerfunc();

       printf("After.\n");
   }
   CATCH(e)
   {
       printf("OMG! Access violation!\n");

       CATCHED;
   }
   END_TRY;

   return 0;
}


Аутпут:
ЦитироватьBefore.
OMG! Access violation!

Process returned 0 (0x0)   execution time : 0.015 s
Press any key to continue.

Вот так. Написал всё это, чтобы не забыть реализацию, и, может, пригодится кому.

Теперь думаю, как лучше реализовать RTTI. Наверное, не обойтись без DSL.