Приветствую Вас ГостьВоскресенье, 06.07.2025, 06:14

Light Midnight Inc.


Каталог статей

Главная » Статьи » Программирование » С/С++/MFC

Исключения.

Исключения традиционно относятся к сложной для понимания части 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 обработка исключений. Пользовались? Я нет. Поделитесь опытом

Категория: С/С++/MFC | Добавил: Cromartie (31.01.2013)
Просмотров: 497 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Наш опрос
Оцените мой сайт
Всего ответов: 543
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Реклама
Cheсking
Часы
Мини-чат
200
Друзья Сайта
  • Light Midnight - Ваша Еда
  • Light Midnight - Anim as life style
  • Поиск