Сайт Льва Волкова
  
· Кровать как элемент мебели появилась на Руси только в начале 17 века. До этого спали либо на полатях, либо на лавка или печи.
 
      На главную  
 Личное
  Статьи
  Задачи 
 Ссылки
 АТ-531
www.levvol.ru    
 

Динамическая память

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

Динамическая память -- это фактически единственная возможность обработки массивов данных большой размерности. Многие практические задачи трудно или невозможно решить без использования динамической памяти

При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным

вверх страницы
Указатели

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

вверх страницы
Объявление указателей

Как правило, в Турбо Паскале указатель связывается с некоторым типом данных. Такие указатели будем называть типизированными. Для объявления типизированного указателя используется значок ^, который помещается перед соответствующим типом, например:

 
var
  p1: ^integer;
  р2: ^real;
type
  PerconPointer = ^PerconRecord;
  PerconRecord = record
    Name: string;
    Job: string;
    Next: PerconPointer
  end;

Здесь p1 - имя переменной-указателя; знак "^" показывает, что p1 является не обычной переменной, а указателем; integer - тип той переменной, на которую указывает p1. Переменная p1 представляет собой не что иное как адрес того места в памяти, где будет храниться сама динамическая переменная (в нашем случае число типа integer).

Обратите внимание: при объявлении типа PerconPointer мы сослались на PerconRecord, который предварительно в программе объявлен не был. Как уже отмечалось, в Турбо Паскале последовательно проводится в жизнь принцип, в соответствии с которым перед использованием какого-либо идентификатора он должен быть описан. Исключение сделано только для указателей, которые могут ссылаться на еще не объявленный тип данных. Это исключение сделано не случайно.

Динамическая память дает возможность реализовать широко используемую в некоторых программах организацию данных в виде списков. Каждый элемент списка имеет в своем составе указатель на соседний элемент, что обеспечивает можность просмотра и коррекции списка. Если бы в Турбо Паскале не было этого исключения, реализация списков была бы значительно затруднена.



Списочная структура данных

В Турбо Паскале можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Для этого служит стандартный тип POINTER, например:

var
  р: pointer;

Указатели такого рода называют нетипизированными. Поскольку нетипизированные указатели не связаны с конкретным типом, с их помощью удобно динамически размещать данные, структура и тип которых меняются в ходе работы программы.

Как уже говорилось, значениями указателей являются адреса переменных в памяти, поэтому следовало бы ожидать, что значение одного укаателя можно передавать другому. На самом деле это не совсем так. В Турбо Паскале можно передавать значения только между указателями, связанными с одним и тем же типом данных. Если, например,

var
  p1,p2: ^integer;
  р3: ^real;
  рр: pointer;

то присвяивание р1 := р2; вполне допустимо, в то время как р1 := р3;запрещено, поскольку p1 и p3 указывают на разные типы данных. Это ограничение, однако, не распространяется на нетипизированные указатели, поэтому мы могли бы записать pp := р3; р1 := рр; и тем самым достичь нужного результата.

Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя является адрес, а чтобы указать, что речь идет не об адресе, а о тех данных, которые размещены по этому адресу, за указателем ставится ^. Если Вы четко уясните себе это, у Вас не будет проблем при работе с динамической памятью.

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

Для всех динамических переменных в памяти отводится пространство, называемое динамической областью, или кучей.

вверх страницы
Процедуры управления кучей

Перед тем как пользоваться динамической переменной, например var p:^real; требуется выделить для неё место в куче. Это делается с помощью процедуры New. Для указателя p это будет

New(p);

В результате такого действия в куче выделено место под переменную типа real, обратиться к ней можно, записав p^, например p^:=123.5.

Если потребуется изменить значение указателя, например, заставить его указывать на другую переменную, то старую переменную следует уничтожить, то есть объявить занимаемую старой переменной память свободной.

Если этого не сделать, то при изменении указателя сама переменная станет мусором (место в памяти объявлено занятым, а получить к нему доступ уже невозможно). Уничтожение динамической переменной выполняется процедурой Dispose:

 Dispose(p); 

Указателю можно присваивать значение другого указателя такого же типа, а также значение nil, которое означает "ни на что не указывает".

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

r^ := sqr(r^) + i^ - 17;

Разумеется, совершенно недопустим оператор

r := sqr(r^) + i^ - 17;

так как указателю r нельзя присвоить значение вещественного выражения. Точно так же недопустим оператор

r^ := sqr(r);

поскольку значением указателя r является адрес, и его (в отличие от того значения, которое размещено по этому адресу) нельзя возводить в квадрат. Ошибочным будет и такое присваивание:

r^ := i;

так как вещественным данным, на которые указывает r, нельзя присвоить значение указателя (адрес).

Указатели можно сравнивать между собой. Однако имеют смысл лишь проверки на равенство и на неравенство указателей:

if p^=r^ then ...
if p^<>r^ then ... 
 

Возможна такая программа:

const
  р: ^real = NIL;
begin
  . . . . .
  if р = NIL then
    new(p);
  . . . . .
  dispose(p);
  p := NIL;
  . . . . .
end.

Никакие другие операции сравнения над указателями не разрешены. Приведенный выше фрагмент иллюстрирует предпочтительный способ объявления указателя в виде типизированной константы с одновременным присвоением ему значения NIL. Следует учесть, что начальное значение указателя (при его объявлении в разделе переменных) может быть произвольным.

Для размещения динамических переменных, наряду с New(p) применяется процедура

GetMem(p,Size)

В качестве параметра p может быть любой указатель. Второй параметр Size - любое выражение типа Word. Эта процедура производит выделение в куче неразрывного блока памяти с размером, определяемым парметром Size

Var p:pointer;
Begin
..............
GetMem(p,4*1024);  
{Теперь p указывает на
блок памяти размером 4К. 
p^ не имеет типа,
но содержит 4096 байт} 
................

Для осовобождения непрерывных участков заданного размера применяют процедуру:

FreeMem(p,Size)

Параметры p и Size аналогичны одноименным параметрам процедуры GetMem(p,Size).

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

SizeOf(var x):word 

которая возвращает число байт, необходимых для размехения в памяти объекта x. В качестве параметра можно указать имя станартного типа. Например, SizeOf(real) вернет 6.

Если память для указателя p была выделена процедурой New(p), то овобождать её следует при помощи процедуры Dispose(p).

Интересны две процедуры Mark(p) и Release(p).

Процедура Mark(p) запоминает состояние динамической памяти в тот момент, когда эта процедура вызывается. В укзателе p сохраняется адрес первого байта свободной области памяти. Далее можно несколько раз выделять память. Затем процедура Release(p) возвращает динамическую память в состояние. которое было запомнено ранее при помощи процедуры Mark(p).

Var p,p1,p2,p3,p4:pointer;
...........
New(p1);
New(p2);
Mark(p);
New(p3);
New(p4);
Release(p);
............
Процедуры Mark и Release

Использовать процедуру Release вместе с Dispose и FreeMem не рекомендуется.

Пример. Напишем программу, рисующую движущийся по экрану мячик. При этом предполагается, что мячик движется на фоне ранее нарисованного изображения, которое не должно быть изменено. Для этого перед рисованием мячика будем сохранять область экрана, на которую он попадёт, затем после перемещения мячика возвращать ранее сохраненную область на экран. Предполагается, что у нас написана процедура DrawSCR, рисующая на экране ихобажение, на фоне которого будет двигаться мячик, например какой-нибудь пейзаж, и две функции f(x), f(y), вычисляющие новые значения координат x, y мячика в зависимости от предыдущих.

Program Ball;
Uses CRT, Graph;
Var d,m:integer;
    x,y,size:integer;
    b_m:pointer;
	q:boolean;
	c:char;

{описание процедур и функций} 
Procedure DrawCSR;
{Звездное небо}
Var x,y,i:integer;
Begin
SetBkColor(black);
randomize;
for i:=1 to 4000 do begin
x:=random(getMaxX); y:=random(GetMaxY);
PutPixel(x,y,white);
end;
End;
Function fx(x:integer):integer;
Begin
x:=x+5
End;
Function fy(y:integer):integer;
Begin
y:=y+3
End; 
Begin
 d:=detect; m:=detect;
 InitGraph(d,m,'');
 {Инициализация графического режима}  		 
 DrawCSR;  {Нарисовать фон}
 x:=11; y:=GetMaxY div 2; 
 {Начальные координаты мячика} 
 SetFillStyle(SolidFill,Red);
 size:=ImageSize(10,10,30,30);
 {Определение размера выделяемой
  под рисунок памяти} 
 GetMem(b_m,size); {Выделение памяти} 
 q:=false;
 Repeat
 GetImage(x,y,x+20,y+20,b_m^); 
 {Сохранение области экрана} 
 Circle(x+10,y+10,10); {Рисование мячика}
 FloodFill(x+10,y+10,white);
 Delay(2500); {Задержка}
 PutImage(x,y,b_m^,0); 
 {Восстановление сохраненной области}
 x:=f(x); y:=f(y);{Новые координаты мячика}
 q:=(x>=(GetMaxX-21)) or (x<=10) 
 or (y>=(GetMaxY-21)) or (y<-10);
 {q равно true, когда мячик 
  окажется за границами экрана}
 Until q;
 FreeMem(b_m,size); {Освобождение памяти}
 c:=readkey;
 CloseGraph;
End.  

Функция Addr(var x):pointer возвращает адрес параметра x. Этой функции идентична унарная операция @x.

 

[назад] [содержание] <[вперед]