- •Лекция. Указатели Лекция. Указатели.
- •Указатели и динамическая память
- •2 Элемент списка
- •Операция взятия адреса @»
- •Сравнение указателей
- •Динамическая память.
- •Адресация памяти в компьютере
- •Выделение и освобождение динамической памяти.
- •Процедуры и функции для работы с динамической памятью.
- •9 ©Кащей Владимир Васильевич, миэт, каф. Иувс, тел. (095) 532-9882
Операция взятия адреса @»
Операция @ создает указатель на переменную.
Операция |
Действие |
Тип операндов |
Тип результата |
@ |
Получение указателя |
Ссылка на переменную, процедуру или идентификатор функции |
указатель, совместимый с Nil |
Сравнение указателей
Над указателями определены только операции присваивания и сравнение на = и <>. Два указателя равны только в том случае, если ссылаются на один и тот же объект.
Примечание: При сравнении указателей в Турбо-паскале просто сравниваются сегменты и смещения. Но! Два логически различных указателя могут фактически указывать на одну и ту же физическую ячейку. Например, $0040:$0049 и $0000:$0449 указывают на один и тот же адрес. Указатели, возвращаемые стандартными процедурами New и GetMem всегда нормализованы (смещение всегда находится в диапазоне от $0000 до $000F) и сравниваются всегда корректно. При создании указателей с помощью функции Ptr и последующем сравнении таких указателей нужно принимать во внимание указанный особый случай.
Значение типа указатель хранится в виде двойного слова: смещение - в младшем, сегмент - в старшем слове… Значение Nil хранится в виде двойного слова, заполненного нулями.
Динамическая память.
Динамическая память – область ОЗУ, предоставляемая программе на турбо-паскале при ее работе за вычетом сегмента данных (64К), стека (обычно 16К) и собственно программы. Обычно это 200-300К и более.
Таким образом, динамическая память – фактически единственная возможность обработки массивов данных большой размерности.
При разработке САПР размерность математических моделей может значительно отличаться в разных проектах. Статическое распределение памяти при этом невозможно.
Для временного запоминания данных при работе с графикой и звуком.
Определение. Динамическое размещение данных – использование динамической памяти непосредственно при работе программ. Статическое размещение выполняет компилятор при компиляции программы. При этом области памяти можно присвоить имя.
При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных. К ним нельзя обращаться по имени, как к статическим переменным.
Адресация памяти в компьютере
Адрес в компьютере – это совокупность сегмента и смещения (2 шестнадцатиричных слова).
Сегмент – участок памяти размером 64К, начинающийся с физического адреса, кратного 16 (то есть 0, 16, 32, 48 и так далее).
Смещение – указывает, сколько байтов от начала сегмента нужно пропустить, чтобы попасть на нужный адрес.
Фрагмент памяти 16 байт называется параграфом. Сегменты могут идти в памяти непрерывно, с промежутками, накладываясь друг на друга. Каждому сегменту соответствует непрерывная и отдельно адресуемая область памяти.
Выделение и освобождение динамической памяти.
Вся динамическая память в турбо-паскале рассматривается как подобная стеку структура, называемая кучей (Heap). Физически куча располагается в старших адресах, сразу за областью памяти для загрузки оверлеев.
Адрес начала кучи хранится в переменной HeapOrg, конец – в FreePtr, текущая граница не занятой динамической памяти хранится в HeapPtr.
Для выделения памяти под любую переменную используется процедура New, единственным параметром которой является типизированный указатель.
Пример:
Var
I,j : ^Integer;
R : ^Real;
Begin
New(I); New(R);
После выполнения New(I) указатель I приобретает значение, которое имел перед этим текущий указатель границы HeapPtr. Сам указатель HeapPtr увеличивает свое значение на 2, так как длина внутреннего представления типа Integer равна 2 байтам. New(R) вызовет смещение на 6 байт.
После того как указатель приобрел конкретное значение, то есть стал указывать на конкретный физический адрес, по этому адресу можно разместить значение соответствующего типа, например,
I^:=2;
R^:=2*Pi;
Допускается, например, R^:=Sqr(R^)+I^-17. Но совершенно недопустимо: R:= Sqr(R^)+I^-17, так как указателюR нельзя присвоить значение вещественного выражения.
Динамическую память можно (и рекомендуется) возвращать в кучу: Dispose(R); Dispose(I) – вернет в кучу 8 байт, ранее взятых.
Процедура Dispose(Ptr) не изменяет значения указателя Ptr, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако, повторное применение процедуры к «свободному» указателю приведет к возникновению ошибки периода исполнения. Чтобы пометить освободившийся указатель, можно использовать Nil.
Const
P:^Real=Nil;
…….
If P=Nil then
New(P);
…
Dispose(P);
P:=Nil;
Следует учесть, что начальное значение указателя при его объявлении в разделе переменных может быть произвольным. Использование указателей, которым не присвоено значение процедурой New или каким-либо другим способом, никак не контролируется системой и может привести к непредсказуемым результатам.
Чередование New и Dispose обычно приводит к «ячеистой» структуре памяти.
Существует еще одна возможность для освобождения целого фрагмента кучи. Перед выделением памяти текущее значение указателя HeapPtr запоминается с помощью процедуры Mark. Теперь можно в любой момент освободить фрагмент кучи от этого адреса до конца кучи с помощью процедуры Release.
Var
P, P1, P2, P3, P4, P5:^Integer;
Begin
P1^ |
P2^ |
P3^ |
P4^ |
P5^ |
|
|
New(P2);
Mark(P);
New(P3);
New(P4);
New(P5);
…
Release(P);
P1^ |
P2^ |
|
P4^ |
P5^ |
|
|
D
ispose
(P3)
P1^ |
P2^ |
|
|
|
|
|
R
elease(P)
Следует учесть, что вызов Release уничтожает список свободных фрагментов в куче, созданных до этого процедурой Dispose поэтому совместное использование Dispose и Release в одной программе не рекомендуется.
Для нетипизированных указателей:
GetMem(P,Size) – резервирование памяти
FreeMem(P,Size) –освобождение памяти
P –нетипизированный указатель
Size – размер запрашиваемой или освобождаемой кучи в байтах.
При работе с GetMem и FreeMem требуется осторожность: освобождать нужно ровно столько памяти, сколько было запрошено
Применение нетипизированных указателей открывает широкие возможности для неявного преобразования типов, что не всегда полезно.
Некорректное использование 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 }
R^:=Pi;
WriteLn(J^);
End.
Что будет выведено на экран дисплея? На экране появятся символы 8575, так как J^ будет рассматривать только 2 байта внутреннего представления числа Pi и будет интерпретировать их как представление целого числа.
