Теория
В прошлый раз я рассказывал об устройстве памяти. Сегодня мы научимся использовать эту самую память. В паскале существует специальный тип - указатели. Переменная указатель - это ссылка на данные или код. Указатель это вам не это :) Значение переменной указателя - это адрес памяти. А что распологается по этому адресу решать вам.
Но сначала несколько общих слов и соображений. Во первых некоторые сокращения: RAM (Random Access Memory) - память общего доступа, т.е. память с которой мы можем делать всё, что захотим. ROM (Read Only Memory) - память только для чтения, в неё мы не можем ничего записать. Ещё одно соображение: мы пишем программы для операционной системы DOS. В Windows большинство DOS программ работает нормально. Однако в WinNT, 2k, XP на 99% уверен, что возникнут проблеммы с работой программ, приведённых ниже. Всё дело в том, что Windows не "любит" когда кто-то работает с памятью, а ему об этом не слова. Поэтому он ограничивает работу таких программ, давая им уверенноть, что они по работали с памятью, а на самом деле ничего такого не произошло. Так что если что-то не сработает, не надо сразу завалить меня гневными письмами. Единственный выход из такого положения: создать загрузочную дискетку с DOS'ом и загрузившись с неё запускать программы.
Для получения адреса переменной вам нужно указать перед её именем знак "собака" - @. Т.е. если
i : integer;
то @i - это адрес переменной i.
Для объявления указателя нам надо создать переменную соответствующего типа. Для начала мы рассмотрим самый простой тип указателей - безтиповые указатели :) Они могут указывать (т.е. хранить адрес) чего угодно. Хотите адрес переменной - пожалуйста, адрес записи - с легкостью, процедуры - ни каких проблем. Имя у этого типа - Pointer (англ. указатель).
Напишем такую программку:
var
i : integer;
p : pointer;
begin
i := 3;
p := @i
end.
а теперь вопрос, что содержится в p (после выполнения программы) ? Ответ адрес переменной i. Если вы посмотрите на указатель p в отладчике, то вы увидите, что он равен Ptr($13C1,$50) (надо довести выполнение программы до полного завершения). Что это такое? Для работы с адресами в паскале есть функция:
function Ptr(Seg, Ofs: Word): Pointer;
она преобразовывает адрес сегмента (Seg) и смещения (Ofs) к указателю. Знак доллара - "$" - перед числом означает, что оно шеснадцатеричное. это специфическое обозначение паскаля. Я при объяснении буду использовать букву h, а в программах мы увидим знак $. Просто это одно и тоже 10h = $10 = 16. Если вы слабо владеете этой системой - обязательно прочитайте выпуск #2 Системы счисления ч1 Шестнадцатеричная
Так вот Ptr($13C1,$50) означает, что наш указатель указывает на область памяти с сегментом 13С1h и смещением в этом сегменте 50h. Поменяем местами объявления p и i.
var
p : pointer;
i : integer;
begin
i := 3;
p := @i
end.
Что же тогда произойдёт? Казалось бы ничего такого - просто поменяли местами объявления двух переменных, что от этого может изменится ? Оказывается может! Давайте посмотрим на это дело в отладчике... действительно p теперь равен Ptr($13C1,$54). Почему такое произошло ? Мы перераспределяем память в сегменте данных. Т.е. в первом случае у нас сначала в памяти отводится место под переменную i, а потом идёт указатель. Во втором случае память сначала выделяется под указатель, а только потом под i. Поэтому смещение в обоих случаях разное.
Указатель без типа хорошо, ну а с типом лучше! Для использования таких указателей лучше создать новый тип. Перед именем того типа, на который наш указаетль указывает нужно поставить символ каре - "^".
type
pinteger = ^integer;
Теперь мы можем создать указатель на переменную типа integer. И присвоить указателю её адрес.
var
i : integer;
p : pinteger;
begin
i := 3;
p := @i;
Однако наверняка вам хочется знать, что же расположено по адресу на который указывает p ?? Для этого существует приём, который называется "разыменование указателей". Суть его в том, что разыменовав указатель вы можете работать с ним так, как если бы это была переменная того типа, на который он указывает. Чтобы разыменовать указатель, поместите ^ после его имени.
begin
i := 3;
p := @i;
write (p^);
выдаст на экран значение 3. Используя символ ^ мы указываем, что надо использовать указатель не как адрес, а как то что содержится по этому адресу. На что указывает p ? На адрес переменной i. Т.е. изменив i мы изменим значение по адресу p. А изменив p, мы получим в p указатель на неизвестно что.
При работе программы используются, не имена переменных, а их адреса. Так как в р у нас адрес i, то изменив значение по этому адресу (по адресу в р) мы изменим значение i. А изменив значение i, мы изменим только значение 2-х байтов по адресу р (и соответственно изменим p^).
Например чему будет равно i после такого:
begin
i := 3;
p := @i;
p^ := 123;
Ответ i = 123. Как? Ведь мы i нигде не изменяем ??
Что делает эта строчка p^ := 123; - она помещает по адресу p значение 123. А адрес р равен адресу i.
Для нашего случая условно говоря p^ = i !!!
Однако поменяв адрес p на что-то ещё p^ уже будет не равно i !!!
Важно вбить себе в голову, что указатель (р) - это только адрес, по которому может распологаться всё, что угодно. А p^ - это значение по этому адресу.
На один и тот же адрес может указывать бесконечное число указателей. Например такой код:
var
i : integer;
p1, p2 : pinteger;
begin
i := 3;
p1 := @i;
p2 := p1;
p2^ := 123;
writeLn (i)
end.
Опять тот же вопрос: чему равно i ? Ответ i = 123. Давайте разберёмся построчно и с отладчиком. Кстати в отладчике вместо адреса сегмента данных указывается Dseg (от англ DateSegment - сегмент данных). Это результат такой функции function DSeg: Word; Она возвращает адрес сегмента данных.
Итак:
- i := 3; - смотрим в отладчике: i = 3; p1, p2 = nil. Что это за nil чуть позже и ниже :).
- p1 := @i; - присваиваем р1 адрес i. ( i = 3; p1 = Ptr(DSeg,$52); p2 = nil)
- p2 := p1; - присваиваем р2 значение р1, т.е. присваиваем р2 адрес i (i = 3; p1, p2 = Ptr(DSeg,$52))
- p2^ := 123; - помещаем по адресу p2 значение 123. А что у нас за адрес в р2 ? Адрес i. Значит мы изменяем значение по этому адресу, т.е. значение i (i = 123; p1, p2 = Ptr(DSeg,$52))
Вернёмся к nil. Когда указатель равен nil это означает, что он указывает ни на что. Т.е. не проинициализированный указатель.