Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 2 августа!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

ПРОГРАММИРОВАНИЕ НА C и С++



Давно заметил, что всё-таки языки С/C++ это не самый лучший вариант программирования под Windows. Сейчас появилась масса более современных и удобных языков, например тот же Python - кроссплатформенный язык, очень легок в изучение. Я его изучил буквально за несколько дней по этому курсу - ссылка. Автор постарался, там видеоуроки на удивление легкие и понятные.

Исключения в C++


Введение
Язык С представляет программисту очень ограниченные возможности обработки исключений, возникших при работе программы. В этом отношении С++ намного развитее С. Здесь у программиста существенно большие возможности по непосредственной обработке исключений. Комитет по разработке стандартов С++ предоставил очень простую, но мощную форму обработки исключений.Темные дни С
Типичная функция, написанная на С, выглядит примерно так:
 long DoSomething()
   {
   long *a, c;
   FILE *b;
   a = malloc(sizeof(long) * 10);
   if (a == NULL)
   return 1;
   b = fopen("something.bah", "rb");
   if (b == NULL) {
   free(a);
   return 2;
   }
   fread(a, sizeof(long), 10, b);
   if (a[0] != 0x10) {
   free(a);
   fclose(b);
   return 3;
   }
   fclose(b);
   c = a[1];
   free(a);
   return c;
   }
 

Выглядит не очень, не так ли? Вы целиком и полностью зависите от значений, которые возвращают вам функции и для каждой ошибки вам постоянно нужен код, который ее обрабатывает. Если вы, скажем, в функции работаете хотя бы с 10 указателями (рапределяете память, освобождаете ее и т.д.), то наверняка половину кода функции будет занимать код обработки ошибок. Такая же ситуация будет в коде, вызывающем эту функцию, так как здесь также нужно обработать все возвращаемые коды ошибок.Try-catch-throw
Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:
  • try (пытаться) - начало блока исключений;
  • catch (поймать) - начало блока, "ловящего" исключение;
  • throw (бросить) - ключевое слово, "создающее" ("возбуждающее") исключение.
А теперь пример, демонстрирующий, как применить то, что вы узнали:
 void func()
   {
   try
   {
   throw 1;
   }
   catch(int a)
   {
   cout << "Caught exception number: " << a << endl;
   return;
   }
   cout << "No exception detected!" << endl;
   return;
   }
 


Если выполнить этот фрагмент кода, то мы получим следующий результат:
 Caught exception
   number: 1
 

Теперь закоментируйте строку throw 1; и функция выдаст такой результат:
 No exception detected!
 

Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может "ловить" любой тип данных, так же как и throw может "кинуть" данные любого типа. Т.е.
 throw AnyClass();
 
будет правильно работать, так же как и
 catch (AnyClass &d) {};
 
.Как уже было сказано, catch может "ловить" данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого:
 catch(dumbclass)
   { }
 

так же, как и
 catch(dumbclass&)
   { }
 

Так же можно "поймать" и все исключения:
 catch(...) { }
 

Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если "кидаются" данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше "ловить" их по ссылке, иначе вся "кидаемая" переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков catch, ловящих "свой" тип данных:
 try {
   throw 1;
   // throw 'a';
   }
   catch (long b) {
   cout << "пойман тип long: " << b << endl;
   }
   catch (char b) {
   cout << "пойман тип char: " << b << endl;
   }
 

"Создание" исключений
Когда возбуждается исключительная ситуация, программа просматривает стек функций до тех пор, пока не находит соответствующий catch. Если оператор catch не найден, STL будет обрабатывать исключение в стандартном обработчике, который делает все менее изящно, чем могли бы сделать вы, показывая какие-то непонятные (для конечного пользователя) сообщения и обычно аварийно завершая программу.Однако более важным моментом является то, что пока просматривается стек функций, вызываются деструкторы всех локальных классов, так что вам не нужно забодиться об освобождении памяти и т.п.Перегрузка глобальных операторов new/delete
А сейчас хотелось бы отправить вас к статье "Как обнаружить утечку памяти" . В ней рассказывается, как обнаружить неправильное управление распределением памяти в вашей программе. Вы можете спросить, при чем тут перегрузка операторов? Если перегрузить стандартные new и delete, то открываются широкие возможности по отслеживанию ошибок (причем ошибок часто критических) с помощью исключений. Например:
 char *a;
 try
 {
 a = new char[10];
 }
 catch (...)
 {
 // a не создан - обработать ошибку распределения памяти,
 // выйти из программы и т.п.
 }
 // a успешно создан, продолжаем выполнение
 

Это, на первый взгляд, кажется длиннее, чем стандартная проверка в С "а равен NULL?", однако если в программе выделяется десяток динамических переменных, то такой метод оправдывает себя.Операторы throw без параметров
Итак, мы увидели, как новый метод обработки ошибок удобен и прост. Блок try-catch может содержать вложенные блоки try-catch и если не будет определено соответствующего оператора catch на текущем уровен вложения, исключение будет поймано на более высоком уровне. Единственная вещь, о которой вы должны помнить, - это то, что операторы, следующие за throw, никогда не выполнятся.
 try
   {
   throw;
     // ни один оператор, следующий далее (до закрывающей скобки)
     // выполнен не будет
   }
   catch(...)
   {
   cout << "Исключение!" << endl;
   }
 

Такой метод может применяться в случаях, когда не нужно передавать никаких данных в блок catch.Приложение
Приведем пример, как все вышеизложенное может быть использовано в конкретном приложении. Преположим, у вас в программе есть класс cMain и экземпляр этого класса Main:
 class cMain
   {
   public:
   bool Setup();
   bool Loop(); // Основной цикл программы
   void Close();
   };
   cMain Main;
 

А в функции main() или WinMain() вы можете использовать этот класс как-нибудь так:
 try
   {
   Main.Setup();
   Main.Loop();
   Main.Close();
   }
   catch (Exception &e)
   {
   // использование класса, ведущего лог.
   log("Exception thrown: %s", e.String());
   // Показываем сообщение об ошибке и закрываем приложение.
   }
 

Основной цикл программы может выглядеть примерно так:
 while (AppActive)
   {
   try
   {
   // какие-то действия
   }
   catch (Exception &e)
   {
   /* Если исключение критическое, типа ошибки памяти,
   посылаем исключение дальше, в main(), оператором throw e;
   или просто throw.
   Если исключение некритично, обрабатываем его и
   возвращаемся в основной цикл. */
   }
   }
 

Исключения. Часть II



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

Специалисты по этим вопросам – откликнитесь!


Скачать пример

Заключение
Метод обработки исключений, приведенный в статье, является удобным и мощным средством, однако только вам решать, использовать его или нет. Одно можно скачать точно - приведенный метод облегчит вам жизнь. Если хотите узнать об исключениях чуть больше, посмотрите публикацию Deep C++ на сервере MSDN.

<< ВЕРНУТЬСЯ В ПОДРАЗДЕЛ

<< ВЕРНУТЬСЯ В ОГЛАВЛЕНИЕ




Материалы находятся на сайте https://exelab.ru/pro/



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS