Содержание
Присваивания
Для указателей действуют гораздо более жёсткие правила совместимости типов, чем для обычных переменных. В операции присваивания могут фигурировать только указатели, адресующие переменные одинаковых типов данных. Нельзя, скажем, записать
Обойти эти ограничения позволяет универсальность нетипизированного указателя Pointer, совместимого с указателями любых типов:
t := q;
p := t;
У указателей также существует свой «ноль», который означает, что указатель не указывает никуда:
Замечание: Если указатель не хранит конкретного адреса (его значение не определено), то это вовсе не означает, что он никуда не указывает. Скорее всего, он всё–таки указывает, но только в какую–нибудь неожиданную (хорошо, если не системную) область памяти.
Сравнения
Для указателей определены две операции сравнения: = и <>.
Две переменные, описанные как указатели на один и тот же тип данных, считаются совпадающими, если они указывают на одну и ту же область памяти.
Для разнотипных указателей сравнения невозможны: попытка записать
if p = q then
WriteLn('yes');
вызовет ошибку уже на этапе компиляции.
Однако сравнивать типизированный и нетипизированный указатели можно.
Динамически распределяемая память
Поскольку к любой переменной можно обратиться двояко — по имени и по адресу, — есть возможность сократить эту избыточность и оставить только один способ. Как мы уже видели, наличие имени означает и наличие адреса. Иными словами, если вы дали переменной имя в разделе var, то в процессе компиляции у неё появится и адрес.
Задумаемся теперь: а если у переменной есть адрес, но нет имени, можно ли оперировать ею с прежней легкостью? Ответ на этот вопрос: «Да, можно!»
Итак, пусть у некоторой переменной нет имени. Тем не менее, можно расположить её в памяти, выделив под неё необходимое количество байтов, и т.д. У переменной будет адрес, будет значение, но не будет имени. Следовательно, обратиться к такой переменной можно будет только с помощью указателя.
«Безымянные» переменные отличаются от «нормальных» переменных:
- Нет имени — нечего описывать в разделе var.
- Ничего не описано, значит, на этапе компиляции память под переменную не выделена. Следовательно, необходима возможность выделять память (и отменять это выделение) прямо в процессе работы программы. Именно из–за этой гибкости такие переменные и называют динамическими.
- Если «потерять» указатель на переменную, то «никто не узнает, где могилка» её: переменная останется недоступным «мусором», занимая место в памяти, вплоть до конца работы программы.
Динамическое выделение памяти
Типизированные указатели
Для выделения памяти служит стандартная процедура New():
New(<имя_указателя>);
Эта процедура ищет в незанятой памяти подходящий по размеру кусок и, «застолбив» это место для безымянной динамической переменной, записывает в типизированный указатель адрес выделенного участка. Поэтому часто говорят, что процедура New() создаёт динамическую переменную. Размер выделяемого «куска памяти» напрямую зависит от типа указателя.
Например, если переменная p была описана как указатель на Integer–переменную, то процедура New(p) выделит два байта; под Real–переменную необходимо выделить шесть байт и т. д.
Нетипизированные указатели
Для того, чтобы выделить память, на которую будет указывать нетипизрованный указатель Pointer, нужно воспользоваться стандартной процедурой GetMem(p : Pointer; size : Word), которая выделит столько байт свободной памяти, сколько указано в переменной size.
Динамическое освобождение памяти
Типизированные указатели
Для уничтожения динамической переменной, то есть для освобождения занимаемой ею памяти, предназначена стандартная процедура
Dispose(<имя_типизир_указателя>)
Процедура Dispose() снимает пометку «занято» с определённого количества байтов, начиная с указанного адреса. Эта область памяти в дальнейшем считается свободной (хотя старое значение бывшей переменной в ней может некоторое время ещё оставаться). Количество освобождаемых байтов определяется типом указателя p.
В результате освобождения памяти при помощи процедуры Dispose() значение указателя, хранившего адрес освобождённой области, становится неопределённым. Во избежание проблем его лучше сразу же «обнулить»:
p := nil;
Нетипизированные указатели
Для того, чтобы освободить память, на которую указывает нетипизрованный указатель, нужно воспользоваться стандартной процедурой FreeMem(p : Pointer; size : Word), которая освободит в памяти столько байтов (начиная с указанного в переменной p адреса), сколько задано в переменной size.
Списочные структуры
Если для каждой динамической переменной описывать и хранить её «личный» указатель, то никакой выгоды на этапе выполнения программы получить не удастся: часть памяти, как и прежде, будет выделяться статически, а её общий объем даже увеличится — ведь каждый указатель требует для себя четыре байта.
Следовательно, нужно сделать так, чтобы место под хранение адресов будущих переменных также выделялось динамически. Решением этой проблемы и служат списки — специальные динамические структуры.
Списки применяются, например, в таких ситуациях:
- программист заранее ничего не знает о том, какой именно объём памяти может потребоваться его программе;
- некоторые (особенно «тяжёлые») переменные нужны поочередно, и после того как первые «отработали своё», их можно смело стирать из памяти, не дожидаясь конца работы программы, — освобождать место для других «тяжёлых» переменных;
- в процессе обработки данных нужно провести большую работу по перестройке всей структуры «на ходу»; и т.д.