Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Структуры и алгоритмы обработки данных.doc
Скачиваний:
348
Добавлен:
12.03.2015
Размер:
1.81 Mб
Скачать

1.2. Переменные-указатели и динамическое распределение памяти.

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

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

Механизм динамического распределения памяти основан на использовании специальных переменных, которые называют указателями. Переменные-указатели или переменные ссылочного типа - это специальные переменные, значениями которых являются адреса областей памяти. Каждый указатель может адресовать некоторую область памяти, в которой могут находиться любые данные. Чаще всего под адрес отводится 4 байта. Необходимо четко различать переменную-указатель и адресуемую ею область памяти.

значение указателя: адрес области памяти 00 00 4А F0

Адресуемые данные (массивы, записи), располагающиеся в памяти начиная с адреса 00 00 4A F0

Для описания переменных-указателей необходимо:

  • ввести имя переменной

  • связать эту переменную с адресуемыми данными, указав их тип или имя с помощью специального символа ^ :

var указатель : ^ базовый тип;

Например, указатели на простейшие базовые типы вводятся следующим образом:

pInt : ^integer; {указатель на отдельное целое число}

pChar : ^char; {указатель на отдельный символ}

pString1, pStr2 : ^string; {два указателя на символьные строки}

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

type TpString= ^string; {ссылочный тип для адресации текстовых строк}

Var pStr1,pStr2 :TpString; {переменные-указатели на строки}

Можно ввести указатели на структурные типы (массивы, записи), используя для этого предварительно объявленные типы:

type TMyArray = array [1..100] of integer;

var pMyArray1 : ^TMyArray; {указатель на начало базового массива}

Необходимо понимать, что объявление переменной-указателя НЕ приводит к выделению памяти для самих адресуемых данных: память выделяется ТОЛЬКО для указателя (например – 4 байта). Поэтому для правильного использования механизма динамического распределения памяти НЕ следует объявлять переменные базового типа (точнее, их объявлять можно, но к динамическому распределению памяти это никакого отношения не имеет).

Например:

type TMyRecord = record

Field1 : SomeType1;

Field2 : SomeType2;

end;

var MyRec : TMyRecord; {обычная переменная-запись со статическим выделением памяти}

pMyRec : ^TMyRecord; {переменная-указатель на будущую структуру-запись с выделением памяти только для указателя, но не для самой записи!}

Динамическое создание переменных базового типа выполняется вызовом стандартной подпрограммы New с параметром-указателем, связанным с данным базовым типом:

New ( pMyRec );

New (pStr1 );

Данный вызов выполняет два следующих действия:

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

  • устанавливает начальный адрес этой области памяти в качестве значения переменной-указателя

Вызов New можно выполнять в любом месте в теле программы любое число раз, в том числе – циклически. Это позволяет при выполнении программы динамически создавать любое необходимое число переменных базового типа.

После выделения области памяти в неё можно записать необходимое значение, используя ссылочную переменную и символ ^ после ее имени:

pStr1^ : = ‘Привет’;

pInt^ : = 1;

Комбинация имени переменной-указателя и символа ^ превращает указатель в переменную базового типа. С такой переменной можно делать все те операции, которые разрешены для базового типа. Например:

pInt^ : = pInt^ + 1;

pChar^ : = ‘A’;

ReadLn ( pStr1^);

pMyArray^[ i ] : = 10;

pMyRec^.Field1 : = соответствующее значение;

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

  • pMyArray и pMyRec – это имена ссылочных переменных

  • pMyArray^ и pMyRec^ - это условные обозначения объектов базового типа, т.е. массива и записи соответственно

  • pMyArray^[ i ] и pMyRec^.Field1 – это обозначения отдельных элементов базового типа, т.е. i-го элемента массива и поля записи с именем Field1

C переменными ссылочного типа допустимы две операции:

1. Присваивание значения одной ссылочной переменной другой ссылочной переменной того же базового типа:

pInt1 : = pInt2;

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

2. Сравнение двух ссылочных переменных на равенство или неравенство:

if pInt2 = pInt1 then …

В практических задачах довольно часто приходится использовать “пустые указатели”, которые никуда не указывают. Для задания такого пустого указателя используется специальное служебное слово nil. Например:

pInt1 : = nil; {указатель ничего не адресует}

if pStr1 = nil then . . .{проверка указателя на пустоту}

Для освобождения динамически распределенной памяти используется вызов Dispose с параметром-указателем:

Dispose ( pStr1 );

После этого вызова соответствующая переменная-указатель становится неопределенной (НЕ путать с ПУСТЫМ указателем!) и ее нельзя использовать до тех пор, пока она снова не будет инициализирована с помощью вызова New или инструкции присваивания. Использование вызова Dispose может приводить к весьма неприятной ситуации, связанной с появлением так называемых “висячих” указателей: если два указателями адресовали одну и ту же область памяти (а это встречается очень часто), то вызов Dispose с одной из них оставляет в другой переменной адрес уже освобожденной области памяти и повторное использование этой переменной приведет либо к неверному результату, либо к аварийному завершению программы.