C++ C++ C# C# ASP.NET Security ASP.NET Security ASM ASM Скачать Скачать Поиск Поиск Хостинг Хостинг  
  Программа для работы с LPT портом...
Язык: .NET — ©Alexey...
  "ASP.NET Atlas" – AJAX в исполнении Micro...
Язык: .NET — ©legigor@mail.ru...
  "Невытесняющая" Многопоточность...
Язык: C/C++ — ©...
  01.05.2010 — Update World C++: Сборник GPL QT исходников
  15.12.2007 — Весь сайт целиком можно загрузить по ссылкам из раздела Скачать
Хостинг:
Windows 2003, ASP.NET 2.0
бесплатный и от 80 руб./мес


   Отправить письмо
Кулабухов Артем, Беларусь




 Исключения / C++ для начинающих / C++

Исключения.



Исключения традиционно относятся к сложной для понимания части 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).


#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-а. Вот как лучше всего это сделать.

//код приведён для 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 исключения?


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

или 

__except(EXCEPTION_EXECUTE_HANDLER) //подставить нужное слово
{
//...
}

__try
{
//....
}
__finaly
{
//...
}



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

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

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

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

Я приведу пример, как можно ловить исключения при работе с файлами, а за подробностями отправлю к MSDN.

 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 не сработает.

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 - 30.03.04, 10:58
Скачать пример