Методические указания для студентов 1 курса факультета математики, механики и компьютерных наук

Вид материалаМетодические указания

Содержание


1.2Неявные указатели
1.3Указатели pointer
Подобный материал:
1   2   3   4   5   6   7   8

1.2Неявные указатели


Указатели неявно встречаются во многих конструкциях языка программирования. Например, при передаче параметра по ссылке в подпрограмму на самом деле передается указатель. Сравним две реализации одной процедуры:

procedure Mult2(var i: integer);

begin

i:=i*2;

end;

procedure Mult2P(pi: pinteger);

begin

pi:=pi*2;

end;

var a: integer;

begin

a:=3;

Mult2(a);

Mult2P(@a);

...

Код, генерируемый для таких процедур, практически идентичен: в обоих случаях в процедуру передается адрес переменной, которую следует удвоить. Однако пользоваться первой версией процедуры с параметром, передаваемым по ссылке, гораздо удобнее: в теле процедуры не надо разыменовывать указатель и при вызове процедуры в качестве параметра надо указывать саму переменную, а не ее адрес. Из данного примера видно, что параметр, передаваемый по ссылке, можно трактовать как указатель, который при использовании неявно разыменовывается.

Другой пример неявных указателей – процедурные переменные. Процедурная переменная хранит адрес процедуры или функции с соответствующей сигнатурой, либо же значение nil (напомним, что сигнатура подпрограммы определяется ее заголовком и включает количество и типы ее параметров, а для функций также и тип возвращаемого значения). Для присваивания процедурной переменной a адреса подпрограммы p с соответствующей сигнатурой знак операции @ использовать необязательно: записи a:=@p и a:=p равнозначны. Например:

type proc = procedure (i: integer);

func = function: real;

var a: proc;

b: func;

procedure p(i: integer);

begin

...

end;

function f: real;

begin

...

end;

begin

a:=@p;

b:=f; // равноценно b:=@f

a(5); // вызов процедуры через процедурную переменную a

writeln(b); // вызов функции через процедурную переменную b

end.

1.3Указатели pointer


Бестиповые указатели pointer хранят адрес памяти, не связанный с объектом определенного типа, и не могут быть разыменованы. Чтобы воспользоваться данными по этому адресу, бестиповой указатель следует преобразовать к указателю на конкретный тип. Например:

type pinteger = integer;

preal =real;

var i: integer;

r: real;

p: pointer;

begin

p:=@i;

pinteger(p):=5;

writeln(pinteger(p));

p:=@r;

preal(p):=3.14;

writeln(preal(p));

end.

Рассмотрим запись pinteger(p) подробнее. Здесь перед доступом к данным по указателю p мы вначале преобразуем его к указателю на integer, а потом разыменовываем. Поскольку перед обращением к pinteger(p) было выполнено присваивание p:=@i, то выражение pinteger(p) становится синонимом имени i и может быть использовано как в левой, так и в правой части оператора присваивания.

Гибкость указателей pointer имеет обратную сторону: их применение потенциально опасно и может приводить к ошибкам, причину которых сложно установить. Например, в результате выполнения кода

p:=@i;

preal(p):=3.14;

мы обратимся к участку памяти, по которому расположено значение целой переменной i, как к вещественной переменной. Поскольку данные вещественного типа занимают в памяти 8 байт (в Delphi), а данные целого типа – всего 4 байта, то последнее присваивание не только изменит 4 байта, занимаемые переменной i, но и запишет оставшиеся 4 байта в область памяти, следующую за переменной i. Поскольку обычно память под глобальные переменные выделяется подряд в порядке их описания, то оставшиеся 4 байта запишутся в область памяти, отведенную под переменную r (именно она описана вслед за i), то есть в результате последнего присваивания значение переменной r будет испорчено. Подобная ошибка не будет выявлена на стадии компиляции, а при выполнении программы проявится не при данном ошибочном присваивании, а позже, когда мы захотим воспользоваться значением переменной r. Именно поэтому рекомендуется либо отказаться от использования бестиповых указателей, либо при их использовании проявлять предельную аккуратность.

Приведем пример, в котором использование указателей pointer оправдано.

Пример. Внутреннее представление значения real.

Зададимся целью посмотреть, как хранится в памяти переменная типа real. Для этого запишем ее адрес в указатель pointer, после чего преобразуем его в указатель на массив байтов и выведем этот массив на экран.

const sz = sizeof(real);

type Arr=array [1..sz] of byte;

PArr=Arr;

var r: real;

p: pointer;

pb: PArr;

i: integer;

begin

readln(r);

p:=@r;

pb:=p;

for i:=1 to sz do

write(pb[i],’ ’);

end.

Отметим одну особенность операции взятия адреса @. В Delphi ее результат зависит от директивы компиляции {$T} («typed @ operator»). По умолчанию установлена директива компиляции {$T-}: это означает, что результат операции @ имеет тип pointer. Если же установлена директива компиляции {$T+}, то результат операции @ – типизированный указатель, базовым типом для которого выступает тип операнда. Кроме того, можно получить адрес переменной, воспользовавшись стандартной функцией Addr(x), которая всегда возвращает значение типа pointer.

Особенностью операции @ можно воспользоваться, чтобы упростить последнее решение. Для этого поставим в начале программы директиву компиляции {$T-}, что позволит нам заменить присваивания p:=@r; pb:=p на pb:=@r и исключить из программы описание переменной p. Подчеркнем, что в режиме {$T+} последнее присваивание приведет к ошибке несоответствия типов, поскольку @r будет возвращать значение типа real. Впрочем, в режиме {$T+} можно воспользоваться явным приведением типов (pb:=PArr(@r)) или функцией Addr (pb:=Addr(r)):