Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Основы разработки программ на языке С++ для систем информационной безопасности. Учебное пособие
.pdf
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)
{
// обработка целочисленного исключения, например DataFromFunction = 1000;

17. Обработка ошибок и исключений
93
Окончание табл. 67
}
catch(char)
{
// обработка исключения символьного типа, например DataFromFunction = ‘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 ••=
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
