вторник, 10 мая 2011 г.

Указатели и указательные типы

Перевод из справочной системы Delphi

Указатель – это переменная, которая обозначает адрес в памяти. Когда указатель содержит адрес другой переменной, мы говорим, что он указывает на ее местоположение в памяти или на данные, которые там хранятся. В случае с массивом или другим структурированным типом, указатель хранит адрес первого элемента в этой структуре. Если этот адрес уже используется, указатель хранит адрес на первый элемент.


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

Обзор указателей


Чтобы понять, как работает указатель, рассмотрим следующий пример:
var
X, Y: Integer; // X и Y – целочисленные переменные
P: ^Integer;// P может ссылаться на тип Integer
begin
  X := 17; // присваиваем значение X
  P := @X; // присваиваем адрес X переменной P
  Y := P^; // выполняем доступ по адресу P; 
//присваиваем результат Y
end;

В строке 2 объявляются целочисленные переменные X и Y. Строка 3 объявляет P как указатель на целочисленное значение; это обозначает, что P может указывать местоположение X или Y. Строка 5 присваивает значение переменной X, а строка 6 присваивает адрес переменной X (обозначенный как @X) переменной P. Наконец, строка 7 получает значение по адресу, на который указывает P (обозначается как ^P) и присваивает его переменной Y. После выполнения этого кода X и Y имеют одинаковое значение, а именно 17.

Оператор @, который использовался здесь для получения адреса переменной также работает с функциями и процедурами.

Символ вставки ^ имеет два значения, оба значения проиллюстрированы в нашем примере. В том случае, если он появляется перед идентификатором ^typeName:

  • он определяет тип, с которым связываются указатели на тип typeName.
  • Когда символ вставки появляется после переменной указательного типа: pointer^
  • он выполняет доступ по адресу указателя (разыменование), то есть возвращает значение, хранимое по адресу в памяти, который содержит в себе указатель.

Этот пример может показаться чем то вроде окольного пути для копирования значения одной переменной в другую, поскольку мы могли выполнить ту же операцию простой инструкцией присваивания. Но указатели полезны по нескольким причинам. Во-первых, понимание указателей поможет вам понять язык Delphi, поскольку зачастую указатели используются в программном коде неявно. Любой тип данных, который требует больших динамически распределяемых блоков памяти использует указатели. Переменные для хранения длинных строк например, по сути являются указателями, так же как и переменные, хранящие экземпляры классов. Кроме того, некоторые продвинутые программисткие техники требуют использования указателей.

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

type
  PInteger = ^Integer;
var
  R: Single;
  I: Integer;
  P: Pointer;
  PI: PInteger;
begin
  ...
  P := @R;
  PI := PInteger(P);
  I := PI^;
end;

Безусловно вещественные и целые числа хранятся в различных форматах. Это присваивание просто копирует двоичные данные из R в I, не производя конвертирования.

В дополнение к присваиванию результата операции @, вы можете воспользоваться несколькими стандартными процедурами для установки значения указателей. Процедуры New и GetMem назначают адрес существующему указателю, а функции Addr и Ptr возвращают указатель на адрес переменной.

Разыменованные указатели могут использоваться со спецификаторами, например, как в следующем выражении:

P1^.Data^. 

Зарезервированное слово nil – это специальная константа, значение которой может быть присвоено любому указателю. Когда значение nil присваивается указателю, он не указывает никуда.



Использование расширенного синтаксиса при работе с указателями

Директива компилятора {$EXTENDED} влияет на использования символа (^). Когда директива {$X+} включена (состояние по умолчанию), вы можете опускать этот символ при обращении по адресу указателей. Тем не менее, символ вставки (^) потребуется для объявления указателей для избежания двусмысленности в тех случаях, когда указатели ссылаются на другие указатели.

При включении расширенного синтаксиса, вы можете опускать символ вставки (^) при обращении к указателю:

{$X+}
 type
   PMyRec = ^TMyRec;
   TMyRec = record
     Data: Integer;
   end;

 var
   MyRec: PMyRec;

 begin
   New(MyRec);
   MyRec.Data := 42;  {#1}
 end.

При выключенной директиве расширенного синтаксиса строка с пометкой {#1} будет выглядеть:

MyRec^.Data := 42;


Типы указателей

Вы можете объявить указательный тип на любые данные, используя синтаксис:

type pointerTypeName = ^type

Если вы объявляете запись или другой тип данных, может быть полезно одновременно объявить и указатель на этот тип. Это может упростить манипуляции экземплярами без копирования больших блоков памяти.

Замечание: Вы можете объявить указательный тип до объявления типа, на который будет ссылаться указатель.

Стандартные указательные типы могут использоваться для различных целей. Наиболее гибкий тип - Pointer, который может ссылаться на данные любого типа.. Тем не менее переменная типа Pointer не может быть разыменована. Помещение символа ^ после переменной типа Pointer вызывает ошибку компиляции. Для доступа к данным, на которые ссылается переменная типа Pointer, следует преобразовать их сначала к другому указательному типу, а затем выполнять доступ по адресу.



Символьные указатели

Фундаментальные типы PAnsiChar и PWideChar представляют собой указатели на значения типа AnsiChar и WideChar соответственно. Обобщенный тип PChar представляет собой указатель на тип Char (то есть в текущей реализации – на WideChar). Эти символьные указатели используются для работы со строками, оканчивающимися на нулевой символ.

Замечание:Не следует пытаться преобразовывать не-символьные указательные типы к типу PChar для работы с арифметикой. Вместо этого следует пользоваться типом PByte, который объявлен с директивой компилятора {$POINTERMATH ON}.

Байтовый указатель

Фундаментальный тип PByte представляет собой указатель на любые байтовые данные, не являющиеся символьными. Он объявлен с директивой компилятора {$POINTERMATH ON}:

function TCustomVirtualStringTree.
   InternalData(Node: PVirtualNode): Pointer;
begin
  if (Node = FRoot) or (Node = nil) then
    Result := nil
  else
    Result := PByte(Node) + FInternalDataOffset;
end;

Проверка указательных типов

Директива компилятора $T управляет типами значений, которые возвращает оператор @. Эта директива имеет вид:

{$T+} or {$T-}

В состоянии {$T-}, тип результата оператора @ всегда является нетипизированным указателем, совместимым по типу с любыми другими указательными типами. В состоянии {$T+}, когда оператор @ применяется к переменной, типом результата будет ^T, где T совместим только с указателями, ссылающимися на значения типа, совпадающего по типу с переменной.



Прочие стандартные указательные типы

Модули System и SysUtils объявляют множество стандартных указательных типов, которые могут быть использованы в работе.

Для включения/выключения арифметики указателей при работе со всеми видами типизированных указателей (то есть инкремент и декремент происходят с учетом размера элемента) следует пользоваться директивой компилятора {POINTERMATH <ON|OFF>}


Следующие указательные типы объявлены в модулях System и SysUtils

Указательный типСсылается на переменные типа
PStringUnicodeString
PAnsiStringAnsiString
PByteArrayTByteArray (объявлен в SysUtils). Используется для преобразования типа для динамически выделенной памяти при доступе к массивам.
PCurrency, PDouble, PExtended, PSingleCurrency, Double, Extended, Single
PIntegerInteger
POleVariantOleVariant
PShortStringShortString. Полезен при переносе старого кода, который использует устаревший тип PString.
PTextBufTTextBuf (объявлен в SysUtils). TTextBuf – внутренний буферный тип в записи TTextRec файла.)
PVarRecTVarRec (объявлен в System)
PVariantVariant
PWideStringWideString
PWordArrayTWordArray (объявлен в SysUtils). Используется для преобразования типа динамически выделенной памяти для массивов, содержащих 2хбайтовые значения.

2 комментария: