Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы разработки программ на языке С++ для систем информационной безопасности. Учебное пособие

.pdf
Скачиваний:
1
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
17. Обработка ошибок и исключений
91
Таблица 65
Код с функцией, способной генерировать исключения
bool fError; int DataFromFunction; fError = MyFunctionA ( 100, DataFromFunction ); if( !fError ) { // ошибки не было, данные обрабатываются } else cout << “Error detected !”; MyFunctionB( 100, DataFromFunction ); // exception
В табл. 65 приведен программный код, содержащий вызов функции, способной выбросить исключение. Если это происходит, выполнение про­граммы завершается выводом сообщения, показанного на рис. 25. Исключе­ния в коде игнорируются.
Рис. 25. Окно сообщения об исключении
Обработка исключений строится на использовании пары операторов try и catch. Обработка исключений при использовании try и catch осуществ- ляется по следующему сценарию:
• последовательно выполняются строки кода в блоке try. Если исклю-
чений не возникло, после блока try исполняется код, размещенный за по­следним оператором catch;
• если возникло исключение в блоке try, дальнейшее исполнение
кода функции прерывается, и система исполнения программ стремится найти ловушку catch, соответствующую типу выброшенного исключения.
17. Обработка ошибок и исключений
92
Если ни одна ловушка не сработала, исключение “пробрасывается” в вы­звавшую функцию;
• выполняется программный код первой подходящей по типу исклю-
чения ловушки. Все ловушки просматриваются сверху вниз. После выпол­нения кода ловушки делается переход на программный код за последним оператором ловушки. Исключение считается обработанным, программа про­должается в обычном режиме.
Таблица 66
Генерация исключений разных типов
void MyFunctionC ( int InpData, int& OutData ) { bool bError = false; char sim = 0;
// обработка входных данных //…
bError = true; // здесь может быть ошибочная ситуация, т.е. bError = true; if( bError) throw 10; if (sim !=’a’) throw sim; }
В блок try обычно помещают программный код минимального раз-
мера, поскольку обработка исключений приводит к увеличению общего объ­ема программы. Это связано с тем, что компилятор дополняет ненадежный код специальным кодом для восстановления после прерывания.
Таблица 67
Ловушки для разнотипных исключений
try { MyFunctionC( 100, DataFromFunction ); //exception } catch(int) { // обработка целочисленного исключения, например DataFrom­Function = 1000;
17. Обработка ошибок и исключений
93
Окончание табл. 67
} catch(char) { // обработка исключения символьного типа, например DataFrom­Function = ‘a’ + ‘A’; }
В табл. 67 и 67 показан код, генерирующий исключения, и код, обра­батывающий исключения. В примере генерируются исключения двух ти­пов – int или char. Для каждого из них в вызывающей функции предусмот­рена ловушка. Заметим, что catch(…) – это вариант ловушки, которая ловит любые типы исключений.
Таблица 68
Обработка исключений стандартной библиотеки
try {
string str( “Micro” ); string rstr( “soft” );
str.append( rstr, 5, 3 ); // позиции 5 в строке rstr не существует } catch (out_of_range& e) { cout << “exception: ” << e.what() << endl; } catch (exception& e) {cout << “exception: “ << e.what() << endl; } catch (…) { a = -1; }
Стандартная библиотека С++ выбрасывает ряд исключений опреде­ленных классов, описанных в самой библиотеке. Их обработка должна быть сделана при вызове соответствующих функций. В табл. 68 показано, как об-
рабатывается исключение, генерируемое методом append класса string.
17. Обработка ошибок и исключений
94
18. ДИНАМИЧЕСКОЕ ВЫДЕЛЕНИЕ ПАМЯТИ
Задача управления памятью на программном уровне относится к ис­пользованию оперативной памяти. Создание переменных программы обяза­тельно связано с потреблением оперативной памяти, объем которой иногда может сильно ограничиваться характеристиками оборудования.
Для устройств и систем с ограниченным объемом оперативной па­мяти важно организовать динамическую процедуру выделения и освобож­дения памяти. Для статических переменных подобное управление невоз­можно. Как альтернативу, многие языки предлагают возможности динами­чески выделять и освобождать память.
Любая программа при загрузке для выполнения имеет определённое распределение областей памяти (табл. 69). Границы каждой области фик­сированы, любое перемещение границы является ошибкой, исключающей продолжение выполнения программы. Для хранения команд процессора выделяется область программного кода. Данная область расположена ря­дом со стеком.
Таблица 69
Распределение памяти программы
1
Куча
2
Память статистических переменных
3
Программный код
4
Стек
Область стека обычно используется для сохранения адресов и пара­метров вызываемых функций, а также для хранения промежуточных пере­менных внутри программных блоков. Использование стека напрямую про­граммисту не разрешается. Режим заполнения стека данными зависит от ло­гики работы компилятора.
Стек заполняется «снизу вверх» и при большом количестве вложен­ных вызовов может перейти границу программного кода. В этом случае фик­сируется аварийное завершение программы из-за переполнения стека.
В памяти статических переменных помещаются чаще всего глобаль­ные переменные фиксированного размера, а также переменные, объявлен­ные с ключевым словом static. Границы рассматриваемой области в про­цессе выполнения программы не изменяются.
18. Динамическое выделение памяти
95
Куча – область, в которой создаются динамические переменные, со-
здаваемые по ходу исполнения программы. В языках программирования могут существовать либо операторы динамического выделения памяти, либо функции.
Все запросы на выделение участков оперативной памяти направля­ются операционной системе. Выделенный блок памяти включает в себя за­головок, в котором указывается идентификатор запросившего память про­цесса, а также длина блока данных в байтах. Освободить этот блок может только тот процесс, который его получил.
Функция стандартной библиотеки Си
void* malloc(size_t size)
выделяет непрерывный участок памяти длинной size байт. Если такого участка выделить не удается, функция возвращает NULL. Получить отказ функция может, в частности, из-за фрагментированности памяти. Фрагмен­тированность – это наличие чередующихся занятых и свободных участков в памяти. Несмотря на наличие суммарного достаточного объема свободной памяти, запрос может операционной системой не удовлетворяться. Пдчерк­нем, что требуемый фрагмент должен быть непрерывным.
Другой вариант функции
void* calloc(size_t n, size_t size)
позволяет изменить размер выделенного блока памяти из n элементов задан­ного размера до size элементов.
Выделенную память можно привести к требуемому типу. Это не вли­яет на ее размер, но указывает компилятору, как генерировать код для обра­ботки содержимого этой памяти. Например,
char *S1; S1 = (char*) malloc (100) ; Функция void* realloc ( void* ptr, size_t size);
позволяет изменить размер уже выделенной памяти по указателю ptr. Новый размер памяти должен составлять size байт. Если память вызвать не удалось, то она возвратит NULL. Новое значение может быть как больше существу­ющего, так и меньше.
18. Динамическое выделение памяти
96
После работы с выделенной памятью ее всегда следует освобождать
вызовом функции
void free (void* ptr).
Параметр этой функции – указатель. Дальнейшее использование ука-
зателя на освобожденную память является опасным.
В табл. 70 показан программный код, использующий динамическое
выделение памяти для целочисленного массива.
Таблица 70
Использование динамически созданного массива
int main(void) {
int* ptr = (int*) calloc(10,sizeof (int) ) ; int s = 0; for(int i = 0; i<10 ; i++ ) scanf("%d", &ptr[ i ] ); for(int I = 0; i<10 ; i++ ) s+ = ptr[ i ]; printf(“ Summa=%d\n", s ); free( ptr ); return 0;
}
В С++ для выделения и освобождения памяти введены операторы new
и delete (табл.71).
Таблица 71
Операторы динамического выделения памяти С++
char *p; p = new char[1024];
// .......
delete p;
Оператор new возвращает указатель на экземпляр объекта, создан-
ного в памяти. Если память не выделена, выбрасываются исключения и нормальное продолжение программы становится невозможным. Любой указатель на динамически выделенную память должен далее использо­ваться в операторе delete.
18. Динамическое выделение памяти
97
Отсутствие вызовов функции или оператора освобождения памяти не считается ошибкой. Действительно, принятие решений о выделении и освобождении памяти является прерогативой программиста. В некоторых языках исполняющая система имеет механизм «сборки мусора», который собирает «ненужные» не освобожденные участки памяти в единое целое. Исполняющая система программ на Си и С++ такой функции не поддер­живает. Если в программе ошибочно не освобождаются области памяти, возникают “утечки” памяти, т.е. области не доступные для дальнейшей ра­боты. Значительные утечки памяти могут приводить к аварийному завер­шению программ.
18. Динамическое выделение памяти
98
19. ТЕСТИРОВАНИЕ ПРОГРАММНОГО КОДА
Тестирование – это процесс экспериментального оценивания качества
программного кода.
При тестировании решают следующие задачи:
1. стремятся обнаружить ошибки;
2. устанавливают соответствие функции программы и ее специфика-
цию;
3. оценивают надежность программы;
4. оценивают специфические показатели, такие как: уровень защиты,
нагрузочная способность, качество диалогового интерфейса и т.д.
Тестирование не может показать отсутствие ошибок. Тест – это информационная структура, которая описывает условия
для выполнения некоторого действия, входные данные, результат выполне­ния с указанием выходных данных и состояния программы (рис.26). Отдель­ные тестовые случаи составляют совокупность, которая считается полным тестом программного кода.
Тестовые случаи формулируются на основе содержательного анализа,
исходной спецификации программы и опыта разработки программ.
Рис. 26. Информационные структуры для проведения тестирования
Результаты тестирования заносятся в протокол тестирования (рис. 26). В табл.72 показан пример программы автоматизированного тестиро-
вания функции. Функция должна открывать заданный файл и добавлять к его содержимому строку символов. Программный код функции приведен ис­ключительно для иллюстрации. В реальности код тестерам не известен.
Структура теста
19. Тестирование программного кода
99
Таблица 72
Пример тестирования функции
// тестируемая функция bool AddInfToFile(char* sPath, char* sText) { fstream TextFile; TextFile.open(sPath,ios_base::app); TextFile << sText; TextFile.close(); return true; }
int _tmain(int argc, _TCHAR* argv[]) { bool Result;
// тест 1 Result = AddInfToFile(“d:\\temp\\info\\file1.txt”,”90901234567
10.05.2020 55”); // тест 2
Result = AddInfToFile(“d:\\temp\\info\\fфайл статистики.txt”,”90901234567 10.05.2020 55”);
// тест 3 Result = AddInfToFile(“ ”,”90901234567 10.05.2020 55”);
// тест 4 Result = AddInfToFile(“d:\\temp\\info\\file1.txt”,” ”);
Тест 1 должен завершиться добавлением указанной строки в указан­ный файл. После запуска теста в заданном каталоге в файле должна по­явиться добавленная строка. Проверить то, что тест завершается именно так, следует с помощью Проводника и Блокнота Windows. Можно заметить, что возвращаемое функцией значение не говорит о том, как завершилась опера­ция. Это можно отнести к недостаткам разработки, однако на этапе тестиро­вания подобные вопросы стараются не ставить. Слишком высока цена воз­врата на ранние этапы проектирования.
Тест 2 ставит своей целью проверку того, что корректно будет создан файл с кириллическим именем. Контроль результата выполняется с помо­щью Проводника Windows.
19. Тестирование программного кода
100
Тест 3 предназначен для проверки реакции функции на отсутствие имени файла: первый параметр вызова является пустой строкой. В зависи­мости от используемой библиотеки может быть сгенерировано исключение или возвращен код ошибки. Реакция на тест должна быть зафиксирована в протоколе.
Тест 4 заключается в записи пустой строки в конец файла. С помощью Проводника Windows сравнивают длину исходного файла и файла после за­пуска теста. Они не должны отличаться.
Любую программу потенциально можно протестировать полностью. Общее число тестов в этом случае совпадает с числом вариантов сочетаний входных, выходных данных и состояний программы (рис. 27). Даже для не­сложных программ это число имеет астрономический порядок, поэтому на практике полное тестирование любой реальной программы невозможно.
Рис. 27. Число тестов для полного тестирования программы
Выходом из положения является разбиение всего множества тестиру­емых объектов на классы эквивалентности (рис. 28). Под объектом тестиро­вания понимаются различные сущности. Например, тестироваться может ис­ходный программный код путем его анализа экспертами. Или тестируются требования исходной спецификации: программный продукт должен удовле­творять всем установленным в ней требованиям. В обоих случаях общей проблемой является размерность пространства тестирования. Большое число строк кода или спецификации создают опасность возникновения ошибки самой проверки. При этом либо правильные объекты будут при­знаны неправильными, либо неправильные – правильными.
В класс объединяются эквивалентные, по соображениям тестирую­щего, варианты данных, строки программного кода, разделы спецификации.
Дальнейшее тестирование проводится на отдельных экземплярах объ­ектов каждого класса. В зависимости от содержания выбирают небольшое число представителей и, получив положительный исход тестирования, счи-
вх
X
вх
Y
||||||
внутрвхвхтестов
SYXN ••=
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]