Методические указания для студентов 1 курса факультета математики, механики и компьютерных наук

Вид материалаМетодические указания

Содержание


1.4Динамическая память и динамические переменные
1.5Процедуры New и Delete
Подобный материал:
1   2   3   4   5   6   7   8

1.4Динамическая память и динамические переменные


Память, отводимая под данные программы, делится на статическую, автоматическую и динамическую. Статическая память выделяется до начала работы программы под глобальные переменные и константы и освобождается только при завершении программы. Автоматическая память выделяется на программном стеке под локальные переменные при вызове подпрограммы, а после завершения подпрограммы автоматически освобождается. При этом статическая память инициализируется нулевыми значениями, а автоматическая – не инициализируется (это делается для ускорения вызова подпрограммы).

Поскольку как программный стек, так и область статической памяти, выделяются заранее в момент начала работы программы, статическая и автоматическая память имеют фиксированный размер. Однако во многих задачах в разные моменты работы программы требуется существенно различное количество памяти. Отводить для этого фиксированный максимально необходимый размер памяти – расточительство. С данной проблемой мы уже сталкивались при работе с массивами: при описании массива указывается его максимально возможный размер, текущая же заполненность массива, как правило, меньше его размера.

Динамическая память, называемая также кучей, выделяется явно по запросу программы из ресурсов операционной системы и контролируется указателем. Она не инициализируется автоматически и должна быть явно освобождена. В отличие от статической и автоматической памяти динамическая память практически не ограничена (ограничена лишь размером оперативной памяти) и может динамически меняться в процессе работы программы. Недостатки динамической памяти являются продолжением ее достоинств. Во-первых, поскольку она контролируется указателем, доступ к ней осуществляется несколько дольше, чем для статической и автоматической памяти. Во-вторых, программист сам должен заботиться о выделении и освобождении памяти, что чревато большим количеством потенциальных ошибок.

1.5Процедуры New и Delete


Для выделения динамической памяти, контролируемой типизированным указателем, используется стандартная процедура New, для освобождения – стандартная процедура Dispose. Если pt – указатель на тип T, то вызов New(pt) распределяет в динамической памяти переменную типа T и записывает в pt адрес этой переменной:



Переменная, распределенная в динамической памяти, называется динамической переменной. Она не имеет своего имени и для доступа к ней используется разыменованный указатель pt. После работы с динамической переменной занимаемая ею память должна быть освобождена вызовом стандартной процедуры Dispose, например: Dispose(pt). Таким образом, динамическая переменная существует между вызовами New и Dispose:

var pt: real;

begin

  New(pt);

  pt:=2.8;

pt:=pt*2;

...

Dispose(pt);

end.

Выделение и освобождение динамической памяти выполняется специальной подсистемой программы, называемой менеджером кучи. Менеджер кучи хранит список всех незанятых блоков в динамической памяти. При вызове New менеджер кучи ищет незанятый блок подходящего размера, выделяет в нем память и модифицирует список незанятых блоков. При вызове Dispose блок вновь помечается как свободный. После завершения программы вся выделенная для нее динамическая память автоматически возвращается назад системе.

Если динамическая память выделяется в подпрограмме для решения локальных задач данной подпрограммы, то она должна быть освобождена в конце работы этой подпрограммы. Исключение составляют так называемые «создающие» подпрограммы, основным предназначением которых является вернуть объект, созданный в динамической памяти. Например:


function NewInteger(i: integer): pinteger;

begin

New(Result);

Result:=i;

end;

var pi: pinteger;

begin

pi:= NewInteger(5);

...

При своем вызове функция NewInteger возвращает указатель на динамическую переменную, которая должна быть впоследствии освобождена. Основная проблема состоит в том, что NewInteger не является стандартной функцией, и при ее вызове можно забыть, что она выделяет динамическую память. Один из способов «напомнить» об этом программисту – дать функции имя, свидетельствующее о ее «создающей» способности. Например, имя такой функции может начинаться с префикса New или Create.

Пример. Массив указателей на переменные разных типов.

В некоторых задачах возникает необходимость хранить в массиве данные различных типов. Пусть в массиве требуется хранить данные типа integer, real и shortstring.

Приведем вначале решение, не использубщее указатели.

Решение 1. Используем записи с вариантами. Опишем следующие типы:

type TVar=(tInt,tReal,tStr);

Variant = record

case t: TVar of

tInt: (i: integer);

tReal: (r: real);

tStr: (s: shortstring);

end;

Теперь опишем массив записей Variant и добавим в него несколько значений:

var A: array [1..10] of Variant;

begin

A[1].t:=tInt; A[1].i:=5;

A[2].t:=tReal; A[2].r:=3.14;

A[3].t:=tStr; A[3].s:='Delphi';

end.

Для вывода содержимого массива, очевидно, следует воспользоваться циклом

for i:=1 to 3 do

case A[i].t of

tInt: writeln(A[i].i);

tReal: writeln(A[i].r);

tStr: writeln(A[i].s);

end;

Такое решение имеет важный недостаток: каждый элемент массива имеет размер, определяемый самым большим типом shortstring, что расточительно.

Решение 2. В вариантной части записи Variant будем хранить не значения соответствующих типов, а указатели на них:

type

TVar=(tInt,tReal,tStr);

pinteger=integer;

preal=integer;

pshortstring=shortstring;

Variant = record

t: TVar;

case t: TVar of

tInt: (pi: pinteger);

tReal: (pr: preal);

tStr: (ps: pshortstring);

end;

Будем добавлять в такой массив указатели на переменные разных типов:

var A: array [1..10] of Variant;

begin

A[1].t:=tInt; New(A[1].pi); A[1].pi:=5;

A[2].t:=tReal; New(A[2].pr); A[2].pr:=3.14;

A[3].t:=tStr; New(A[3].ps); A[3].ps:='Delphi';

Для вывода содержимого такого массива воспользуемся следующим циклом:

for i:=1 to 3 do

case A[i].t of

tInt: writeln(pinteger(A[i].p));

tReal: writeln(preal(A[i].p));

tStr: writeln(pstring(A[i].p));

end;

В данном решении суммарный объем данных определяется не размером максимального типа данных, а реальным содержимым в момент выполнения программы. По окончании работы с массивом A динамическую память, занимаемую его элементами, следует освободить. Поскольку параметр процедуры Delete имеет тип pointer, то для освобождения занимаемой памяти можно передать любое из полей-указателей, например, pi:

for i:=1 to 3 do

Delete(A[i].pi);