
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №1 - Введение. Данные в памяти программ
- •Введение
- •Модель памяти в прикладных программах
- •Int main ()
- •Int main ()
- •Int main ()
- •Int main ()
- •Char buf[ 2000000 ];
- •Return f(); // бесконечно долго вызываем сами себя // вызываем переполнение стека через время
- •Int main ()
- •Return f();
- •Int main ()
- •Delete[] p; // Освобождаем память
- •Int main ()
- •Int a[ n ]; // ошибка, размер нельзя вычислить во время компиляции
- •Delete[] p;
- •Int main ()
- •Проблема фиксированного размера массивов
- •Int main ()
- •Int main ()
- •Int main ()
- •Динамически растущие массивы (векторы)
- •Int main ()
- •Struct IntegerVector
- •Int * m_pData;
- •Int m_nUsed;
- •Int m_nAllocated;
- •Void IntegerVectorDestroy ( IntegerVector & _vector )
- •Int main ()
- •#Ifndef _integer_vector_hpp_
- •#Include "integer_vector.Hpp"
- •Void IntegerVectorRead ( IntegerVector & _vector, std::istream & _stream );
- •Ста: Лекция №2 - Связные списки.
- •Всегда ли хорош вектор?
- •Связные списки
- •Ста: Лекция №3 - Реализация и использование простейших атд
- •Абстрактные типы данных (атд)
- •Атд “Список” ( “Последовательность” )
- •Атд “Стек”
- •Атд “Очередь”
- •Ста: Лекция №7 - Деревья
- •Основные сведения о деревьях
- •Обход деревьев
- •Атд “Дерево”
- •Типичные структуры данных для n-арных деревьев
- •1. Массив меток и массив родительских индексов.
- •2. Массив меток и заголовок со списками дочерних узлов.
- •3. Динамическая структура с указателями
- •В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:
- •Бинарные деревья
- •Глава 3 “Элементарные структуры данных”
- •Глава 4 “Абстрактные типы данных”
- •Глава 10 “Элементарные структуры данных” (подразделы 10.1-10.3)
- •Глава 2 “Основные абстрактные типы данных” (подразделы 2.1-2.4)
- •Глава 6 “Элементарные методы сортировки”.
- •Глава 5 “Рекурсия и деревья”.
Int main ()
{
int n;
std::cin >> n; // вводим размер массива с клавиатуры
Int a[ n ]; // ошибка, размер нельзя вычислить во время компиляции
int * p = new int[ n ]; // ок, используется значение во время выполнения
Delete[] p;
}
Каждое динамическое выделение, помимо запрашиваемого пользовательского объема, расходует некоторый дополнительный объем служебных данных, размер которого сильно варьируется в зависимости от среды разработки, ее версии и конфигурации сборки. Например, Visual Studio 2010 в отладочной конфигурации дополнительно к запрашиваемой памяти выделяет еще 36 байт для внутренних нужд отладчика. Соответственно, при слишком малых объемах выделения удельный вес служебной памяти может превышать удельный вес полезной памяти.
Случайное повреждение служебной части динамически выделенной памяти также может привести к фатальному завершению программы, при этом обнаружить момент повреждения очень сложно, поскольку проблема обычно проявляется в момент освобождения памяти, что значительно позже момента повреждения.
При написании программ с повышенными требованиями к производительности к динамическому выделению памяти следует подходить очень внимательно. Обычно динамическое выделение памяти работает эффективнее для крупных блоков данных по сравнению с мелкими. Кроме того, выделение и освобождение памяти требует немалой порции времени, что может быть очень заметным в крупных программах. Временная доля для выделения+освобождения динамической памяти часто превышает 30% от общего времени выполнения.
Если память, которая более не используется программой, не освобождать, возникает явление утечки памяти (memory leak). Выделенный блок помечен распределителем динамической памяти как используемый, однако в программе не содержится ни одной переменной-указателя, хранящей его адрес, что делает его недоступным. В результате, блок никак не используется до завершения выполнения программы. Он не освобождается ни для других программ, ни для других блоков внутри данной программы.
Int main ()
{
int * p = new int[ 10000000 ]; // Выделить выделили, а освобождать кто будет?
...
}
Для маленьких программ это явление возможно не имеет огромного значения, однако для больших программ, особенно для работающих в режиме 24x7, таких как веб-серверы, наличие значительного объема утечек памяти рано или поздно приводит к ее исчерпанию, а значит к последующему фатальному завершению.
Обнаружение утечек памяти является весьма нетривиальной задачей, требующей специальных инструментов для трассировки выделения и освобождения. Наилучшими методами предотвращения утечек памяти является аккуратность разработчика, а также применение “умных” указателей (std::unique_ptr, std::shared_ptr). Начинающих программистов, недостаточно аккуратно следящих за освобождением памяти, часто сравнивают с людьми, которые не моют за собой посуду после приема пищи - рано или поздно чистые тарелки заканчиваются.
При интенсивной работе с кучей, имеет место также более сложное явление фрагментации, когда распределитель памяти не может выделить цельный блок стоящий подряд, хотя имеется достаточное общее свободное пространство в виде нескольких меньших блоков.
В программах, сталкивающихся с такими сложностями, обычно реализовывают полностью собственную ручную логику распределения динамической памяти. Память выделяется крупными блоками, а алгоритм дальнейшего распределения оптимизируется под нужды конкретной задачи.