На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> Исключения
    Исключения.

    Исключения традиционно относятся к сложной для понимания части C++. В форумах часто возникают вопросы на эту тему.. в общем – статья назрела.
    Я не претендую на авторство высказанных здесь мыслей, я просто собрал всё вместе.


    Исключение – это явление, которое происходит при ненормальном развитие событий в программе и требует особой логики обработки. В идеале правильно спроектированная программа не нуждается в обработке исключений. Дело в том, что ситуации, ведущие к исключениям, можно отлавливать на ранних стадиях, анализируя все возвращаемые функциями коды ошибок. Если в вашей программе вы хотите отгородится исключением от попыток деления на 0 или выделения –200 байт (читается «минус двухсот байт»), то вы находитесь далеко от правильного пути. И в этом случае лучше доработать алгоритм работы. Заранее проверить «Сколько байт я хочу выделить?». Заранее проверить, «а на что же я собираюсь поделить?». Стандарт С (ISO/IEC 9899) не содержит обработку исключений. Это ещё один довод в пользу того, что правильно спроектированная программа должна правильно работать и без них.

    Живём мы в реальном мире, поэтому обработка исключений нам нужна. Операционная система не смогла выделить те 500 Мб, которые вы попросили. Драйвер не оказался загруженным. Файла не оказалось в нужное время в нужном месте. Да мало ли ещё что произошло! И вот тут нас спасёт обработка исключений. Кроме того, иногда исключения упрощают вывод сообщений об ошибках в программе, например, при использовании MFC исключений. Об этом - впереди. В общем, исключения – это дешёвый способ существенно повысить устойчивость вашего (особенно системного или серверного) программного обеспечения.

    Я не буду рассказывать про обработку исключений подробно, про это гораздо лучше описано в учебной литературе, а отвечу лишь на некоторые часто задаваемые вопросы.


    Вопрос. Есть ли в С обработка исключений?
    Обработка исключений есть только в C++. Стандарт С (ISO/IEC 9899) её не содержит.

    Вопрос. Кто берёт на себя ответственность за обработку исключений?
    CRTL – C-Run-Time-Library.

    Вопрос. Что такое SEH?
    SEH – Structured Exception Handling – в операционной системе Windows включена обработка исключений на уровне операционной системы. Блоки SEH оформляются с помощью операторов __try, __finally, __except. Если SEH-исключение не перехвачено, то произойдет появление хорошо известного окна с предложением впаять разработчику и остановка процесса.




    Вопрос. Чем плохо использовать операторы __try, __except и т.д.
    Плохая совместимость с программами, написанными на «чистом» С++. В пределах одной функции невозможно пользоваться CRTL и SEH исключениями.
    Если вы работаете с Visual Studio 6, CRTL преобразует стандартное исключение в SEH исключения в случае, если при сборке проекта указан ключ /EHa или (эквивалентный) /GX, и установки галочки Enable Exception Handling в состояние No. В Visual Studio 7: заходим на вкладку свойств проекта C/C++ Code Generation, находим строчку Enable C++ exception, ставим в этой строчке No. Дальше двигаемся в конец к секции Command Line. В ней есть окошко Additional Options. Надо прописать /EHac.
    Недостаток этого подхода - невозможно определить тип исключения, и ,что более серьёзно, возникают проблемы с плавающей арифметикой (последнее утверждение не проверял, но поверил одному товарищу). Имеется более усовершенствованный метод. Заключается он в использовании так называемого se транслятора. Вот примерный код (Visual Studio).

    ExpandedWrap disabled
       
      #include <eh.h>
       
      static void se_translator(unsigned int code,_EXCEPTION_POINTERS *)
       {
        if( code == EXCEPTION_FLT_DENORMAL_OPERAND   ||
            code == EXCEPTION_FLT_DIVIDE_BY_ZERO     ||
            code == EXCEPTION_FLT_INEXACT_RESULT     ||
            code == EXCEPTION_FLT_INVALID_OPERATION  ||
            code == EXCEPTION_FLT_OVERFLOW           ||
            code == EXCEPTION_FLT_STACK_CHECK        ||
            code == EXCEPTION_FLT_UNDERFLOW)
          {
           short cw=???;        
              
           __asm {
                    fninit
                    fldcw  cw
                 }
          }
       
        throw Exception(CPUError(code));
       }
       
      void Setup_SE_translator()
      {
        _set_se_translator(se_translator);
      }


    Смысл кода приблизительно в следующем. Вызовом функции _set_se_translator можно установить функцию, которая будет получать управление в случае возникновения в текущем потоке SEH-исключения. Главное назначение этой функции - получить код SEH-исключения, завернуть в подходящую обёртку и выбросить нормальное C++ исключение, которое в дальше можно поймать обычным catch(). Коды этих исключений можно получить из windows.h, а описание - в MSDN в статье про EXCEPTION_RECORD, либо в прикрепленном файле. Среди этих кодов есть семейство особо важных, связанных с плавающей точкой. При получении одного из этих кодов, надо делать маленькую дополнительную обработку. А именно, нужно командой fninit сбросить сопроцессор в нормальное состояние и загрузить подходящее слово управление. Иначе флаги исключений по-прежнему будут висеть в сопроцессоре, что вызовет возбуждение нового исключения при попытке его снова использовать – Вам оно нужно? Вообще, использование исключений сопроцессора -- это отдельный нетривиальный вопрос.

    Вопрос. Я работаю с STL, очень часто использую операцию push_back(), при этом не знаю, как контролировать ситуацию, когда память push_back – ом не выделена, потому что push_back не возвращает код ошибки. Как мне быть?

    Всё нормально – вам необходимо ловить исключение std::bad_alloc – именно оно генерируется в случае неудачного проведения операции push_back. И не только push_back() – но и вообще везде, где STL перераспределяет память – например, resize().

    Вопрос. Я пытаюсь поймать исключение std::bad_alloc при выделении памяти оператором new, но у меня ничего не получается. Помогите!
    Тут возможны несколько причин.
    1) Генерация стандартного исключения std::bad_alloc возможна только стандартным оператором new. То есть для начала необходимо сделать как минимум #include <new>.
    2) Стандарт гарантирует, что в памяти сможет расположиться std::bad_alloc. Если вы напишете catch(std::bad_alloc){}, то при этом CRTL будет пытаться расположить в памяти не только сам bad_alloc, но и его копию. Про копию Стандарт C++ ничего не говорит, поэтому CRTL может игнорировать копии bad_alloc-а. Правильнее писать: catch(std::bad_alloc &){}.
    3) Вы не загрузили std::bad_alloc в качестве new handler-а. Вот как лучше всего это сделать.
    ExpandedWrap disabled
       
      //код приведён для Visual C++, в иных компиляторах возможны изменения//
      #include <new>
      #include <new.h>
       
      //функция установки new handler-a.
      int _cdecl my_new_handler(size_t)
      {
      throw std::bad_alloc();
      return 0;
      }
       
      //это в какой нибудь функции:
       
        _PNH                _old_new_handler;
        _old_new_handler = _set_new_handler(my_new_handler);
       
      //тут new будет кидать исключения std::bad_alloc
       
        _set_new_handler(_old_new_handler);


    Обязательно ли возвращать old_new_handler на место – не знаю, скорее всего необязательно. По моему - лучше всего это сделать один раз в самом начале программы, а по завершении – вернуть old_new_handler. С другой стороны производительность стандартного оператора new (как и всего остального стандартного) немного хромает, если вы желаете добиться экстра производительности – то old_new_handler лучше вернуть на место. В общем – я предупредил – остальное на вашей совести.

    4) Вы работаете с MFC. В этом случае вы можете поймать только указатель на исключение CException либо производный от него. В этом случае, если вы будете, например, пытаться выделить большое количество памяти, то MFC будет упорно кидать сообщение «Out of memory». И с этим ничего поделать нельзя – придётся ловить MFC исключения (не помогает даже ручная установка new handler-а), это видимо сделано под девизом «Мы в Майкрософт, всегда считаем, что стандарт можно улучшить» (Copyright кто то из MS, но не Билл Гейтс);

    Вопрос. У меня что то случилось с размером контейнера при вызове исключения std::bad_alloc – size() вернул одно, а перечисление с помощью итератора – на один элемент больше.
    Такое бывает если исключение кидает конструктор копии – size() не учитывает недоконструированный элемент, а при перечислении он может и остаться, это касается контейнеров std::list, std::dequе и других. Это – «особенность дизайна» некотрых реализаций STL, например, той, что поставляется с Visual C++. Exception safety контейнеров стандартной библиотеки была добавлена в последний момент процесса стандартизации, поэтому далеко не все реализации контейнеров правильно ведут себя в присутствии исключений. Так версия STL от Dinkumware, что поставляется с VC 6 тянется ещё со времен VC 4.2, т.е. года 1994 - последняя версия стандарта C++ вышла в 1998 году (комментировать нужно?). Бороться с этим можно путём обновления STL на более свежую реализацию (например, от STLPort – www.stlport.com). Либо не бросать исключения в конструкторах.


    Вопрос. Перечислите плюсы и минусы использования SEH по сравнению с обычными CRTL исключениями.
    Плюсы:
    1) позволяет ловить больший спектр исключений, к которым относится деление на 0, переполнение стека, и т.д.
    2) обработка исключений ведётся на уровне ядра операционной системы (в WinNT образных ОС);
    3) возможность использовать исключения без CRTL. Часто для уменьшения размера программы её собирают без CTRL. В этом случае использовать «стандартные» C++ исключения невозможно. SEH можно будет воспользоваться, если загрузить kernel32.dll.

    Минусы:
    1) плохая совместимость с С++. SEH исключения реализованы на уровне ядра ОС, которое ничего не знает про С++, например про классы. Если произошла исключительная ситуация, то SEH не гарантирует, что уберёт за собой весь мусор, потому что не будут вызваны деструкторы пользовательских классов. Это связано с тем, что если компилятор не видит генерации C++-исключений, то он и не создает код, который отвечает за размотку стека при исключениях (только при использовании слов __try, __except и т.д.).
    2) невозможность (в пределах одной функции) пользоваться SEH и стандартными исключениями одновременно.

    Вопрос. Как использовать SEH исключения?

    ExpandedWrap disabled
       
      __try
      {
      //....
      }
      __except(GetExceptionCode() == ….) //подставить нужное слово
      {
      //....
      }
       
      или
       
      __except(EXCEPTION_EXECUTE_HANDLER) //подставить нужное слово
      {
      //...
      }
       
      __try
      {
      //....
      }
      __finaly
      {
      //...
      }


    GetExceptionCode() – возвращает код возникшего исключения – можно использовать для вывода диагностического сообщения. Если Вы используете слово __finaly, то этот блок будет выполнен в любом случае, даже если попытаться выйти из блока __try с помощью return;
    В одном блоке __except и __finaly одновременно быть не могут.

    Кроме того, можно получить машинно-независимую информацию об исключении, при помощи функции GetExceptionInformation().

    Структурная обработка особых ситуаций средствами Win32 API

    Вопрос. Как ловить MFC исключения?

    Я приведу пример, как можно ловить исключения при работе с файлами, а за подробностями отправлю к MSDN.
    ExpandedWrap disabled
       
       CFile  f;
       CFileException *pE = new CFileException;
                  TCHAR       szErrorString[255];  
       
       if (f.Open(m_sDraftName, CFile::modeRead | CFile::shareDenyWrite, pE) == FALSE)
       {  
      pE->ReportError(MB_OK | MB_ICONSTOP);
      pE->GetErrorMessage(szErrorString, 255);
      WriteErrorInLogFile(szErrorString); //функция записи в лог (пользовательская).
          pE->Delete();
          return FALSE;
      }
      delete pE;


    У класса CException и его производных имеется метод ReportError – который выводит на экран сообщение об ошибке.
    Так же из этого сообщения можно просто сформировать строку, например, для вывода в log файл. Для этого есть метод GetErrorMessage();
    Так же MFC исключения можно ловить дедовским способом try/catch.

    Вопрос. В Visual C++ я видел операторы try и TRY. В чём отличие и чем лучше пользоваться?

    Макросы TRY/CATCH/AND_CATCH/END_CATCH/THROW/THROW_LAST тянутся из тех времен, когда компилятор C++ от MS еще не поддерживал стандартную обработку исключений. Пользоваться ли ими – это уже ваш выбор, но в свете сказанного ранее – не советую.

    Вопрос. Как насчёт быстродействия кода получаемого при использовании исключений?
    Быстродействие его практически не страдает, но вот объём существенно возрастает. И всё из за добавления кода «для отката».


    Вопрос. Как насчёт исключений в UNIX-like системах?
    В UNIX-ах при возникновении исключений система шлёт сигналы, например, при возникновении ошибки с плавающей точкой FreeBSD шлёт сигнал SIGFPE – Floating Point Exception.

    Вопрос. Нестандартное использование исключений.
    Естественно, можно использовать исключения в нестандартных ситуациях. Например, для выхода из многоступенчатого цикла – т.е. там, где break не сработает.
    ExpandedWrap disabled
       
      try
      {
        for(..)
        {
           for(..)
           {
              if(...)
                 throw;//генерация исключения
           }
        }
      }
      catch(..)
      {
      }



    Более подробную информацию по ловле исключений читайте в прикрепленном файле. (40 927 байт в zip архиве, всё написано русским по белому – кто не испугался – срочно качаем!).

    Вот ещё одна полезная ссылка по теме:
    http://msdn.microsoft.com/library/default....xceptdotnet.asp



    Осталось неосвещенным много чего. Перечислю:

    1) Exception handling в DOS, Win9x, UNIX.
    2) Exception handling в компиляторах Borland (я слышал, что им не нужно изгаляться с преобразование C++ исключения в SEH, а что Borland кидает исключительно SEH исключения? – Borland не претендует на универсальность, и в данном случае это просто прекрасно!).
    3) Exception handling в ИмяРек компиляторах.
    4) Exception handling в OLE/COM.
    5) Ещё я слышал, что появилась VEH обработка исключений. Пользовались? Я нет. Поделитесь опытом

    Специалисты по этим вопросам – откликнитесь!
    Сообщение отредактировано: AQL -

    Прикреплённый файлПрикреплённый файлExceptions.zip (39.97 Кбайт, скачиваний: 467)
      Цитата
      AQL, 23.03.04, 15:28
      Вопрос. Как насчёт быстродействия кода получаемого при использовании исключений?
      Быстродействие его практически не страдает, но вот объём существенно возрастает. И всё из за добавления кода «для отката».


      Я бы уточнил, что не страдает, если исключения не возникают. Если же исключения возникают часто (например, используются не по назначению, не только для обработки исключительных ситуаций), то производительность может упасть. Насколько -- зависит от ситуации. Например, большое кол-во catch блоков, ловящих исключения с глубокой иерархией предков по базовому классу -- для каждого будет сравнение typeinfo всех классов в иерархии.
      Тут многое зависит от реализации. В целом же, я согласен с тем, что при правильном использовании исключений производительность не страдает. Главное не забывать, что окончательный вывод о недостаточной производительности того или иного куска кода нужно делать с помощью профайлера.

      Добавлено в :
      Цитата
      AQL, 23.03.04, 15:28
      Осталось неосвещенным много чего. Перечислю:
      ...
      3) Exception handling в ИмяРек компиляторах.

      А вот тут есть про VC:
      How a C++ compiler implements exception handling - C++ - MFC
        Страуструп пытался создать язык таким образом, что если средство не используется, то и не должна ухудшаться производительность или увеличиваться расход памяти.
          Думаю, будет уместно добавить, что существуют реализации (по крайней мере, одна :)) псевдо-исключений для Си через longjmp. В аттаче - одна из них - хорошо документированный, правда, по-английски, хедер.
          Прикреплённый файлПрикреплённый файлcexcept.h (9.92 Кбайт, скачиваний: 546)
            Цитата

            В идеале правильно спроектированная программа не нуждается в обработке исключений. Дело в том, что ситуации, ведущие к исключениям, можно отлавливать на ранних стадиях, анализируя все возвращаемые функциями коды ошибок.


            А я думаю, что анализ возвращаемых функциями значений является "не слишком приемлемой в рамках языка С++" альтернативой обработке исключений, т.к. последние являются стандартным средством языка С++ для создания отказоустойчивого кода, хотя, и проверка возвращаемых функциями значений и обработка исключений неизбежно увеличивают размер кода, который приходится просматривать при сопровождении. Страуструп, по этому поводу, предлагает использовать искдючения там, где стандартная локализация ошибки является неприемлемым или не элегантным решением той или иной проблемы.
              Цитата

              я думаю, что анализ возвращаемых функциями значений является "не слишком приемлемой в рамках языка С++" альтернативой обработке исключений, т.к. последние являются стандартным средством языка С++ для создания отказоустойчивого кода, хотя, и проверка возвращаемых функциями значений и обработка исключений неизбежно увеличивают размер кода, который приходится просматривать при сопровождении. Страуструп, по этому поводу, предлагает использовать искдючения там, где стандартная локализация ошибки является неприемлемым или не элегантным решением той или иной проблемы.


              я кажется именно это и написал? Дело в том, что довольно глупо, например, отлавливать с помощью исключений деления на 0. И ещё можно привести массу примеров, когда можно обойтись без них. Исключения - для исключительных случаев!
                Цитата AQL @ 27.07.04, 08:41
                я кажется именно это и написал? Дело в том, что довольно глупо, например, отлавливать с помощью исключений деления на 0. И ещё можно привести массу примеров, когда можно обойтись без них. Исключения - для исключительных случаев!

                Вообще-то, деление на 0 отлавливает процессор аппаратно, и обработчик этого исключения обычно пустует, если такое случиться с процессом приложения, то ось его завершит, а если обработчик написать, то ось его подключит. Может это неудобно программисту, но юзеру....
                  Тут поступила информация о том, что при использовании __try и __except можно избежать утечки ресурсов (читай - вызвать все деструкторы.
                  Итак, внимание, вопрос (для меня тоже интересный) - КАК?
                    Цитата BugHunter @
                    Тут поступила информация

                    От кого информация-то поступила? Из агентства ОБС? :D

                    Цитата
                    при использовании __try и __except

                    Может, всё-таки __try / __finally ?

                    Цитата
                    избежать утечки ресурсов (читай - вызвать все деструкторы.

                    Интересное прочтение :)
                    ExpandedWrap disabled
                      ....
                      HANDLE hFile = INVALID_HANDLE_VALUE;...
                      __try
                      {
                      .....
                        hFile = CreateFile.....
                      .....
                        (int*)0 = 0;
                      .....
                      }
                      __finally
                      {
                        if (hFile != INVALID_HANDLE_VALUE)
                          CloseHandle(hFile);
                      }

                    Кто скажет, что здесь нету кода для избежания утечки ресурсов - пусть кинет в меня камнем.
                    А кто скажет, что тут вызываются деструкторы - в того я сам кину чем-нибудь. :)
                      ну, так все умеют :).
                      Не, где то я встречал именно про деструкторы :yes:. И в этом весь сахар.
                        Информация поступала от ТрефПтицы, но она почему то не хочет дописывать сюда ничего :( (давно уже просил).
                          Вроде вызываются деструкторы в случае SEH. Проверить несложно - тривиальный пример :)
                          Для компилятора внутренний механизм CEH и SEH практически один и тот же, т.е. SEH кинутый в одному модуле, можно поймать CEH-оым catch-ем в другом. Для примера можно взять исключения COM, которые ловятся CEH-catch-ем на обьект _com_error
                            Цитата grustnoe @
                            Вроде вызываются деструкторы в случае SEH.

                            Вызываются, никто не спорит. Но они и без SEH вызываются.
                            К тому же есть большое ограничение - не разместишь автоматические объекты с деструкторами в фунциях с блоками __try/__except/__finally
                            Так что непонятно всё...
                              аха, именно поэтому, наверно, я SEH-ом не пользуюс :no:
                                Цитата
                                В идеале правильно спроектированная программа не нуждается в обработке исключений. Дело в том, что ситуации, ведущие к исключениям, можно отлавливать на ранних стадиях, анализируя все возвращаемые функциями коды ошибок

                                Использование исключений и анализ возвращённых кодов - это два способа организовать общую стратегию обработки ошибок на уровне приложения. Если я использую исключения, то это не означает, что мой код менее идеален. Использование того или иного способа зависит от стиля программирования и от конкретной ситуации. Некоторые коммерческие и free библиотеки позволяют при помощи макросов собрать версию библиотеки с требуемым способом обработки ошибок. Об исключениях хорошо написал Герб Саттер в книге "Новые сложные задачи на С++"
                                ... там же написано и про это:
                                Цитата
                                Операционная система не смогла выделить те 500 Мб ...

                                Цитата
                                ... и вот тут нас спасёт обработка исключений

                                ... нет не спасёт.
                                Удачи!
                                Сообщение отредактировано: T0ad -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0646 ]   [ 15 queries used ]   [ Generated: 28.04.24, 03:12 GMT ]