Работа с динамическими переменными и указателями

Free Pascal имеет гибкое средство управления памятью – указатели.

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

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

type massiv=array [1..2500] of real;

var a:^integer; b,c:^real; d:^massiv;

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

var p,c,h: pointer;

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

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

Например:

Var

p1, p2:^integer; p3:^real; pp:pointer;

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

Вся динамическая память во Free Pascal представляет собой сплошной массив байтов, называемый кучей. Физически куча располагается за областью памяти, которую занимает тело программы. Начало кучи хранится в стандартной переменной heaporg, конец – в переменной heapend. Текущая граница незанятой динамической памяти хранится в указателе heapprt.

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

var

i,j:^integer;

r:^real;

begin

new(i);

new(R);

new(j)

В результате выполнения первого оператора указатель i принимает значение, которое перед этим имел указатель кучи heapprt. Сам heapprt увеличивает свое значение на 4, так как длина внутреннего представления типа integer, связанного с указателем i, составляет 4 байта. Оператор new(r) вызывает еще одно смещение указателя heapprt, но уже на 8 байтов, потому что такова длина внутреннего представления типа real. Аналогичная процедура применяется и для переменной любого другого типа. После того как указатель стал определять конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа, для чего сразу за указателем без каких-либо пробелов ставится значок ^, например:

i^:=4+3;

j^:=17;

r^:=2 * pi;

Таким образом, значение, на которое указывает указатель, то есть собственно данные, размещенные в куче, обозначаются значком ^. Значок ^ ставится сразу за указателем. Если после указателя значок ^ отсутствует, то имеется в виду адрес, по которому размещаются данные. Динамически размещенные данные (но не их адрес!) можно использовать для констант и переменных соответствующего типа в любом месте, где это допустимо, например:

r^:=sqr(r^)+sin(r^+i^)-2.3

Невозможен оператор

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

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

r^:=sqr(r);

поскольку значением указателя r является адрес, и его (в отличие от того значения, которое размещено по данному адресу) нельзя возводить в квадрат. Ошибочным будет и присваивание r^:=i, так как вещественным данным, на которые указывает r^, нельзя давать значение указателя (адрес). Динамическую память можно не только забирать из кучи, но и возвращать обратно. Для этого используется процедура dispose(p), где р – указатель, который не изменяет значение указателя, а лишь возвращает в кучу память, ранее связанную с указателем.

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

Другая возможность состоит в освобождении целого фрагмента кучи. С этой целью перед началом выделения динамической памяти текущее значение указателя heapptr запоминается в переменнойуказателе с помощью процедуры mark. Теперь можно в любой момент освободить фрагмент кучи, начиная с того адреса, который запомнила процедура mark, и до конца динамической памяти. Для этого используется процедура release.

Процедура mark запоминает текущее указание кучи heapptr (обращение mark(ptr), где ptr – указатель любого типа, в котором будет возвращено текущее значение heapptr). Процедура release(ptr), где ptr – указатель любого типа, освобождает участок кучи от адреса, хранящегося в указателе до конца кучи.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: