Как очистить динамическую память паскаль
Вся динамическая память в Турбо Паскале рассматривается как сплошной массив байтов, который называется кучей. Физически куча располагается в старших адресах сразу за областью памяти, которую занимает тело программы.
Начало кучи хранится в стандартной переменной HEAPORG (рис. 6.3), конец - в временной HEAPEND. Текущую границу незанятой динамической памяти указывает указатель HEAPPTR.
Память под любую динамически размещаемую переменную выделяется процедурой NEW. Параметром обращения к этой процедуре является типизированный указатель. В результате обращения указатель приобретает значение, соответствующее динамическому адресу, начиная с которого можно разместить данные, например:
После выполнения этого фрагмента указатель / приобретет значение, которое перед этим имел указатель кучи HEAPPTR, а сам HEAPPTR увеличит свое значение на 2, так как длина внутреннего представления типа INTEGER, с которым связан указатель I, составляет 2 байта (на самом деле это не совсем так: память под любую переменную выделяется порциями, кратными 8 байтам - см. п.6.7). Оператор
вызовет еще раз смещение указателя HEAPPTR, но теперь уже на 6 байт, потому что такова длина внутреннего представления типа REAL. Аналогичным образом выделяется память и для переменной любого другого типа.
Рис.6.3. Расположение кучи в памяти ПК
После того как указатель приобрел некоторое значение, т.е. стал указывать на конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа. Для этого сразу за указателем без каких-либо пробелов ставится значок ^, например:
Таким образом, значение, на которое указывает указатель, т.е. собственно данные, размещенные в куче, обозначаются значком ^, который ставится сразу за указателем. Если за указателем нет значка ^, то имеется в виду адрес, по которому размещены данные. Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя является адрес, а чтобы указать, что речь идет не об адресе, а о тех данных, которые размещены по этому адресу, за указателем ставится л. Если Вы четко уясните себе это, у Вас не будет проблем при работе с динамической памятью.
Динамически размещенные данные можно использовать в любом месте программы, где это допустимо для констант и переменных соответствующего типа, например:
Разумеется, совершенно недопустим оператор
так как указателю R нельзя присвоить значение вещественного выражения. Точно так же недопустим оператор
поскольку значением указателя R является адрес, и его (в отличие от того значения, которое размещено по этому адресу) нельзя возводить в квадрат. Ошибочным будет и такое присваивание:
так как вещественным данным, на которые указывает R, нельзя присвоить значение указателя (адрес).
Динамическую память можно не только забирать из кучи, но и возвращать обратно. Для этого используется процедура DISPOSE. Например, операторы
вернут в кучу 8 байт, которые ранее были выделены указателям I и R (см. выше).
Отметим, что процедура DISPOSE(PTR) не изменяет значения указателя PTR, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако повторное применение процедуры к свободному указателю приведет к возникновению ошибки периода исполнения. Освободившийся указатель программист может пометить зарезервированным словом NIL. Помечен ли какой-либо указатель или нет, можно проверить следующим образом:
Никакие другие операции сравнения над указателями не разрешены.
Приведенный выше фрагмент иллюстрирует предпочтительный способ объявления указателя в виде типизированной константы (см. гл. 7) с одновременным присвоением ему значения NIL. Следует учесть, что начальное значение указателя (при его объявлении в разделе переменных) может быть произвольным. Использование указателей, которым не присвоено значение процедурой NEW или другим способом, не контролируется системой и может привести к непредсказуемым результатам.
Чередование обращений к процедурам NEW и DISPOSE обычно приводит к «ячеистой» структуре памяти. Дело в том, что все операции с кучей выполняются под управлением особой подпрограммы, которая называется администратором кучи. Она автоматически пристыковывается к Вашей программе компоновщиком Турбо Паскаля и ведет учет всех свободных фрагментов в куче. При очередном обращении к процедуре NEW эта подпрограмма отыскивает наименьший свободный фрагмент, в котором еще может разместиться требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины помечается как занятая часть кучи. (Подробнее о работе администратора кучи см. п.6.7).
Другая возможность состоит в освобождении целого фрагмента кучи. С этой целью перед началом выделения динамической памяти текущее значение указателя HEAPPTR запоминается в переменной-указателе с помощью процедуры MARK. Теперь можно в любой момент освободить фрагмент кучи, начиная от того адреса, который запомнила процедура MARK, и до конца динамической памяти. Для этого используется процедура RELEASE. Например:
В этом примере процедурой MARK(P) в указатель Р было помещено текущее значение HEAPPTR, однако память под переменную не резервировалась. Обращение RELEASE(P) освободило динамическую память от помеченного места до конца кучи. Рис.6.4 иллюстрирует механизм работы процедур NEW-DISPOSE и NEW-MARK-RELEASE для рассмотренного примера и для случая, когда вместо оператора RELEASE(P) используется, например, DISPOSE(P3).
Следует учесть, что вызов RELEASE уничтожает список свободных фрагментов в куче, созданных до этого процедурой DISPOSE, поэтому совместное использование обоих механизмов освобождения памяти в рамках одной программы не рекомендуется.
Как уже отмечалось, параметром процедуры NEW может быть только типизированный указатель. Для работы с нетипизированными указателями используются процедуры:
GETMEM (P, SIZE) - резервирование памяти;
FREEMEM(P, SIZE) - освобождение памяти.
Здесь Р - нетипизированный указатель;
SIZE - размер в байтах требуемой или освобождаемой части кучи.
Puc.6.4. Состояние динамической памяти: а) перед освобождением; б) после Dispose(p3); в) после Release(p)
За одно обращение к куче процедурой GETMEM можно зарезервировать до 65521 байта динамической памяти.
Использование процедур GETMEM-FREEMEM, как и вообще вся работа с динамической памятью, требует особой осторожности и тщательного соблюдения простого правила: освобождать нужно ровно столько памяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована.
Нетрудно обнаружить, что наличие нетипизированных указателей в Турбо Паскале I в стандартном Паскале их нет) открывает широкие возможности неявного преобразования типов. К сожалению, трудно обнаруживаемые ошибки в программе, связанные с некорректно используемыми обращениями к процедурам NEW и DISPOSE, также могут привести к нежелательному преобразованию типов. В самом деле, пусть имеется программа:
Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, проследим за значениями указателя HEAPPTR. Перед исполнением программы этот указатель имел значение адреса начала кучи HEAPORG, которое и было передано указателю I, азатем и J. После выполнения DISPOSE(I) указатель кучи вновь приобрел значение HEAPORG, этот адрес передан указателю R в процедуре NEW(R). После того как по адресу R разместилось вещественное число pi=3.14159, первые 2 байта кучи оказались заняты под часть внутреннего представления этого числа. В то же время J все еще сохраняет адрес HEAPORG, поэтому оператор WRITELN(J^) будет рассматривать 2 байта числа pi как внутреннее представление целого числа (ведь J - это указатель на тип INTEGER) и выведет 8578.
О, гуру, помогите мне, несведущему! Не получается полностью освободить динамическую память. Гугль курил.
Предполагается освобождать память процедурой dispmem:
Так не получается. Сравниваю memavail в начале и в конце программы. Ещё пробовал так:Тоже не помогло. Собственно вопрос! Как все таки освободить память?! И почему у меня не работает, делал все по букварю..
Заранее благодарен. __________________
Помощь в написании контрольных, курсовых и дипломных работ здесь
Очистка динамической памяти
Как очистить память в таком случае? Выдаёт ошибку 204. Ошибочная операция с указателем. type.
Не могу вывести элементы из динамической памяти
написал прогу заполняет список 16ю элементами (динамическая память)потом должна их вывести и.
Разместить элементы файла в динамической памяти
Создать файл, содержащий сведения о владельцах автомобилей: фамилия владельца, марка автомобиля и.
Запушить в ячейку динамической памяти через модуль
Не получается запушить в ячейку динамической памяти через модуль, постоянно находит ошибки. Вот и.
Найти среднее арифметическое четных строк с использованием динамической памяти
Дан двумерный целочисленный массив, состоящий из случайных чисел. Найти среднее арифметическое.
Разместить элементы файла в динамической памяти в односвязном линейном списке
Задача "Разместить элементы файла в динамической памяти в односвязном линейном списке. Из связного.
Разместить в динамической памяти элементы, встречающиеся в текстовом файле один раз
Дан текстовый файл, содержащий целые числа, разделенные пробелом. Разместить в динамической памяти.
Создать в динамической памяти односвязный список типа «очередь» из случайных целых чисел
1. Создать в динамической памяти односвязный список типа «очередь» из случайных целых чисел .
В приведенном выше примере процедурой MARK(P) в указатель Р было помещено текущее значение HEAPPTR, однако память под переменную не резервировалась. Обращение RELEASE(P) освободило динамическую память от помеченного места до конца кучи. Рис. 6.4 иллюстрирует механизм работы процедур NEW-DISPOSE и NEW-MARK-RELEASE для рассмотренного примера и для случая, когда вместо оператора RELEASE(P) используется, например, DISPOSE(P3).
Следует учесть, что вызов RELEASE уничтожает список свободных фрагментов в куче, созданных до этого процедурой DISPOSE, поэтому совместное использование обоих механизмов освобождения памяти в рамках одной программы не рекомендуется.
Как уже отмечалось, параметром процедуры NEW может быть только типизированный указатель. Для работы с нетипизированными указателями используются процедуры:
- Р – нетипизированный указатель;
- SIZE – размер в байтах требуемой или освобождаемой части кучи.
Рис. 6.4. Состояние динамической памяти: а) перед освобождением; б) после Dispose(p3); в) после Release(p)
За одно обращение к куче процедурой GETMEM можно зарезервировать до 65521 байта динамической памяти.
Использование процедур GETMEM-FREEMEM, как и вообще вся работа с динамической памятью, требует особой осторожности и тщательного соблюдения простого правила: освобождать нужно ровно столько памяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована.
Нетрудно обнаружить, что наличие нетипизированных указателей в Турбо Паскале (в стандартном Паскале их нет) открывает широкие возможности неявного преобразования типов. К сожалению, трудно обнаруживаемые ошибки в программе, связанные с некорректно используемыми обращениями к процедурам NEW и DISPOSE, также могут привести к нежелательному преобразованию типов. В самом деле, пусть имеется программа:
Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, проследим за значениями указателя HEAPPTR. Перед исполнением программы этот указатель имел значение адреса начала кучи HEAPORG, которое и было передано указателю I, азатем и J. После выполнения DISPOSE(I) указатель кучи вновь приобрел значение HEAPORG, этот адрес передан указателю R в процедуре NEW(R). После того как по адресу R разместилось вещественное число pi=3.14159, первые 2 байта кучи оказались заняты под часть внутреннего представления этого числа. В то же время J все еще сохраняет адрес HEAPORG, поэтому оператор WRITELN(J^) будет рассматривать 2 байта числа pi как внутреннее представление целого числа (ведь J – это указатель на тип INTEGER) и выведет 8578.
При выполнении программы каждая используемая в ней переменная получает свой адрес в оперативной памяти. Программисту не нужно заботиться о механизме распределения адресов, это делается автоматически. Машина сама ищет свободное место в памяти и выделяет в ней место для переменных. В Турбо-Паскале есть два способа распределения памяти: статический и динамический. При статическом распределении всем объявленным в программе переменным в сегменте данных выделяются фиксированные участки оперативной памяти. В связи с этим использование заранее не объявленных переменных не допускается. При динамическом распределении памяти имеется возможность создавать новые, не объявленные заранее, переменные и размещать их на свободные участки в динамической области оперативной памяти. Динамическая переменная не указывается явно в описаниях переменных и к ней нельзя обратиться по имени. Это достигается за счет использования указателей.
Указатель – это элемент данных, представляющий собой ссылку на определенную ячейку оперативной памяти (т.е. адрес ячейки), начиная с которой записывается значение переменной. Переменные, которые размещаются в оперативной памяти динамическим способом с помощью указателей, называются динамическими переменными.
Указатель может принимать значения, равные всем адресам оперативной памяти, по которым возможна запись данных. Указатель может иметь стандартное значение Nil (пусто), которая говорит, что соответствующая переменная в динамической памяти отсутствует (в указателе не содержится никакого адреса).
Описание указателей
Указатель объявляется с помощью символа каре (^), за которым записывается идентификатор типа динамической переменной:
1)
2)
В этом случае переменные A, B, C являются указателями на переменные типа Integer. Для обращения к значениям этих переменных служат идентификаторы A^, B^, C^, т.е. имя переменной и знак каре.
Кроме того, указатель может быть объявлен явно следующим образом:
3)
Над указателями не определено никаких операций, кроме проверки на равенство и неравенство.
Для динамических переменных (A^, B^, C^) допустимы все те же операции, что и над обычными переменными данного типа.
Переменные типа указатель не могут быть элементами списка ввода-вывода.
- Стандартные процедуры и функции для работы с указателями
Работа с динамической памятью осуществляется с помощью процедур и функций. Процедуры:
- Любым действиям с динамической переменной должна предшествовать процедура ее размещения в ОЗУ. Эта процедура имеет вид: New (Varp:Pointer). Она создает новую динамическую переменную, присваивая указателю p значение ее адреса в ОЗУ. При этом динамической переменной отводится блок памяти, соответствующий размеру типа, с которым объявлен указатель p.
- Когда в ходе вычислительного процесса переменная становится ненужной, ее следует удалить. Это осуществляется процедурой Dispose (Varp:Pointer). Данная процедура освобождает память, занятую динамической переменной, делая значение ее указателя неопределенным.
Все изложенное пока не дает ответа на вопрос: «А зачем это нужно?» Отвечаем. Динамические переменные используются в основном в двух ситуациях:
- для работы с массивами больших размеров;
- для работы с особыми структурами переменных размеров, которые получили название динамических структур данных.
Массив указателей (см. Алексеев стр. 227-232)
В чем же преимущество динамического выделения памяти, если приходится выполнять так много дополнительных действий? Дело в том, что транслятор ограничивает суммарный объем статической памяти одним сегментом, т.е. 64 К. Объем динамической памяти ограничен лишь физическим ресурсом компьютера, а это гораздо больше.
Предположим, мы хотим запомнить как можно больше длинных строк символов. Размер строки – 256 байт, поэтому их предельное количество в статической памяти равно 64*1024/256=256.
Для хранения большего числа строк разместим в статической памяти лишь указатели на них, а сами строки перенесем в динамическую память. Заметим, что размер одного указателя – 4байта.
Указатели чаще всего используются для работы с динамическими массивами памяти, которые представляют собой массивы переменной длины, память под которые может выделяться и изменяться в процессе выполнения программы, как при каждом новом запуске программы, так и в разных ее частях. Обращение к i-му элементу динамического массива x имеет вид x^[i].
При работе с динамическими переменными необходимо соблюдать следующий порядок работы:
- описать указатели;
- распределить память;
- обработать динамический массив;
- освободить память.
Понятие динамического массива можно распространить и на матрицы. Динамическая матрица представляет собой массив указателей, каждый из которых адресует одну строку (или столбец). Рассмотрим описание динамической матрицы. Пусть есть типы данных massiv и указатель на него din_massiv:
Динамическая матрица X будет представлять собой массив указателей:
Работать с матрицей необходимо следующим образом:
- определить ее размеры (пусть N – число строк, M – число столбцов);
- выделить память под матрицу:
Каждый элемент статического массива X[i] – указатель на динамический массив, состоящий из M элементов типа real. В статическом массиве X находится N указателей.
- для обращения к элементу динамической матрицы, расположенному в i-той строке и j-м столбце, следует использовать следующую конструкцию: X[i]^[j];
- после завершения работы с матрицей необходимо освободить память:
При хранении большого количества однотипных данных можно использовать динамические массивы, но у них есть существенные недостатки:
- размер динамического массива постоянен;
- невозможно добавление элементов в середину массива.
Если же количество данных непостоянно и изменяется в процессе выполнения программы, либо существует необходимость добавления элементов в середину набора данных, что особенно актуально при работе с упорядоченными наборами данных, то целесообразно использовать динамические структуры данных.
Динамические структуры данных
Структурированные типы данных, такие, как массивы, множества, записи, представляют собой статические структуры, т.к. их размеры неизменны в течение всего времени выполнения программы.
Часто требуется, чтобы структуры данных меняли свои размеры в ходе решения задачи. Такие структуры называются динамическими, к ним относятся стеки, очереди, списки, деревья и др. Описание динамических структур с помощью массивов, записей и файлов приводит к неэкономному использованию памяти ЭВМ и увеличивает время решения задач.
В поисках решения проблемы быстрой обработки больших объемов данных были предложены динамические структуры данных. Они характеризуются следующими особенностями:
- для отдельных элементов структуры память выделяется в тот момент, когда в них появляется необходимость (а не сразу и одним блоком как для массивов);
- число элементов динамической структуры заранее не объявляется и может изменяться от нуля до некоторого значения, определяемого спецификой соответствующей задачи или доступным объемом памяти;
- память, занимаемая структурой, не представляет собой непрерывную область, т. е. элементы могут быть разбросаны в памяти хаотическим образом;
- логическая последовательность элементов задается в явном виде с помощью одного или нескольких указателей, хранящихся в самих элементах. Как правило, каждый элемент, кроме своего значения, хранит указатель на следующий элемент или на два соседних с ним элемента.
Для того чтобы идея стала совсем понятной, проведем такую аналогию. При записи на прием к врачу каждый пациент получает свой порядковый номер в списке пациентов, и ему сообщается время приема. Если очередь к врачу движется строго по номерам, то никто не обязан запоминать своих соседей по очереди.
Другое дело — живая очередь (в магазине, железнодорожной кассе). Количество человек в очереди постоянно меняется, люди приходят и уходят, но никакой неразберихи не происходит — каждый знает, за кем он стоит. Образуется связанная цепочка людей. Очевидно, кому-то из стоящих в очереди и пришла идея создания динамических структур данных.
Простая по своей сути идея оказалась не столь простой в реализации, поэтому дальнейший материал потребует повышенного внимания. Рассмотрим наиболее распространенную динамическую структуру, которая называется связанным списком.
Связанный список — это такая структура данных, элементами которой служат записи одного и того же формата, связанные друг с другом с помощью указателей, расположенных в самих элементах. Элементы списка часто называются его узлами.
Использование записей для представления элементов связанного списка вполне закономерно. Это единственный комбинированный тип данных, который допускает группирование данных различных типов. Каждый элемент списка состоит из двух различных по значению частей: содержательной (информационной) и указующей (см. рис. ниже). В минимальном варианте содержательная и указующая части занимают по одному полю записи, но могут представлять собой и более одного поля.
В содержательной части хранятся данные, ради которых и создается список.
Если указующая часть хранит адрес одного соседнего элемента списка, то
такой список называется односвязным, или однонаправленным. Поле указателя последнего элемента содержит специальный признак Nil (признак конца списка). Для каждого элемента (кроме последнего) имеется единственный следующий элемент, поэтому связанные списки иногда называют линейными. Логическая структура односвязного списка представлена ниже.
В случае, когда в указующей части хранятся адреса и предыдущего, и следующего элементов, список называется двусвязным, или двунаправленным. Двунаправленный список более универсален, т. к. по такой цепочке можно двигаться в двух направлениях: прямом и обратном. В двусвязном списке можно продвигаться от элемента к элементу двумя противоположными путями, поэтому в конце каждого из них в поле соответствующего указателя находится признак пустого указателя (Nil).
Из односвязного и двусвязного списков можно получить кольцевой список, который вообще не содержит пустых указателей. Кстати, буфер клавиатуры ПК реализован именно как кольцевой список.
С линейными списками можно выполнять те же действия, что и с одномерными массивами, поскольку назначение списков и массивов одно и то же — обработка данных в оперативной памяти.
Перечислим типовые действия со списками:
- добавить новый узел непосредственно перед заданным узлом;
- удалить заданный узел;
- объединить два (или более) линейных списка в один список;
- разбить линейный список на два (или более) списка;
- сделать копию списка;
- выполнить сортировку узлов списка в возрастающем порядке по некоторым полям в узлах;
- найти в списке узел с заданным значением в некотором поле.
Очень часто встречаются линейные списки, в которых добавление и удаление производятся только в первом или последнем узлах. Назовем эти списки.
Прежде чем рассматривать действия со связанными списками, введем обозначения переменных, которыми будем пользоваться при описании соответствующих алгоритмов и структур данных (см. в табл.).
Элемент | Описание |
item, pitem | Тип для одного элемента списка (запись) и указателя на него |
data | Поле данных (информационная часть элементов списка) |
next,prev | Указатели следующего, предыдущего элементов (указующая часть) |
head, top | Указатели на первыйи последний элемент списка |
pl,p2 | Рабочие указатели |
Опишем тип одного элемента односвязного списка и указателя на этот элемент:
Обратите внимание на описание — это единственный разрешенный случай, когда тип используется до объявления (item). Очевидно, разработчики компилятора сделали исключение ввиду особой важности списковых структур.
Перейдем к примерам. Наиболее просто реализуются действия со стеком, поэтому первый пример демонстрирует использование стека. Все действия со стеком выполняются только на одном конце, который называется вершиной стека. Стеки как структуры данных имеют широкое применение в системном программировании (например, при разработке компиляторов). В частности, область памяти, в которую помещаются параметры и локальные переменные подпрограмм, имеет структуру стека. Именно благодаря устройству стека возможны такие приемы программирования, как вложенные подпрограммы и рекурсия.
Конечно, в примере реализуется учебный стек, содержащий целые числа.
В примере реализованы две основные операции со стеком: добавление и удаление элементов. Для решения задачи потребовалось всего два указателя типа pitem. Один из них (top) всегда указывает на вершину стека, второй (p) – рабочий указатель, предназначенный для временного хранения адресов различных элементов. Обратите внимание на типовую процедуру вывода списка при помощи цикла while. Стандартное действие p:= p^ . prev; означает переход к следующему элементу стека (для стека правильнее назвать этот элемент не следующим, а предыдущим, т.к. он был помещен в стек раньше). Поэтому элементы стека можно вывести только в порядке, обратном тому, в котором они выводились.
Подведем итоги. Итак, связные списки и массивы – две основные структуры в оперативной памяти, которые используются для обработки данных. Сравним их между собой, выделив главные моменты:
- организация данных в памяти в виде связных списков обеспечивает более экономное использование памяти по сравнению с массивами;
- другим преимуществом связных списков является удобство вставки и удаления элементов. В массиве для этих целей приходилось раздвигать или сдвигать элементы. В списке для удаления и вставки достаточно только поменять значения указующих полей соседних элементов;
- явным достоинством массивов является простота их использования по сравнению со списками;
- еще более существенным преимуществом массива является высокая скорость доступа к элементу массива по его индексу. А для получения доступа к последнему элементу односвязного списка необходимо последовательно обойти всю цепочку, начиная с самого первого элемента.
Из сказанного можно сделать вывод – при решении задач обработки данных во многих случаях выбор между массивом и списком сделать непросто. Необходимо тщательно взвесить все плюсы и минусы обоих вариантов, а далее действовать по обстоятельствам. Начинающим рекомендуем не бояться указателей и динамических структур, т.к. программирование действий с ними – хороший способ развития логического мышления.
Читайте также: