Лекция № Распределение памяти. Динамические переменные

Вид материалаЛекция

Содержание


Help можно выбрать пункт Error messages
Динамическая память
Всегда сохраняйте программу перед запуском!
Подобный материал:




Лекция № 9. Распределение памяти. Динамические переменные.

  1. Постановка задачи.  



Проблема: при решении задач, в которых предполагается обработка массивов, встает вопрос об объявлении типов массива, определении переменных и выделении памяти для хранения элементов массивов. Например, объявлен тип:  


const number = 15000;  


type massiv = array[0..number] of longint;  


var chisla : massiv;  


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

«Error 22: Structure too large.»


Это значит: «Ошибка 22: Структура чересчур объемиста»  


Теперь в меню Help можно выбрать пункт Error messages, а затем выбрать пояснение о сути ошибки. Оно гласит, что максимальный размер для структурированных типов составляет 65520 байт. Что это значит? Для хранения одного элемента типа longint требуется 4 байта памяти, а для массива из 15000 элементов – 60000 байт. При этом раскладе программа работает правильно. Если же потребуется хранение 20000 элементов, то это значит и 80000 байт памяти. Таким образом, превышается максимальный объем памяти. Выход из данной ситуации, в том числе и для работы с массивами, большими 20000 элементов, в языке Паскаль возможен и представляется в виде использования динамической памяти и указателей.  

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



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


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


Динамическая память, известная также как «куча», рассматривается в Turbo Pascal как массив байтов, который имеет объем порядка 300000 байт. Это позволяет обрабатывать массивы гораздо большего объема.  


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


  1. Карта памяти.  



Вся оперативная память, выделяемая для работы программы, написанной на Турбо Паскале, делится на сегменты.  


Сегмент – это непрерывный участок памяти, размер которой не превышает 65536 байт.  

  • один сегмент всегда необходим для размещения кода главной программы;  



  • если программа имеет модульную структуру, то дополнительно по одному сегменту выделяется для каждого модуля;  



  • память обязательно выделяется под системный модуль SYSTEM, -  



все эти сегменты носят название сегментов кода.  


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


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


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


{$М <размер стека>, <мин. объем дин. памяти>, <макс, объем дин. пам.>}  


Размер сегмента стека не может превышать 64К; по умолчанию, т. е. в случае отсутствия соответствующей директивы, размер стека устанавливается равным 16К.  


За сегментом стека размещается оверлейный буфер, размер которого может устанавливаться программистом.  


Оставшаяся свободной область частично или полностью может быть занята динамической памятью. В ней размещаются динамические переменные.  


Сегменты программы располагаются в памяти в определенном порядке:  


сегмент главной программы,  


кодовые сегменты модулей,


сегмент кода модуля SYSTEM,  


сегмент данных  


сегмент стека.  


Все версии системы программирования Турбо Паскаль кроме последней 7.0 могли адресовать только 640К байт памяти. То есть, все сегменты, оверлейный буфер и динамические переменные должны были помещаться в области памяти с адресами от 0 до 640К. В седьмой версии это ограничение снято.

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

  1. Указатели.


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


В Паскале имеется два вида указателей: типизированные и нетипизированные.

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

Var <имя переменной-указателя>:<имя базового типа>;

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

Var <имя переменной-указателя>: pointer;


Var p1: integer; {указатель на переменную целого типа}

p2:string; {указатель на строку}

p3: pointer; {нетипизированный указатель}


Нетипизированный указатель pointer совместим со всеми типами указателей.


Указатель размещается в сегменте данных (или в стеке – для параметров подпрограмм) и занимает 4 байта. Сами же переменные размещаются в динамической памяти – heap («куча»). Следует помнить, что:
  • динамические переменные являются переменными с управляемым временем жизни. Их время жизни длится от момента создания до момента уничтожения, и управляет этим процессом программа;
  • по окончании работы программы вся выделенная для нее память, в том числе и куча, освобождается;
  • следует своевременно удалять созданные в ходе выполнения программы динамические переменные, чтобы программа не «ела память».

Для размещения данных в динамической памяти (для создания динамических переменных) используются процедуры New и GetMem.

New(p) – используется для типизированных указателей.

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

GetMem(p, Size) – используется для нетипизированных указателей.

Параметр Size задает размер памяти в байтах, которую необходимо выделить (в 16-разрядных компиляторах размер Size не может превышать 64 Кбайта). Например, GetMem(p, 100) выделяет для хранения данных, на которые указывает переменная p, ровно 100 байт, а указатель p получает значение адреса первого байта выделенной области.

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

Всегда сохраняйте программу перед запуском!


Указатели получают свои значения только после выполнения процедур выделения памяти или обнуления. Для обнуления указателя ему присваивают «пустое» значение – nil, не указывающее ни на какое место в памяти.

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

Для обращения к самим переменным используется операция разыменования указателя – записывается с помощью знака после имени указателя:

<имя указателя>

Например: p1:=5; p2:=’Privet’; pmas[3]:=5.6;


Для экономного использования динамической памяти следует вовремя освобождать память, выделенную для данных, если эти данные больше не нужны для работы программы. Для освобождения памяти используются процедуры Dispose(p) и FreeMem(p,Size).

Процедура Dispose(p) используется для типизированных указателей – освобождает память, на которую указывал p.

Процедура FreeMem(p,Size) используется для нетипизированных указателей – освобождает Size байтов, начиная с адреса p.

Следует помнить, что после освобождения памяти указатели не обнуляются и указывают на несуществующую переменную. Следует после высвобождения памяти всем высвободившимся указателям присваивать значение nil.


Динамические переменные используются, в основном, в двух ситуациях:
  • для работы с массивами больших или переменных размеров;
  • для работы с особыми структурами данных переменных размеров – динамическими структурами данных.



Примеры.

Пример 1: Динамический массив.

type mas=array[1..1] of integer;

tmas=mas;

var m:tmas; n,i,max: integer; size,s:longint;

begin

write('vvedite n:'); readln(n);

size:=n*sizeof(integer);

getmem(m, size);

randomize;

for i:=1 to n do

begin

m[i]:=random(10);

{ write(m[i], ' ');}

s:=s+m[i]

end;

max:=1;

for i:=2 to n do

if m[i]>=m[max] then max:=i;

writeln('s=',s);

writeln('max - m[',max,']=',m[max]);

{ writeln('m[135]=',m[135],' ','m[1034]=',m[1034]);}

readln;

freemem(m,size)

end.