Содержание | Объявление указателей | Выделение и освобождение дин.памяти | Примеры указателей | Процедуры и функции | Проблема потерянных ссылок | ||||||||||||||||||||
Динамическая память
Все глобальные переменные и типизированные константы размещаются в одной
непрерывной области оперативной памяти, которая называется сегментом
данных. Длина сегмента определяется архитектурой процессора 8086 и
составляет 64 Килобайта (65536 байт), что может вызвать определённые
трудности при описании и обработке больших массивов данных. С другой
стороны объём стандартной памяти - 640 Килобайт. Выход - использовать
динамическую память. Динамическая память - это оперативная память ЭВМ, предоставляемая Турбо-Паскалевой программе при её работе, за вычетом сегмента данных (64 К), стека (обычно 16 К) и собственно тела программы. По умолчанию размер динамической памяти определяется всей доступной памятью ЭВМ и, как правило, составляет не менее 200 - 300 Кбайт.
Регистры данных и адресов делятся на три группы:
физический адрес = 20010H
Как правило, в Турбо-Паскале указатель связывается с некоторым типом данных (типизированные указатели). Type Внимание! При объявлении типа PerPnt мы сослались на PerRec, который ещё не был объявлен, это связано с тем, что существует исключение из правил для указателей, которые содержат ссылку на ещё неописанный тип данных. Это исключение сделано не случайно, так как динамическая память позволяет использовать организацию данных в виде списков (каждый элемент имеет в своём составе ссылку на соседний элемент - обратите внимание на поле Next). В Турбо-Паскале можно объявлять
указатель и не связывать его с конкретным типом данных: Указатели такого рода называют нетипизированными. В них удобно размещать данные, структура которых меняется по ходу работы. В Турбо-Паскале можно передавать
значения только между указателями, связанными с одним и тем же типом
данных. но можно сделать так: 2. Выделение и освобождение динамической памяти Вся динамическая память –
пространство ячеек, называемое кучей. Физически куча располагается в
старших адресах, сразу за программой. Указатель на начало кучи храниться в
предопределенной переменной HeapOrg, конец -
FreePtr, текущую границу незанятой
динамической памяти указывает указатель HeapPtr.
Для выделения памяти под любую переменную используется процедура
New. Единственным параметром является
типизированный указатель: После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. После того как указатель приобрёл некоторое значение, то есть стал указывать на конкретный байт памяти, по этому адресу можно разместить значения соответствующего типа: I^:= 2; Возврат динамической памяти обратно в
кучу осуществляется оператором Dispose: К указателям можно применять операции
отношения, в том числе и сравнения с Nil: Использование указателей, которым не присвоено значение процедурой New или каким-либо другим способом, никак не контролируется системой и может привести к непредсказуемым результатам. Многократное чередование New и Dispose приводит к “ячеистой” структуре кучи. Дело в том, что все операции с кучей выполняется под управлением особой программы, которая ведёт учёт всех свободных фрагментов в куче. При выполнении оператора New( ) эта программа отыскивает минимальный свободный фрагмент, в котором может разместиться требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины, помечаются как занятая часть кучи. Можно освободить целый фрагмент
кучи следующим образом: Var В этом примере процедурой Mark(P) в указатель P было помещено текущее значение HeapPtr, однако память под переменную не резервировалась. Вызов Release уничтожает список свободных фрагментов в куче, созданных Dispose, поэтому совместное применение этих процедур не рекомендуется. Для работы с нетипизированными
указателями используются также процедуры GetMem(P,
Size) и FreeMem(P, Size) -
резервирование и освобождение памяти. За одно обращение к куче процедурой GetMem можно зарезервировать до 65521 байт. Освобождать нужно ровно столько памяти, сколько было зарезервировано, и именно с того адреса, с которого память была зарезервирована, иначе программа не будет работать и завершаться корректно!!! Использование нетипизированных
указателей даёт широкие возможности неявного преобразования типов:
Примеры использования
указателей. Теперь к любому элементу можно обратиться: PtrArr[i,j]^:=...; Но длина внутреннего представления указателей 4 байта, поэтому потребуется ещё 100*200*4 = 80000 байт, что превышает размер сегмента (65536 байт), доступный для статического размещения данных. Можно было бы работать с адресной
арифметикой (арифметикой над указателями), то есть не создавать массив
указателей, а вычислять адрес элемента непосредственно перед обращением к
нему. Однако в Турбо-Паскале над указателями не определены никакие
операции, кроме присваивания и отношения. Но задачу решить можно: Далее с помощью Ptr(Seg, Ofs: Word): Pointer можно создать значение указателя, совместимое с указателем любого типа. Таким образом, сначала с помощью
GetMem забираем из кучи несколько фрагментов подходящей длины (не более
65521). Удобно по строкам 200 * 100 = 20000 байт. Начало каждого фрагмента
запоминается в массиве PtrStr из 100 указателей. Теперь для доступа к
любому элементу строки нужно вычислить смещение этого элемента от начала
строки и сформировать указатель. далее работаем с Pr^:=. . . и т.д.
Function AddrE(i,j: Word):
ExtPoint; Function GetExt(i,j: Integer):
Extended; Procedure PutExt(i,j: Integer;
X: Extended); Begin {main} Мы предполагали, что каждая строка размещается в куче с начала границы параграфа и смещение каждого указателя PtrStr ровно 0. В действительности при последовательных обращениях к GetMem начало очередного фрагмента следует за концом предыдущего и может не попасть на границу сегмента. В результате при размещении фрагментов максимальной длины (65521 байт) может возникнуть переполнение при вычислении смещения последнего байта. 3. Процедуры и функции для работы с динамической памятью • Функция
Addr - возвращает результат типа Pointer, в котором содержится
адрес аргумента.
Проблема потерянных ссылок Кроме того, необходимо учитывать еще одну проблему, связанную с противоречием между стековым принципом размещения статических переменных и произвольным характером создания и уничтожения динамических переменных. Рассмотрим следующий схематический пример программы: Program LostReference; Вызов New в процедуре GetPerson приводит к отведению памяти для динамической переменной типа Person. Указатель на эту переменную присваивается переменной Р. Рассмотрим ситуацию, возникающую после выхода из процедуры GetPerson. По правилам блочности все локальные переменные подпрограммы перестают существовать после ее завершения. В нашем случае исчезает локальная переменная Р. Но, с другой стороны, область памяти, отведенная в процессе работы GetPerson, продолжает существовать, так как освободить ее можно только явно, посредством процедуры Dispose. Таким образом, после выхода из GetPerson отсутствует какой бы то ни было доступ к динамической переменной, так как единственная "ниточка", связывавшая ее с программой - указатель Р - оказался потерянным при завершении GetPerson. Вывод на печать общего объема свободной памяти до и после работы GetPerson подтверждает потерю определенной области. При проектировании программ, интенсивно использующих динамическую память, следует с особой внимательностью относиться к данной проблеме, так как Turbo Pascal, как, впрочем, и многие другие языки программирования, не имеет встроенных средств борьбы с засорением памяти неиспользуемыми динамическими переменными. Во всяком случае нужно придерживаться правила, согласно которому при выходе из блока необходимо или освободить все созданные в нем динамические переменные, или сохранить каким-то образом ссылки на них (например, присвоив эти ссылки глобальным переменным). К описанной проблеме примыкает коллизия другого рода, заключающаяся в ситуации, когда некоторая область памяти освобождена, а в программе остался указатель на эту область. Рассмотрим следующий пример: Var В этом примере глобальная ссылочная переменная Р первоначально (в процедуре X1) устанавливается на локальную переменную i. После завершения процедуры X2 переменная i исчезает, указатель Р “повисает”. Вызов процедуры Х2 приводит к тому, что на место, локальной переменной i, будет помещена локальная переменная j, и указатель Р теперь ссылается на нее, что подтверждает результат второго вызова WriteLn. Таким образом, смысл указателя Р зависит в данном случае не от семантики решаемой задачи, a от системных особенностей ее функционирования, что может привести к неожиданным эффектам. Аналогичные ситуации возможны при повторном использовании областей динамической памяти: если на освобожденную область остался указатель, то он может быть (по ошибке) использован после повторного выделения этой памяти для другой переменной, что опять- таки не будет "замечено" системой, но может сделать поведение программы непредсказуемым. |
|||||||||||||||||||||||||