
- •Структуры и алгоритмы обработки данных: план курса
- •Лабораторные работы
- •Литература
- •Ста: Лекция №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 a = 5; // Точка 1
f( 3 );
// Точка 3
}
В отличие от сегмента кода и сегмента данных, точный необходимый размер для стека вычислить во время компиляции не представляется возможным, т.к. функции могут вызывать друг друга с очень большим и непредсказуемым уровнем вложенности. Соответственно, выделяется некоторый конечный достаточно большой для общего назначения размер. Этот размер зависит от используемого компилятора и может быть настроен в случае необходимости. Программы в среде Visual Studio по умолчанию получают стек размером 1 мегабайт. Достичь данного предела не так уж сложно, достаточно объявить внутри функции слишком большой по размеру массив:
Int main ()
{
Char buf[ 2000000 ];
buf[ 0 ] = 'a';
}
Запуск такой программы приведет к ошибке переполнения стека:
Из этого вытекает, что стек не предназначен для хранения больших объемов данных, а должен использоваться лишь для промежуточных переменных и аргументов функций.
Переполнение стека также может случиться из-за слишком глубокого уровня вложенности вызовов функций. Это легко продемонстрировать на приведенной ниже некорректной рекурсивной функции. Хотя функция и не содержит ни одного локальной переменной и ни одного аргумента, объем сохраняемой служебной информации через некоторое время превысит ограничение на размер стека:
int f ()
{
Return f(); // бесконечно долго вызываем сами себя // вызываем переполнение стека через время
}
Int main ()
{
Return f();
}
Неаккуратные манипуляции с адресами памяти в области стека могут не только непроизвольно затереть значения соседствующих переменных, но и задеть служебную информацию, размещаемую в стеке. Ошибиться очень легко, достаточно при обращении к массиву выйти за допустимую границу индексов. При неудачном попадании случайно модифицируемых ячеек представляется возможным затереть адрес инструкции для возврата из функции, что полностью разрушит стек и фатально завершит программу с трудно уловимым местом ошибки.
Вся остальная наибольшая область памяти называется кучей (heap), или динамической памятью, и распределяется программистом вручную при помощи операторов new. В отличие от других областей памяти, освобождение блоков в динамической памяти также нужно делать вручную, используя операторы delete. Например, следующая программа выделяет блок памяти из 10 миллионов целых чисел, а затем освобождает его:
Int main ()
{
int * p = new int[ 10000000 ]; // Выделяем память
Delete[] p; // Освобождаем память
}
Если при помощи new выделялся массив, то освобождать его нужно при помощи оператора delete[]. Если выделялся одиночный объект - его освобождают через оператор delete. В ряде сред разработки нарушение данного правила приводит к фатальному завершению программы (к сожалению, Visual Studio о такой ошибке никак не сигнализирует).
Наиболее важное преимущество динамической памяти - возможность определения объема выделения во время выполнения программы. Выделяя массивы в глобальной области либо на стеке необходимо указывать их конкретные исчисляемые во время компиляции размеры. При выделении же массивов в динамической памяти, их размер может определяться значением любого выражения с переменными, зависящего от текущего состояния программы, внешних данных от пользователя и других факторов.
#include <iostream>