Лабораторная работа №4_No_Classes2014
.pdf
Основы создания программ в Си |
Page 31 of 38 |
|
|
|
|
float GetFiguresP(void** ppFigures, int count)
{// периметр float P = 0;
for(int i = 0; i < count; i++)
{
void* p = ppFigures[i]; // указатель на фигуру
// получаем тип фигуры
Figures kind = *((Figures*)p); switch(kind)
{
case FCyrcle: // круг
P += 2*((Cyrcle*)p)->Rad*3.14; break;
case FRectangle: // прямоугольник
P += 2*(((Rect*)p)->width+((Rect*)p)->len); break;
case FTriangle:
{// автоматическая переменная указателя на треугольник
Triangle* t = (Triangle*)p; //периметр треугольника
P+= t->side_a + t->side_b + t->side_c; break;
}
default:
printf("Error in Сalculation of Perimeter!!! \n"); return 0;
}
}
return P;
}
Использование функций расчета в функции main будет производиться при обработке соответствующих команд «'s' - Calculate Square for figures.» и «‘p' - Calculate Perimeter for figures».
...
switch (key)
{
case 'a': case 'A':
...
break;
Основы создания программ в Си |
Page 32 of 38 |
|
|
|
|
case 's': case 'S':
if(!realCount) // проверка наличия фигур для расчета
{
printf("No figures were putted for calculation!!!\n"); break;
}
float s = GetFuguresS(ppFigArray,realCount); printf("Squere of figures is: %f", s); break;
case 'p': case 'P':
if(!realCount) // проверка наличия фигур для расчета
{
printf("No figures were putted for calculation!!!\n"); break;
}
float p = GetFiguresP(ppFigArray,realCount); printf("Perimeter of figures is: %d", p); break;
...
Для выбранного индивидуального задания реализуйте методы обработки данных для расчета нужных значений в приложении. Результаты вычислений должны выводиться в функции wmain.
Использование указателя на функцию
Обобщая реализованные части приложения можно заключить следующее:
1.Приложение реализует меню ввода геометрических объектов разного типа (круг, прямоугольник, треугольник) на основе ввода символов с клавиатуры и оператора выбора switch;
2.Ссылки (адреса) динамически созданных объектов фигур хранятся в одном массиве;
3.Реализованы функции обработки данных имеющихся геометрических фигур, например, расчет общей площади фигур и расчет общего периметра фигур.
Операции обработки (функции) геометрических фигур явно имеют одинаковые сигнатуры – на вход они принимают адрес первого элемента массива типа void*, т.е. void**, и фактичееское количество элементов в этом массиве. Типом возвращаемого значения этих функций является float. Прототип этих функций имеет обобщенно следующий вид:
float someOperation (void**, int);
Основы создания программ в Си |
Page 33 of 38 |
Соответственно различия будут только в имени функции. В таком случае возможно применение указателя на функции обработки массива геометрических фигур. Например, можно объявить указатель на функцию данного типа, инициализировать в операторе выбора и вызвать функцию на выполнение.
Для удобства объявления указателя на функцию, целесообразно использовать ключевое слово typedef, которое позволит создать тип данных указателя на функцию:
// объявление метода обратного вызова - указателя на метод typedef float (*callbackMethod)(void**, int );
Представленное объявление определяет тип указателя на функцию “callbackMethod”. Функция на которую будет ссылаться данный указатель должна возвращать значение типа float и принимать 2 аргумента соответственно типов void** и int.
Таким образом, можно модифицировать содержание функции main:
bool executeCalc = false; // флаг необходимости выполнения функции // объявление указателя на функцию
callbackMethod method = NULL; switch (key)
{
case 'a':
...
case 's': case 'S':
if(!realCount)
{
printf("No figures were putted for calculation!!!\n"); break;
}
method = GetFuguresS; printf("Squere of figures is: "); break;
case 'p': case 'P':
if(!realCount)
{
printf("No figures were putted for calculation!!!\n"); break;
}
method = GetFiguresP; printf("Perimeter of figures is: "); break;
Основы создания программ в Си |
Page 34 of 38 |
|
|
|
|
default:
printf("Bad command. Try again or Quit.\n");
}
// проверка инициализации указателя на функцию if(method != NULL)
{// выполнение выбранного метода для фигур float value = method(ppFigArray,realCount);
printf("%10.2f\n", value);
}
Как представлено в операторе выбора swithc инициализируется указатель на соответствующую функцию и выводится нужный текст. Далее при условии, что указатель на функцию проинициализирован, выполняется вызов метода расчета.
Для выбранного индивидуального задания реализуйте прототип метода обратного вызова и используйте для уканаия на нужные функции обработки данных заполнения массива динамических объектов.
Наблюдение за утечками памяти в инструментарии VisualStudio 2005
Как возможно предположить разработанное приложение будет иметь утечки памяти, в случае если указатели в массиве ppFigArray будут перезаписываться, без удаления соответствующих объектов. Таким образом, объекты в памяти приложения будут оставаться, занимать нужное пространство даже если приложение уже не имеет ни одного указателя на эти объекты и никогда его уже не будет использовать.
Инструментарий среды разработки VisualStudio 2005 предоставляет возможность показать такие объекты в памяти приложения, которые остаются не уничтоженными после завершения работы приложения. Т.е. фактические утечки памяти. Кроме этого, с использованием специальных отладочных библиотек имеется возможность определить момент создания такого объекта , который в последствии будет «потерян» (своевременно не уничтожен) приложением.
Для активации режима отслеживания утечек памяти необходимо выполнить следующие действия:
1.Подключить нужные заголовочные файлы библиотек окружения разработки (stdlib.h и crtdbg.h).
2.Вызвать функцию сбора данных об утечках памяти _CrtDumpMemoryLeaks в конце выполнения функции wmain.
Реализация этих изменений в коде будет выглядеть следующим образом:
В файле подключения заголовочных файлов stdafx.h:
Основы создания программ в Си |
Page 35 of 38 |
|
|
|
|
#include <stdio.h> // /* printf, scanf, NULL */ #include <conio.h>
#include <tchar.h>
#include <stdlib.h> // /* malloc, free, rand */ #include <math.h>
#include <crtdbg.h> // определение утечки памяти
В завершении метода wmain вызываем производим вызов функции _CrtDumpMemoryLeaks:
_CrtDumpMemoryLeaks(); // собираем утечки памяти return 0;
}
Теперь если в приложении произвести ввод фигур повторно, т.е. при наличии фигур в массиве вызвать команду «'a' - Fill figures array» и ввести фигуры заново, то ссылки на первоначально расположенные в массиве фигуры будут «потеряны» приложением. Объекты будут занимать память процесса но будут не доступны. При завершении работы приложения будет вызвана функция _CrtDumpMemoryLeaks, которая выведет информацию о наличии таких потерянных объектов в окно Вывод (Output). Точнее сказать будет выведена информация о наличии блоков памяти определенного размера, которые были выделены приложением но не были очищены.
Рис. 4-29 Вывод информации об утечках памяти в инструментальном окружении
VisualStudio 2005
При детальном рассмотрении информации об утечках памяти можно выявить информацию необходимую для определения времени и места создания объекта, который в результате не был уничтожен корректно. Например, следующая информация об утечках показывает:
|
Основы создания программ в Си |
Page 36 of 38 |
||
|
|
|
|
|
|
Dumping objects -> |
|
|
|
|
m:\tmp\lab4_test\lab4_test\lab4_test.cpp(135) : {104} normal block at |
|
||
|
0x00369DE8, 16 bytes long. |
|
||
|
Data: < |
> 01 00 00 00 14 00 00 00 CD CD CD CD CD CD CD CD |
|
|
|
m:\tmp\lab4_test\lab4_test\lab4_test.cpp(167) : {103} normal block at |
|
||
|
0x00369D98, 16 bytes long. |
|
||
|
Data: < |
> 03 00 00 00 05 00 00 00 08 00 00 00 06 00 00 00 |
|
|
|
... |
|
|
|
|
|
|
|
|
1, Имя файла в котором произошла ошибка работы с динамически распределяемой памятью
(m:\tmp\lab4_test\lab4_test\lab4_test.cpp);
2.Номер строчки в которой произошло распределение памяти (135) – эта информация доступна не во всех случаях.
3.Номер операции по распределению памяти (индекс – {104}), который возможно использовать впоследствии для установки специальной точки останова и выявления момента создания объекта (выделения памяти под объект). Индексы указываются в обратном хронологическом порядке.
4.Адрес по которому располагается объект – фактически это значение указателя на объект который был потерян.
5.Размер распределенной области памяти (16 bytes long)
6.Символьное представление и значения первых байтов в обозначенной области памяти (Data: <
>03 00 00 00 05 00 00 00 08 00 00 00 06 00 00 00).
Таким образом, можно определять утечки памяти в приложении.
Для выбранного индивидуального задания реализуйте контроль за утечками памяти.
Уничтожение динамически созданных объектов
Для корректной очистки памяти достаточно создать метод - RemoveFigures, который будет освобождать память, выделенную для объектов геометрических фигур. В реализации метода также будет необходимо определять тип объекта, на который ссылается указатель, так же как это сделано в функциях расчета. Метод очистки будет принимать в качестве параметров указатель на массив указателей объектов void** и фактическое количество объектов, которые нужно удалить из памяти.
// метод для очистки памяти фигур
void RemoveFigures(void** ppFigs, int count)
{
int counter = 0; // индекс текущего объекта
void* p = ppFigs[counter]; // указатель на текущий объект
Основы создания программ в Си |
Page 37 of 38 |
|
|
|
|
while(p != NULL)
{
// получаем тип фигуры
Figures kind = *((Figures*)p); switch(kind)
{
case FCyrcle:
free((Cyrcle*)p); // очистка выделенной памяти printf("Cyrcle was removed. \n");
break;
case FRectangle: free( (Rect*)p);
printf("Rect was removed. \n"); break;
case FTriangle:
free( (Triangle*)p); printf("Triangle was removed. \n"); break;
default:
printf("Error in RemoveFigures!!! \n"); return;
}
ppFigs[counter] = NULL; // обнуление указателя в массиве counter++;
if(counter == count) return;
p = ppFigs[counter];
}
}
Вызов метода необходимо разместить в конце функции main и в начале функции AddFigures.
В функции main вызов будет выглядеть следующим образом:
// очистка памяти
RemoveFigures(ppFigArray, realCount);
_CrtDumpMemoryLeaks(); // собираем утечки памяти return 0;
}
Основы создания программ в Си |
Page 38 of 38 |
В методе AddFigures очистка массива фигур будет производиться при условии, что первый указатель в массив0е не равен NULL:
int AddFigures(void** ppFigs)
{
if(ppFigs[0] != NULL) // проверка что первый элемент массива не нулевой
RemoveFigures(ppFigs, 4);
int counter = 0; bool quit = false;
...
Для выбранного индивидуального задания реализуйте функцию для корректного удаления динамически созданных объектов. Используйте функцию в методе заполнения массива динамических объектов.
