Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Верхняя граница памяти MS-DOS

Поиск
Область памяти для динамических переменных (куча)
Оверлейный буфер (если необходим)
Стек для хранения локальных переменных и параметров
Сегмент данных
Рабочий код системного модуля (System)
Рабочие коды подключаемых модулей
Рабочий код основного блока программы
Префикс сегмента программы

Рисунок 1 - Распределение памяти при выполнении программы

При запуске программы (ЕХЕ-файла) MS-DOS организует в памяти нечто вроде анкеты на этот файл длиной 256 байт, которая называется префиксов структуры программы. После префикса начинается код ЕХЕ-файла. Код ЕХЕ-файла состоит из рабочего кода системного модуля, рабочих кодов подключаемых модулей и рабочего кода основного блока программы. Статические глобальные переменные основного блока и все типизированные константы включая локальные, располагаются в сегменте данных, общий объем которого не может в сумме превышать 64К. За сегментом данных следует облаем стека. В ней располагаются локальные переменные и параметры-значения процедур и функций во время их работы по вызову. Область стека не может превышать 64К (обычно 16К). Стек заполняется от своей верхней границы (она может быть назначена директивой компилятору $М) по направлению к началу, т.е. к старту сегмента. Выше стека программа отводит себе память под буфер для работы оверлеев - перекрывающихся частей программ. Если они не используются, то буфер не отводится. Еще выше располагается область памяти для размещения динамических переменных и структур данных, называ­емая областью кучи или просто кучей (еще она называется Heap-областью).

По мере того, как программы становятся более сложными и требуется; работа с большим количеством данных, область объемом в 64К, зарезервированная в Турбо Паскале для данных, может оказаться недостаточной, чтобы; содержать все необходимые программе данные.

Предположим, есть программа, требующая массива в 400 строк по 100 символов каждая. Для этого массива требуется примерно 40К, что меньше максимума в 64К. Если остальные переменные помещаются в оставшиеся 24К, массив такого объема проблемы не представляет. Но если нужно два таких массива? Это потребует 80К. Чтобы работать с большими объемами данных, нужно использовать динамически распределяемую область памяти.

Динамически распределяемая область памяти - это вся память, которую операционная система делает доступной для программы и которая не используется ее кодом, сегментом данных и стеком. Объемом распреде­ляемой динамической памяти можно управлять с помощью директивы компилятора $М.

Известно, что все переменные, встречающиеся в программе, должны быть описаны. Перед началом выполнения программы каждой переменной для размещения ее значений выделяется место в сегменте данных. Размер выделяемого места зависит от типа переменной. Например, для переменной типа Integer выделяется 2 байта. Обращение в программе к объекту, размещенному в некотором месте памяти, осуществляется с помощью имени переменной. Соответствие между переменной и сопоставленным ей местом в памяти сохраняется для описанных в программе переменных на всем протяжении выполнения программы.IB Паскале имеются средства, позволяю­щие заниматься отведением и освобождением памяти для размещения объектов того или иного типа непосредственно по ходу выполнения програм­мы. Память в этом случае отводится в динамической области. Данные, размер которых задается непосредственно во время выполнения программы, называются динамическими. Для объявления динамических данных в Паскале используется ссылочный тип, называемый еще типом-указателем. С помощью ссылочного типа можно объявлять переменные, значением которых будет адрес ячейки памяти.

Ссылочные переменные

Описание ссылочного типа выглядит следующим образом:

Туре Ptr = ^t,

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

Например:

Туре

Pint = ^Integer;

W = array [1..20] of Real;

p = ^W;

Var

N: Pint;

U: p;

Под переменную N и переменную U будет отведено по 4 байта памяти в сегменте данных. Переменные будут содержать адрес какой-либо ячейки памяти, расположенной в динамической области. Но прежде, чем переменная ссылочного типа примет значение, необходимо в ходе выполнения программы выполнить специальную процедуру. Ссылка представляет собой адрес начала, т.е. первой ячейки, некоторого места в памяти, выделенного для объекта базового типа. Переменная U будет содержать адрес первой ячейки, выделенной под массив W в динамической области памяти.

В объявлениях ссылочных типов после символа "^" может стоять только простое имя типа. В случае сложных имен используется переопределение типов, как в приведенном примере.

Указатели, связанные с адресами значений конкретных базовых типов, называются типизированными. N и U - типизированные указатели. В Турбо Паскале можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Такие указатели называются нетипизиро-ванными. Описание нетипизированных указателей осуществляется с помощью служебного слова Pointer, например, Var P: Pointer.

Адрес хранится как два слова: одно из них определяет сегмент, а другое - смещение. Значение указателя не может быть в явном виде выведено на экран или печать. Его надо предварительно расшифровать. Для работы с указателями вводится специальный набор функций:

Addr(X) Ссылка на начало объекта X в памяти (тип Pointer) Аналогом этой функции является операция @Х
Seg(X) Сегмент, в котором хранится объект X (тип Word)
Ofs(X) Смещение в сегменте для объекта X (тип Word)
Ptr(S,O:Word) Pointer Ссылка на место в памяти, заданное значениями смещения О и сегмента S (тип Pointer)
SizeOf(x) Размер объекта X в байтах

Так как значение указателя состоит из двух слов (Word), хранящих сегмент и смещение, можно вывести их в отдельности, используя функции Seg и Ofs:

Writeln('Сегмент ', Seg(p), ' смещение ', Ofs(p));

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

Var

Р1, Р2: ^Integer;

РЗ: ^Real;

РР: Pointer;

то присваивание Р1:= Р2 вполне допустимо, в то время как Р1:= РЗ запрещено, поскольку Р1 и РЗ указывают на разные типы данных. Это ограничение не распространяется на нетипизированные указатели. Можно записать РР:= РЗ; Р1:= РР и достичь нужного результата. Присутствие в программе таких переприсвоений говорит о том, что программист делает это осознано и в программе действительно нужны такие действия. Указателю можно присвоить значение Nil. Nil - это предопределенная константа типа Pointer, соответствующая адресу 0000:0000 (пустая ссылка). Если указателю присвоено значение Nil, то этот указатель ни на какие данные не ссылается.

Указатели могут сравниваться с помощью операций отношения = или <> (не равно). Сравнение для указателей - ненадежная операция. Если два указателя содержат один и тот же адрес в памяти, но записанный в них разными способами, то они считаются различными. Зато можно проверить, ссылается ли указатель р на что-нибудь или нет путем сравнения р <> Nil.

Содержимое ячейки доступно через имя указателя. Чтобы обратиться к данным, находящимся по адресу, содержащемуся в указателе, используется символ "^", который ставится сразу после имени ссылочной переменной. Эта операция называется операцией разыменования. Суть ее состоит в переходе от ссылочной переменной к значению, на которое она указывает. Пусть имеется следующее описание:

Var

a, b: ^Real;

тогда в программе с переменными а^ и b^ допустимы все действия, что и с любыми переменными типа Real, например,

а ^ := b ^;

b^:= sin(a"); и т.д.

Память под любую динамически размещаемую переменную выделяется процедурой New(p). Только после выполнения процедуры New имеет смысл обращаться к ссылочным переменным. Параметром обращения к этой процедуре является типизированный указатель. В результате обращения указатель приобретает значение, соответствующее адресу, начиная с которого, можно разместить данные. Это адрес динамической области данных или кучи. Начало кучи хранится в стандартной переменной Heaporg, конец - в переменной Heapend. Текущую границу незанятой динамической памяти указывает указатель Heapptr. Если Var I^ integer, то после выполнения New(i) указатель i приобретет значение, которое перед этим имел указатель кучи Heapptr, а сам Heapptr увеличит свое значение на 2, так как длина внутреннего представления типа Integer, с которым связан указатель i, составляет 2 байта. Надо понимать, что на самом деле механизм выделения памяти сложнее, но для нас важно понять принципиальную схему (рис. 2).

Рисунок 10.2 - Расположение кучи в памяти ПК

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

Проанализируем результаты вывода в следующей программе:

Var

p1, ip: ^integer;

Begin

{1}Writeln(ip^,' ',p1^);

{Выводятся значения, размещенные в динамической памяти по адресу Heapptr)

new(ip); new(p1);

{2}Writeln(ip^,' ',p1^);

{Выводятся значения, размещенные в динамической памяти по адресу Heapptr}

ip^:= 7;

р1^:= 20;

{3}Writeln(ip^,' ',p1^); {7 20}

ip:= р1;

{4}Writeln(ip^,' ',p1^); {20 20}

dispose(ip);

{dispose(p1);}

{Использовать нельзя, так как р1 и ip указывают на одну и ту же ячейку памяти}

{5}Writeln(ip^,' ',p1^); {20 20}

ip:= nil;

{6}Writeln(ip^,' ',p1^); {Случайное значение и 20}

End.

Несмотря на то, что первый оператор вывода предшествует процедуре New, ошибки не произойдет. Однако за указателями ip и р1 ячейки еще не закреплены и могут быть использованы другими динамическими переменными. Первый оператор является некорректным: работать с динамическими переменными можно только после выполнения процедуры New. Второй оператор Writeln выведет ту информацию, которая на момент начала выполнения программы расположена в ячейках с адресом Heapptr и Heapptr + 2. Следующим оператором в эти ячейки будет записана информация: 7 и 20 соответственно. Третий оператор вывода выдаст эти значения. Значение указателя ip изменилось после выполнения присваивания ip:= р1. Теперь оба указателя указывают на одну и ту же ячейку памяти, в которой хранится,, число 20. К числу 7 нет доступа. Это значение теперь пассивно занимает, память, т.е. 7 превратилось в мусор. Освободить память можно только для одного указателя ip или р1, так как оба эти указателя указывают на одну и ту < же ячейку памяти. Пятый оператор Writeln выведет те же значения 20 и 20, но это лучше рассматривать как случайность. После выполнения процедуры Dispose(ip) значения ссылок ip и р1 не определено, как и значения ip" и р1А. Выполнение шестого оператора Writeln также не приведет к ошибке, хотя его присутствие не имеет никакого смысла. Приведем еще один пример.

Type

Plnteger = ^Integer;

Var

SomeNumber: Integer;

Begin

SomeNumber:= 17;{присвоить SomeNumber 17}

SomeAddress:= @SomeNumber; {SomeAddress указывает на SomeNumber}

Writeln(SomeNumber); {напечатать 17}

{Writeln(SomeAddress); не допускается; указатели печатать нельзя}

Writeln(SomeAddress^); {напечатать 17}

AnotherAddress:= SomeAddress;{также указывает на SomeNumber}

AnotehrAddress^:= 99; {новое значение для SomeNumber}

Writeln(SomeNumber);{Напечатать 99} End.

Перед использованием указателей им всегда нужно присваивать значения. Если разыменовывается указатель, которому еще не присвоено значение, то считанные из него данные могут представлять собой случайные биты, а присваивание значения указываемому элементу может затереть другие данные, программу или даже операционную систему. Чтобы избежать разыменования указателей, которые не указывают на что-либо значащее, нужен некоторый способ информирования о том, что указатель недопустим. В Паскале предусмотрено зарезервированное слово Nil, которое можно использовать в качестве содержательного значения указателей, которые в данный момент ни на что не указывают. Указатель Nil является допустимым, но ни с чем не связанным. Перед разыменованием указателя нужно убедиться, что он отличен от Nil (не пуст).

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

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

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

Рисунок – 10.3. Процедуры Mark и Release

Иногда нежелательно выделять память тем способом, как это делает New. Может потребоваться выделить больше или меньше памяти, чем это делает New по умолчанию. Параметром процедуры New может быть только типизированный указатель. Для работы с нетипизированными указателями используются процедуры Getmem и Freemem:

Getmem (p, Size) - резервирование памяти;

Freemem (p, Size) - освобождение памяти,

где р - переменная типа указатель; Size - размер в байтах требуемой или освобождаемой части кучи.

Процедуры Getmem и Freemem используются в паре так же, как New и Dispose.

Существуют еще две функции, возвращающие важную информацию о динамически распределяемой области памяти: Mem Avail и MaxAvail. Функция MemAvail возвращает общее число байт, доступных для распределения в динамической памяти. Перед выделением большого объема в динамически распределяемой памяти полезно убедиться, что такой объем памяти доступен. Функция MaxAvail возвращает размер наибольшего доступного блока непрерывной памяти в динамически распределяемой области. Первоначально при запуске программы MaxAvail равно MemAvail, поскольку вся динамически распределяемая область памяти является доступной и непрерывной. После распределения нескольких блоков памяти пространство в динамически распре­деляемой области скорее всего станет фрагментированным. Это означает, что между частями свободного пространства имеются распределенные блоки. Функция MaxAvail возвращает размер наибольшего свободного блока.

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

Var

IP: ^Integer;

Begin

New(IP);

New(IP);

End.

При первом вызове New в динамически распределяемой памяти выделяется 2 байта и на них устанавливается указатель IP. Второй вызов New выделяет другие 2 байта и IP устанавливается на них. В программе первые 2 байта будут потеряны.

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

Var

IP: ^Integer;

Begin

New(IP);

...

Dispose(IP);

IP:= Nil;

...

If IP = Nil then

New(IP);

End.



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 164; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.133.143.25 (0.009 с.)