Управление динамической памятью в Турбо Паскаль


Указатели и динамическая память

содержание

  • динамическая память.
  • адреса и указатели.
  • объявления указателей.
  • Выделение и освобождение динамической памяти.
  • Использование указателей.
  • Процедуры и функции для работы с динамической памятью.
    • функция ADDR.
    • функция CSEG.
    • процедура DISPOSE.
    • функция DSEG.
    • процедура FREEMEM.
    • процедура GETMEM.
    • процедура MARK.
    • функция MAXAVAIL.
    • функция MEMAVAIL.
    • процедура NEW.
    • функция OFS.
    • функция PTR.
    • процедура RELEASE.
    • функция SEG.
    • функция SIZEOF.
  • Администратор вместе.

 

ДИНАМИЧЕСКАЯ ПАМЯТЬ

Все переменные, объявленные в программе, размещаются в одной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента данных определяется архитектурой микропроцессоров 80х86 и составляет 65536 байт, что может вызвать известные трудности при обработке больших массивов данных. С другой стороны, объем памяти ПК (обычно не менее 640 Кбайт) достаточен для успешного решения задач с большой размерностью данных. Выходом из ситуации может быть использование так называемой динамической памяти.

Динамическая память — это оперативная память ПК, предоставляется программой при ее работе, за вычетом сегмента данных (64 Кбайт), стека (обычно 16 Кбайт) и собственно тела программы.Размер динамической памяти можно варьировать в широких пределах. По умолчанию этот размер определяется всей доступной памятью ПК и, как правило, составляет не менее 200 … 300 Кбайт.

Динамическая память — это фактически единственная возможность обработки массивов данных большой размерности. Многие практические задачи трудно или невозможно решить без использования динамической памяти. Такая необходимость возникает, например, при разработке систем автоматизированного проектирования (САПР): размерность математических моделей, используемых в САПР, может значительно

отличаться в различных проектах; статическую (т.е. на этапе разработки САПР) распределение памяти в этом случае, как правило, невозможно. Наконец, динамическая память широко используется для временного запоминания деревянных при работе с графическими и звуковыми средствами ПК. Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. В отличие от этого статическое размещение осуществляется компилятором Турбо Паскаля в процессе компиляции программы. При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменных.

АДРЕС И указатели

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

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

Указатель — это переменная, которая в качестве своего значения содержит адрес байта памяти.

В ПК адреса задаются совокупностью двух шестнадцатеричных слов, которые называются сегментом и смещением. Сегмент — это участок памяти, имеет длину 65536 байт (64 Кбайт) и начинается с физического адреса, кратного 16 (то есть 0, 16, 32, 48 и т.д.).Смещение указывает, сколько байт от начала сегмента необходимо пропустить, чтобы обратиться к нужному адресу.

Адресное пространство ПК составляет 1 Мбайт (речь идет о так называемой стандартной памяти ПК, на современных компьютерах с процессорами 80386 и выше адресное пространство составляет 4 Гбайт, однако в Турбо Паскале нет средств, поддерживающих работу с дополнительной памятью; при использовании среды Borland Pascal with Objects 7.0 такая возможность есть). Для адресации в пределах 1 Мбайта нужно 20 двоичных разрядов, которые выходят из двух шестнадцатеричных слов (сегмента и смещения) следующим образом: содержание сегмента смещается влево на 4 разряда, освободившиеся праве разряды заполняются нулями, результат состоит из содержанием смещения.

Схема формирования адреса в ПК

 

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

Таким образом, по своей внутренней структуре любой указатель представляет собой совокупность двух слов (данных типа WORD), трактуемых как сегмент и смещение.С помощью указателей можно размещать в динамической памяти любой из известных в Турбо Паскале типов данных. Лишь некоторые из них (BYTE, CHAR, SHORTINT, BOOLEAN) занимают во внутреннем представлении один байт, другие — несколько смежных.Поэтому на самом деле указатель адресует только первый байт данных.

 

объявления указателей

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

 

var

p1: ^ integer; р2: ^ real; type

PerconPointer = ^ PerconRecord; PerconRecord = record

Name: string; Job: string;

Next: PerconPointer end;

 

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

Читать  Порядковые типы в Паскаль

Учетная структура данных

 

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

 

var

р: pointer;

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

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

 

var

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

гг: pointer;

то присваивания

р1 = р2;

вполне допустимо, в то время как

р1 = р3;

запрещено, поскольку Р1 и Р3 указывают на различные типы данных.Это ограничение, однако, не распространяется на нетипизированные указатели, поэтому мы могли бы записать

pp = р3;

 

р1 = гг;

и тем самым достичь нужного результата.

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

 

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

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

 

Начало кучи хранится в стандартной переменной HEAPORG, конец — в переменной HEAPEND. Текущую границу незанятой динамической памяти указывает указатель HEAPPTR.

 

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

 

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

 

var

i, j: ^ integer; r: ^ real; begin

New (i) . . . . .End.

 

После выполнения этого фрагмента указатель i примет значение, которое перед этим имел указатель кучи HEAPPTR, а сам HEAPPTR увеличит свое значение на 2, так как длина внутреннего представления типа INTEGER, с которым связан указатель i, составляет 2 байта (на самом деле это не совсем так: память под любую переменную выделяется порциями, кратными 8 байт). оператор

new (r)

вызовет еще раз смещение указателя HEAPPTR, но теперь уже на 6 байт, потому что такая длина внутреннего представления типа REAL. Аналогичным образом выделяется память и для переменной любого другого типа.После того как указатель получил некоторое значение, то есть стал указывать на конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа. Для этого сразу за указателем без каких-либо пробелов ставится значок ^, например:

i ^ = 2; {В область памяти i помещено значение 2}

r ^ = 2 * pi; {В область памяти r помещено значение 6.28}

Таким образом, значение, на которое указывает указатель, то есть собственно данные, размещенные в куче, обозначены ^, который ставится сразу за указателем. Если за указателем

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

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

r ^ = sqr (r ^) + i ^ — 17;

Разумеется, абсолютно недопустим оператор

r = sqr (r ^) + i ^ — 17;

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

Точно так же недопустимо оператор

r ^ = sqr (r)

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

Читать  Лекция Паскаль 8 – Понятие подпрограммы, Объявления подпрограммы, ее тело и оператор ее вызова

r ^ = i;

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

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

dispose (r) dispose (i)

вернут в кучу 8 байт, которые ранее были выделены указателям I и R. Отметим, что процедура DISPOSE (PTR) не изменяет значение указателя

PTR, а лишь возвращает в кучу память, ранее связанную с этим указателем.

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

const

р: ^ real = NIL;

begin

. . . . .

If р = NIL then

new (p) . . . . .

Dispose (p) p = NIL;

. . . . . End.

 

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

значение процедурой NEW или иным способом, не контролируемой системой и может привести к непредсказуемым результатам.Чередование обращений к процедурам NEW и DISPOSE обычно приводит к «воротниковой» структуры памяти.Дело в том, что все операции с ней выполняются под управлением особой подпрограммы, которая называется администратором вместе. Она автоматически пристыковывается к нашей программе компоновщиком Турбо Паскаля и ведет учет всех сводных фрагментов в купе. При очередном обращении к процедуре NEW эта подпрограмма ищет наименьший свободный фрагмент, в котором еще может разместиться необходима переменная.Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины обозначается как занята часть вместе.

Другая возможность заключается в освобождении целого фрагмента вместе. С этой целью перед началом выделения динамической памяти текущее значение указателя HEAPPTR запоминается в переменной-указателе с помощью процедуры MARK. Теперь можно в любой момент освободить фрагмент кучи, начиная от того адреса, который запомнила процедура MARК, и к концу динамической памяти.Для этого используется процедура RELEASE. Например:

 

var

p, p1, p2, р3, р4, Р5: ^ integer; begin

new (p1)

new (p2);

mark (p)

new (p3)

new (p4)

new (p5) . . . . .

Release (p) end.

 

В этом примере процедурой MARK (P) в указатель Р было помещено тукущее значение HEAPPTR, однако память под переменные не резервировалась.Обращение RELEASE (P) освободило динамическую память от обозначенного места до конца вместе.Пирведенний ниже рисунок иллюстрирует механизм работы процедур NEW-DISPOSE и NEW-MARK-RELEASE для рассматриваемого примера и для случая, когда вместо оператора RELEASE (P) используется, например, DISPOSE (P3).

а б в

Состояние динамической памяти:

а) перед увольнением; б) после Dispose (p3) в) после Release (p)

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

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

GETMEM (P, SIZE) — резервирование памяти; FREEMEM (P, SIZE) — освобождение памяти.

здесь

Р — нетипизированных указатель; SIZE — размер в байтах необходимой или звильняемои части вместе.

За одно обращение к куче процедурой GETMEM можно зарезервировать до 65521 байта динамической памяти.

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

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

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 + 2}

r ^ = pi;

Writeln (j ^) {??} End .

 

Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, Проследим по значениям указателя HEAPPTR. Перед выполнением программы этот указатель имел значение адреса начала купи HEAPORG, которое и было передано указателю I, а затем и J. После выполнения DISPOSE (I) указатель кучи снова получил значение HEAPORG, этот адрес передан указатель R в процедуре NEW (R).После того как по адресу R разместилось действительное число pi = 3.14159, первые 2 байта кучи оказались заняты под часть внутреннего представления этого числа.В то же время J все еще сохраняет адрес HEAPORG, поэтому оператор WRITELN (J ^) рассматривать 2 байта числа pi как внутреннее представление целого числа (ведь J — это указатель на тип INTEGER) и выведет 8578.

Читать  Файлы Паскаль, файловый тип, Процедуры и Функции в Pascal

Использование указателей

Подведем некоторые итоги. Итак, динамическая память составляет 200 … 300 Кбайт или более, ее начало сохраняется в переменной HEAPORG, a конец отвечает

адресу переменной HEAPEND. Текущий адрес свободного участка динамической памяти сохраняется в указателе HEAPPTR.

Посмотрим, как можно использовать динамическую память для размещения больших массивов данных. Пусть, например, нужно обеспечить доступ к элементам прямоугольной матрицы 100х200 типа EXTENDED.Для размщеения такого массива нужно память 200000 байт (100 * 200 * 10).

Казалось бы, эту проблему можно решить следующим образом:

var

i, j: integer;

PtrArr: array [1..100, 1..200] of ^ real;

begin

for i: = 1 to 100 do for j: = 1 to 200 do

new (PtrArr [i, j]); . . . . .End.

 

Теперь к любому элементу вновь динамического массива можно обратиться по адресу, например

PtrArr [1,1] ^ = 0;

if PtrArr [i, j * 2] ^> 1 then. . . . .

Вспомним, однако, что длина внутреннего представления указателя составляет 4 байта, поэтому для размещения массива PTRARR нужно 100 * 200 * 4 = 80000 байт, превышает размер сегмента данных (65536 байт), доступный, как уже отмечалось, программой для статического размещения данных.

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

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

SEG (X) — возвращает сегментный часть адреса; OFS (X) — возвращает смещение.Аргументом Х при обращении к этим функциям может служить любая переменная,

в том числе и та, на которую указывает указатель. Например, если имеем

var

р: ^ real; begin

. . . . .

New (p)

p ^ = 3.14; . . . . .End.

 

то функция SEG (P) вернет сегментный часть адреса, по которому располагается 4-байтный указатель Р, в то время как SEG (P ^) — сегмент 6-байтный участки купи, в которой хранится число 3.14.

С другой стороны, с помощью встроенной функции PTR (SEG, OFS: WORD): POINTER

можно создать значение указателя, совместим с указателями любого типа. Таким образом возможна такая последовательность действий. Сначала процедурой GETMEM из кучи забираются несколько фрагментов соответствующей длины (напомню, что за одно обращение к процедуре можно зарезервировать не более 65521 байт динамической памяти).Для рассматриваемого примера удобно резервировать фрагменты такой длины чтобы в них могли, например, разместиться строки прямоугольной матрицы, то есть 200 * 10 = 2000 байт. Начало каждого фрагмента, то есть фактически начало размещения в памяти каждой строки, запоминается в массиве PTRSTR, состоящий из 100 указателей.Теперь для доступа к любому элементу строки нужно вычислить смещение этого элемента от начала строки и сформировать соответствующий указатель:

var

i, j: integer;

PtrStr: array [1..100] of pointer; pr: ^ real; const

SizeOfReal = 6; begin for i: = 1 to 100 do

GetMem (PtrStr [i], SizeOfReal * 200); . . . . .{Обращение к элементу матрицы [i, j]}

pr = ptr (seg (PtrStr [i] ^), ofs (PtrStr [i] ^) + (j-1) * SizeOfReal) if pr ^> 1 then. . . . .End.

 

Поскольку оператор вычисления адреса PR = PTR … будет, судя по всему, использоваться в программе неоднократно, полезно ввести вспомогательную функцию GETR, что возвращает значение элемента матрицы, и процедуру PUTR, которая устанавливает новое значение элемента.Каждая из них, в свою очередь, обращается к функции ADDRR для вычисления адреса.Ниже приводится программа, которая создает в памяти матрицу с NxM случайных чисел и вычисляет их среднее значение.

program Primer1;

const

SizeOfReal = 6; {Длина переменной типа REAL}

N = 100; {Количество столбцов}

М = 200; {Показать #}

var

i, j: integer;

PtrStr: array [1 ..N] of pointer; s: real; type

RealPoint = ^ real; {——————} Function AddrR (i, j: word): RealPoint;

{По сегмента i и смещение j выдает адрес вещественной переменной} begin

AddrR = ptr (seg (PtrStr [i] ^), ofs (PtrStr [i] ^) + (j-1) * SizeOfReal)

end; {AddrR} {———- ——-} Function GetR (i, j: integer): real;

{Выдает значение действительной переменной по сегменту i и смещение j ее адреса} begin

GetR = AddrR (i, j) ^ end ; {GetR} {——————} Procepure PutR (i, j: integer; x: real)

{помещает в переменную, адрес которой имеет сегмент i смещение j, истинное значение x} begin

AddrR (i, j) ^ = x end ; {PutR} {——————} begin {Main} for

Читать  Цикл со счетчиком – Счетчик цикла

i = 1 to N do begin

GetMem (PtrStr [i], M * SizeOfReal) for j = 1 to M do PutR (i, j, Random)

end;

s: = 0; for i: = 1 to N do

for j = 1 to M do

s = s + GetR (i, j)

WriteLn (s / (N * M): 12:10) end .{Main}

 

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

 

ПРОЦЕДУРЫ И ФУНКЦИИ ДЛЯ РАБОТЫ С динамической памятью

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

Функция ADDR.

Возвращает результат типа POINTER, в котором содержится адрес аргумента. обращения:

ADDR (Х)

здесь

Х — любой объект программы (имя любой переменной, процедуры, функции).

Возвращается адрес совместим с указателем любого типа. Отметим, что аналогичный результат возвращает операция @.

Функция CSEG.

Возвращает значение, которое хранится в регистре CS микропроцессора (в начале работы программы в регистре CS содержится сегмент начала кода

программы). обращения: CSEG

Результат возвращается в слове типа WORD.

Процедура DISPOSE.

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным указателем. обращения: DISPOSE (ТР)

здесь

ТР — собирательный указатель.

При повторном использовании процедуры по уже уволенному фрагмента возникает ошибка периода выполнения. При увольнении динамических объектов можно указывать вторым параметром обращения к DISPOSE имя деструктора.

Функция DSEG.

Возвращает значение, которое хранится в регистре DS микропроцессора (в начале работы программы в регистре DS содержится сегмент начала данных

программы). обращения: DSEG

Результат возвращается в слове типа WORD.

Процедура FREEMEM.

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированного указателем. Обращение: FREEMEM (Р, SIZE)

здесь

Р — нетипизированных указатель;

SIZE — длина в байтах уволенного фрагмента.

При повторном использовании процедуры по уже уволенному фрагмента возникает ошибка периода выполнения.

Процедура GETMEM.

Резервирует за нетипизированного указателем фрагмент динамической памяти требуемого размера. обращение:

GETMEM (Р, SIZE)

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

Процедура MARK.

Запоминает текущее значение указателя кучи HEAPPTR.Обращение: MARK (PTR)

здесь

PTR — указатель любого типа, в котором будет возвращено текущее значение

HEAPPTR.

Используется совместно с процедурой RELEASE для освобождения части вместе.

Функция MAXAVAIL.

Возвращает размер в байтах самого непрерывного участка вместе. Обращение: MAXAVAIL

Результат имеет тип LONGINT.За один вызов процедуры NEW или GETMEM нельзя зарезервировать памяти больше, чем возвращаемого значения этой функцией.

Функция MEMAVAIL.

Возвращает размер в байтах общего свободного пространства вместе. Обращение: MEMAVAIL

Результат имеет тип LONGINT.

Процедура NEW.

Резервирует фрагмент кучи для размещения переменной. обращения: NEW (ТР)

здесь

ТР — собирательный указатель.

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

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

type

PInt: ^ integer; var

p: PInt; begin

p = New (PInt) . . . . .End.

 

 

При размещении в динамической памяти объекта разрешается в качестве второго параметра обращения к NEW указывать имя конструктора.

Функдия OFS.

Возвращает значение типа WORD, содержащий смещение адреса указанного объекта. вызов:

OFS (Х)

здесь

Х — выражение любого типа или имя процедуры.

 

Функция PTR.

 

Возвращает значение типа POINTER по заданному сегмента SEG и смещение OFS. вызов:

PTR (SEG, OFS)

здесь

SEG — выражение типа WORD, содержащий сегмент; OFS — выражение типа WORD, содержащий смещение.

Возвращаемого значения функции, совместно с указателем любого типа.

Процедура RELEASE.

Освобождает участок вместе. обращения: RELEASE (PTR)

здесь

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

Освобождается участок кучи от адреса, хранящегося в PTR, до конца вместе.Одновременно уничтожается список всех свободных фрагментов, которые, возможно, были созданы процедурами DISPOSE или FREEMEM.

Функция SEG.

Возвращает значение типа WORD, содержащий сегмент адреса указанного объекта. вызов:

SEG (Х)

здесь

Х — выражение любого типа или имя процедуры.

Читать  Конспект лекций Паскаль для 1 курса заочного отделения – Методические рекомендации к выполнению контрольных работ

Функция SIZEOF.

Возвращает длину в байтах внутреннего представления указанного объекта.

Вызов: SIZEOF (Х)

здесь

Х — имя переменной, функции или типа.

Например, везде в программе Primer1 вместо константы SIZEOFREAL можно было бы использовать обращение SIZEOF (REAL).

 

АДМИНИСТРАТОР КУПИ

Как уже отмечалось, администратор кучи — это служебная подпрограмма, которая обеспечивает взаимодействие пользователя программы с кучей. Администратор кучи обрабатывает запросы процедур NEW, GETMEM, DISPOSE, FREEMEM и др.И меняет значение указателей HEAPPTR и FREELIST. Указатель HEAPPTR содержит адрес нижней границы свободной части вместе, а указатель FREELIST — адрес описателя первого свободного блока.В модуле SYSTEM указатель FREELIST описан как POINTER, однако фактически он указывает на следующую структуру данных:

type

PFreeRec = ^ TFreeRec; TFreeRec = record

Next: pointer; Size: pointer end ;

 

Эта учетная структура предназначена для описания всех свободных блоков памяти, которые расположены ниже границы HEAPPTR.Происхождение блоков связано со случайной последовательностью использования процедур NEW-DISPOSE или GETMEM-FREEMEM ( «ячеистая» структура кучи).Поле NEXT в записи TFREEREC содержит адрес описателя следующего по списку свободного блока кучи или адрес, совпадающий с HEAPEND, если этот участок последний в списке.Поле SIZE содержит ненормализованных длину свободного блока или 0, если ниже адреса, содержащегося в HEAPPTR, нет свободных блоков.Ненормализованных длина определяется так: в старшем слове этого поля содержится количество свободных пунктов, а в младшем — количество свободных байт в диапазоне 0 … 15. Следующая функция превращает значение поля SIZE в фактическую длину свободного блока:

Function BlockSize (Size: pointer): Longint;

{Функция превращает ненормализованных длину свободного блока в байты}

type

PtrRec = record

Lo, Hi: word end;

var

LengthBlock: Longint; begin

BlockSize = Longint (PtrRec (Size).Hi) * 16 + PtrRec (Size).Lo end;

 

 

Сразу после загрузки программы указатели HEAPPTR и FREELIST содержат один и тот же адрес, который совпадает с началом кучи (этот адрес содержится в указателе HEAPORG).При этом в первых 8 байтах кучи сохраняется запись, соответствующую типу TFREEREC (поле NEXT содержит адрес, совпадающий со значением HEAPEND, а поле SIZE — ноль, что служит дополнительным признаком отсутствия «ячеек» динамической памяти).При работе с кучей указатели HEAPPTR и FREELIST иметь одинаковые значения до тех пор, пока в куче не образуется хотя бы один свободный блок ниже границы, содержится в указателе HEAPPTR. Как только это произойдет, указатель FREELIST станет ссылаться на начало этого блока, а в первых 8 байтах

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

Описанный механизм раскрывает один не очень существенный недостаток, связанный с работой администратора кучи, а именно: в любой уволился блок администратор должен поместить описатель этого блока, а это значит, что длина блока не может быть менее 8 байтов. Администратор кучи всегда выделяет память блоками, размер которых кратен размеру записи TFREEREC, то есть кратный 8 байт.Даже если программа запросит 1 байт, администратор выделит ей фактически 8 байт. Те же 8 байт будут выделены при запросе 2, 3, …, 8 байт; при запросе 9 байт будет выделен блок в 16 байт и т.д. Это обстоятельство следует учитывать, если Вы хотите минимизировать возможные потери динамической памяти. Если запрашиваемый размер не кратен 8 байт, в сочетании образуется «дыра» размером от 1 до 7 байт, причем она не может использоваться ни при каком другом запросе динамической памяти до того момента, когда связанная с ней переменная не будет удалена из кучи.

Если при очередном обращении к NEW или GETMEM администратор не может найти в куче нужен свободный блок, он обращается к функции, адрес которой содержит переменная HEAPERROR. Эта функция соответствует следующему процедурного типа:

type

HeapErrorFun = function (Size: word): integer;

здесь

SIZE — размер той переменной, для которой нет свободной динамической памяти.

Стандартная функция, адрес которой при запуске программы содержит переменная HEAPERROR, возвращает 0, что приводит к останову программы ошибочно периода счета с кодом 203.Вы можете переопределить эту функцию и таким образом блокировать останов программы. Для этого необходимо написать собственную функцию и поместить ее адрес в указатель HEAPERROR. Например:

Function HeapFunc (Size: Word): integer; far;

begin

HeapFunc = 1 end;

begin {Основная программа}

HeapError = @HeapFunc; . . . . .End.

 

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

— прекратить работу программы;

1 — присвоить соответствующему указателю значение NIL и продолжить работу программы;

2 — повторить выделение памяти; разумеется, в этом случае внутри функции типа HEAPERRORFUN необходимо освободить память нужного размера.

 

По материалам http://klax.tula.ru/~zet/themes/pointers.html#procsandfuncs 08.03.2016

 

[Всего голосов: 4    Средний: 5/5]