Типизированные и нетипизированные указатели
Думаю, вы уже знаете, что указатели могут быть типизированными и нетипизированными.
Типизированный указатель содержит ссылку на область памяти, где хранятся данные определенного типа (byte, real и т.п.).
Нетипизированные указатели содержат ссылку на область памяти, где могут храниться любые данные, поэтому их (нетипизированные указатели) удобно использовать в тех случаях, когда требуется работать с данными, структура и тип которых меняются в ходе выполнения программы (и/или заранее неизвестны).
Сами указатели хранятся в статической области памяти, поэтому их нужно объявлять, как и другие переменные.
При использовании нетипизированных и типизированных указателей надо быть внимательным, поскольку здесь легко можно совершить труднонаходимую ошибку. Например, если у нас в программе явно определены следующие типизированные указатели:
var P1 : ^integer; //Типизированный указатель
P2 : ^single; //Типизированный указатель
P3 : pointer; //Нетипизированный указатель
то такой код:
P1 := P2;
вызовет ошибку во время компиляции и программа не будет создана. Однако компилятор можно обмануть (сознательно или неосознанно), например, так:
P3 := P2; P1 := P3;
то есть мы таки записали в указатель Р1 значение указателя Р2. Кстати, можете взять этот приём на вооружение - в некоторых редких случаях это может быть необходимо. Однако в большинстве случаев это является ошибкой (хотя компилятор её не заметит и программа будет создана). Эту ошибку показывает следующий пример:
program myprog;
var iNum : integer; //Переменная целого типа
fNum : single; //Переменная вещественного типа
P1 : ^integer; //Типизированный указатель
P2 : ^single; //Типизированный указатель
P3 : pointer; //Нетипизированный указатель
begin
iNum := 100;
fNum := 100;
WriteLn('iNum = ', iNum); //Выведет 100
WriteLn('fNum = ', fNum:0:1); //Выведет 100.0
P1 := @iNum; //Р1 = адрес переменной iNum
P2 := @fNum; //Р2 = адрес переменной fNum
WriteLn('iNum = ', P1^); //Выведет 100
//Это не вызовет ошибки, однако так делать опасно,
//потому что теперь в память по адресу, где должна храниться
//целочисленная переменная, записаны данные вещественного типа,
//и искажение данных гарантировано
P3 := P2;
P1 := P3;
//Выведет 1120403456 или что-то типа того
iNum := P1^;
WriteLn('iNum = ', iNum);
ReadLn;
end.
Здесь, во-первых, мы разрываем связь между указателем Р1 и переменной iNum. Ну это ладно, это не страшно. Давайте посмотрим, что же мы натворили:
- Адрес переменной fNum, который хранился в указателе Р2, мы поместили в нетипизированный указатель Р3. При этом помним, что в переменной fNum у нас хранится вещественное число 100.
- Далее в указатель Р1 мы записали значение указателя Р3. То есть таким образом мы в указатель Р1 записали адрес переменной fNum.
- И теперь указатель Р1 содержит ссылку на место в памяти, где хранится число 100.
- Однако, если мы получим данные, на которые ссылается указатель Р1, и сохраним их в переменную iNum, то это будет не 100, а какое-то непонятное число.
Таким образом мы получили “искажённую картину мира”, то есть наши ожидания не оправдались и мы не смогли преобразовать через указатели вещественное число в целое. А всё потому, что представление целых и вещественных чисел в памяти компьютера совершенно разное. И не зря компилятор ругается, когда мы пытаемся напрямую присвоить значение типизированного указателя одного типа указателю другого типа.
Вот такие (и другие) неприятности поджидают вас в работе с указателями. Если хотите изучить тему указателей более глубоко, то вам сюда.