Выделение и освобождение динамической памяти

Вся динамическая память – пространство ячеек, называемое кучей. Физически куча располагается в старших адресах, сразу за программой. Указатель на начало кучи храниться в предопределенной переменной HeapOrg, конец - FreePtr, текущую границу незанятой динамической памяти указывает указатель HeapPtr. Для выделения памяти под любую переменную используется процедура New. Единственным параметром является типизированный указатель:

Var
I,J: ^Integer;
R: ^Real;
Begin
New(I); {под I выделяется область памяти,}
{адрес первого байта этой области помещается в I}
End.

После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. После того как указатель приобрёл некоторое значение, то есть стал указывать на конкретный байт памяти, по этому адресу можно разместить значения соответствующего типа:

I^:= 2;
R^:= 2*Pi;
Допустима запись: R^:= Sqr (R^) + I^ - 17;
Но недопустима запись: R:= Sqr (R^) + I^ - 17; так как указателю R нельзя присвоить значение вещественного выражения.

Возврат динамической памяти обратно в кучу осуществляется оператором Dispose:
Dispose(R);
Dispose(I); - вернут в кучу, ранее забранные 8 байт.
Dispose(Ptr) не изменяет значения указателя Ptr, а лишь возвращает в кучу память, связанную с этим указателем. Однако повторное применение процедуры к “свободному” указателю приведет к возникновению ошибки времени исполнения. Чтобы указать, что указатель свободен, нужно использовать зарезервированное слово Nil.

К указателям можно применять операции отношения, в том числе и сравнения с Nil:

Const
P:^Real = Nil;
. . . . . . . .
Begin
If P = Nil then
New (P);
. . . . . . . .
Dispose(P);
P:= Nil;
End.

Можно освободить целый фрагмент кучи следующим образом:

1. Перед началом выделения динамической памяти значения указателя HeapPtr запоминается в переменной-указателе с помощью процедуры Mark.
2. Выполнение программы.
3. Освобождение фрагмента кучи от заполненного адреса до конца динамической памяти с использованием процедуры Release.

Var
P, P1, P2, P3, P4, P5: ^Integer;
Begin
New(P1);
New(P2);
New(P3);
New(P4);
New(P5);
Mark(P);
. . . . . . . .
Release (P);
End.

В этом примере процедурой Mark(P) в указатель P было помещено текущее значение HeapPtr, однако память под переменную не резервировалась.

Вызов Release уничтожает список свободных фрагментов в куче, созданных Dispose, поэтому совместное применение этих процедур не рекомендуется.

Для работы с нетипизированными указателями используются также процедуры GetMem(P, Size) и FreeMem(P, Size) - резервирование и освобождение памяти.
P - нетипизированный указатель, Size - размер.

За одно обращение к куче процедурой GetMem можно зарезервировать до 65521 байт. Освобождать нужно ровно столько памяти, сколько было зарезервировано, и именно с того адреса, с которого память была зарезервирована, иначе программа не будет работать и завершаться корректно!!!

Использование нетипизированных указателей даёт широкие возможности неявного преобразования типов:
Var
i,j: ^Integer;
r: ^Real;
Begin
New (i); { I:= HeapOrg; HeapPtr:= HeapOrg+2 }
j:= i; { J:= HeapOrg }
j^:=2;
Dispose (i); { HeapPtr:= HeapOrg }
New (r); { R:= HeapOrg; HeapPtr:= HeapOrg+6 }
r^:= Pi;
WriteLn (j^);
End.
{Будет выведено: "8578"}
{здесь преобразование не имеет никакого смысла}

Проблема потерянных ссылок
Работа с динамическими переменными через указатели требует большой тщательности и аккуратности при проектировании программ. В частности, следует стремиться освобождать выделенные области сразу же после того, как необходимость в них отпадает, иначе “засорение” памяти ненужными динамическими переменными может привести к быстрому ее исчерпанию.

Кроме того, необходимо учитывать еще одну проблему, связанную с противоречием между стековым принципом размещения статических переменных и произвольным характером создания и уничтожения динамических переменных. Рассмотрим следующий схематический пример программы:

Program LostReference;
Type
PPerson = ^Person;
Person = Record
. . . .
End;

Procedure GetPerson;
Var
Р: РРerson;
Begin
P:= New(PPerson);
End;

Begin
WriteLn(MemAvail);
GetPerson;
Writeln(MemAvail);
End.

Вызов New в процедуре GetPerson приводит к отведению памяти для динамической переменной типа Person. Указатель на эту переменную присваивается переменной Р. Рассмотрим ситуацию, возникающую после выхода из процедуры GetPerson. По правилам блочности все локальные переменные подпрограммы перестают существовать после ее завершения. В нашем случае исчезает локальная переменная Р. Но, с другой стороны, область памяти, отведенная в процессе работы GetPerson, продолжает существовать, так как освободить ее можно только явно, посредством процедуры Dispose. Таким образом, после выхода из GetPerson отсутствует какой бы то ни было доступ к динамической переменной, так как единственная "ниточка", связывавшая ее с программой - указатель Р - оказался потерянным при завершении GetPerson. Вывод на печать общего объема свободной памяти до и после работы GetPerson подтверждает потерю определенной области.

При проектировании программ, интенсивно использующих динамическую память, следует с особой внимательностью относиться к данной проблеме, так как Turbo Pascal, как, впрочем, и многие другие языки программирования, не имеет встроенных средств борьбы с засорением памяти неиспользуемыми динамическими переменными. Во всяком случае нужно придерживаться правила, согласно которому при выходе из блока необходимо или освободить все созданные в нем динамические переменные, или сохранить каким-то образом ссылки на них (например, присвоив эти ссылки глобальным переменным).

К описанной проблеме примыкает коллизия другого рода, заключающаяся в ситуации, когда некоторая область памяти освобождена, а в программе остался указатель на эту область. Рассмотрим следующий пример:

Var
P: Integer;

Procedure X1;
Var
i: Integer;
Begin
i:= 12345;
P:= @i;
WriteLn(P^); { напечатает 12345 }
End;

Procedure X2;
Var
j: Integer;
Begin
j:= 7777;
WriteLn(P^); { напечатает 7777, а не 12345 }
End;

Begin
X1;
X2;
End;

В этом примере глобальная ссылочная переменная Р первоначально (в процедуре X1) устанавливается на локальную переменную i. После завершения процедуры X2 переменная i исчезает, указатель Р “повисает”. Вызов процедуры Х2 приводит к тому, что на место, локальной переменной i, будет помещена локальная переменная j, и указатель Р теперь ссылается на нее, что подтверждает результат второго вызова WriteLn.

Таким образом, смысл указателя Р зависит в данном случае не от семантики решаемой задачи, a от системных особенностей ее функционирования, что может привести к неожиданным эффектам. Аналогичные ситуации возможны при повторном использовании областей динамической памяти: если на освобожденную область остался указатель, то он может быть (по ошибке) использован после повторного выделения этой памяти для другой переменной, что опять- таки не будет "замечено" системой, но может сделать поведение программы непредсказуемым.

 

61. Файлы.

Доступ к файлам. Процедуры и функции для работы с файлами. Текстовые файлы. Типизированные и нетипизированные файлы.

Файл - именованная область внешней памяти ПЭВМ (жесткого диска, гибкой дискеты, электронного "виртуального" диска), либо логическое устройство - потенциальный источник или приемник информации.

юбой файл имеет три характерных особенности:

  • Во-первых, у него есть имя, что дает возможность программе работать одновременно с несколькими файлами.
  • Во-вторых, он содержит компоненты одного типа. Типом компонентов может быть любой тип Турбо-Паскаля, кроме файлов. Иными словами, нельзя создать «файл файлов».
  • В-третьих, длина вновь создаваемого файла никак не оговаривается при его объявлении и ограничивается только емкостью устройств внешней памяти.

Файловый тип или переменную файлового типа можно задать одним из трех способов:
< имя > = FILE OF <тип>;
< имя > = ТЕХТ;
< имя > = FILE; .

Здесь < имя > - имя файлового типа или файловой переменной,
FILE, OF, TЕХТ - кодовые слова (англ.: файл, из, текст),
< тип > - любой тип Турбо-Паскаля, кроме файлов. Пример:

Type
Man=record
Name: string;
LastName: string;
End;
Men=file of Man;
Var
Staff: Men;
Numbers: file of real;
Book: Text;
A_File: File;

В зависимости от способа объявления можно выделить три вида файлов:

• типизированные (задаются предложением FILE OF ...),
• текстовые (задаются предложением ТЕХТ),
• нетипизированные (задаются предложением FILE).

Доступ к файлам

Любой Турбо-Паскалевой программе доступны два предварительно объявленных файла со стандартными файловыми переменными: INPUT - для чтения данных с клавиатуры и OUTPUT - для вывода на экран. Стандартный Паскаль требует обязательного упоминания этих файлов в заголовке программы. В Турбо-Паскале это необязательно, вот почему заголовок программы можно опускать.

Любые другие файлы, а также логические устройства становятся доступны программе только после выполнения особой процедуры открытия файла (логического устройства). Эта процедура заключается в связывании ранее объявленной файловой переменной с именем существующего или вновь создаваемого файла, а также в указании направления обмена информации: чтение из файла или запись в него.

Связывание файловой переменной с именем файла осуществляется обращением к встроенной процедуре ASSIGN:
ASSIGN(< ф.п. >, < имя файла или л.у. >);
Здесь < ф.п. > - файловая переменная (правильный идентификатор, объявленный в программе как переменная файлового типа);
< имя файла или л.у. > - текстовое выражение, содержащее имя файла или логическое устройство.

Пример:
Assign(Book,’PascalLecture.txt’);

Если имя файла задается в виде пустой строки, например, ASSIGN(f, ‘’), то файловая переменная связывается со стандартным файлом INPUT или ОUТРUТ.