Pointer — Тип Delphi


Содержание

Pointer — Тип Delphi

От Delphi 4 к Delphi 5

Во многих задачах используются переменные, которые создаются и уничтожаются во время выполнения программы. С этой целью в Object Pascal организована поддержка указателей, для которых введен специальный тип Pointer.

Указатель является величиной, указывающей на некоторый адрес в памяти, где хранятся какие-то данные. Если вы имеете дело с массивом или другим структурным типом, то указатель содержит адрес первого элемента в структуре. Переменные, которые содержат адреса других переменных, принято называть указателями .

Указатели объявляются точно так же, как обычные переменные:

P: Pointer; <объявление переменной указателя >

K: Integer; <объявление переменной целого типа >

Переменная P занимает четыре байта и может содержать адрес любого участка памяти, указывая на байты памяти со значениями любых типов данных. Для инициализации переменной, присвоим ей адрес переменной K.

P := Addr(К); <инициализация переменной с помощью функции Addr>

Инициализацию можно провести вторым способом:

P^ :=@K; <инициализация с помощью оператора @>

Обычно используется более краткий и удобный второй способ. Таким образом, если некоторая переменная Р содержит адрес другой переменной К, то говорят, что Р указывает на К (рисунок 1) . Вы можете изменить значение переменной К, не прибегая к идентификатору K.

Можно изменить значение содержимого указателя, например, вместо предварительно определенного значения в Р адреса К, присвоим значение 10.

Если вы запишете:

то это будет ошибкой.

Из-за сильной типизации языка Object Pascal перед присваиванием, необходимо преобразовать выражение P^ к целому типу Integer. Объявим указатель P следующим образом:

Такая запись говорит о том, что переменная P по-прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных.

В данном случае указатель P называют типизированным , в отличие от переменных типа Pointer, которые называют не типизированными указателями.

При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип, а переменные указатели просто объявлять с этим типом.

Модифицируем предыдущий пример (рисунок 2):

Значение PInteger — указательный тип данных. Чтобы отличить указательные типы от других, лучше назначать им идентификаторы, начинающиеся с буквы P.

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

Рассмотрим объявление типа для указателя на некоторую запись:

Переменная Р типа PScreen является указателем и может содержать адрес любой переменной типа TScreen.

Чаще всего указатели используются для работы с объектами в динамически распределяемой области памяти, особенно при работе с записями.

После объявления в секции var указатель содержит неопределенное значение. Переменные-указатели, как и обычные переменные, перед использованием нужно инициализировать. Использование неинициализированных переменных приводит к неправильным результатам, а использование неинициализированных указателей приводит к ошибке Acces violation (доступ к неверному адресу памяти) и принудительному завершению приложения.

Один из способов инициализации указателя состоит в присваивании ему адреса некоторой переменной соответствующего типа, этот способ рассмотрен выше.

Второй способ состоит в динамическом выделении участка памяти под переменную соответствующего типа и присваивании указателю его адреса. Основное назначение указателей — работа с динамическими переменными.

Размещение динамических переменных производится в специальной области памяти, которая называется Heap (куча). Размер кучи равен размеру свободной памяти компьютера (рис 2.)

Для размещения динамической переменной (рисунок 3) вызывается стандартная процедура:

New(var P: Pointer);

Эта процедура выделяет требуемый по размеру участок памяти и заносит его адрес в переменную-указатель Р.

List, P: PListEntry;

P^.Text := ‘Hello world’;

Новая процедура создает новую динамическую переменную и устанавливает переменную указателя, чтобы указать на это. P является переменной любого типа указателя. Размер размещенного блока памяти соответствует размеру типа, на который P указывает. Вновь созданная переменная может ссылаться на P^. Если там недостаточно памяти пригодной для размещения динамической переменной, то генерируется исключение EOutOfMemory .

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

P^ := ‘Мой любимый язык Delphi. ‘;

После выполнения процедуры Dispose указатель P перестает быть связан с конкретным адресом памяти. В нем будет случайное значение, как и до обращения к процедуре New. Не стоит делать попытки присвоить значение переменной Р, в противном случае это может привести к нарушению нормальной работы программы. Поэтому необходимо четко придерживаться последовательности работы с динамическими переменными:

создание динамической переменной;

выполнение с переменной необходимых действий;

разрушение динамической переменной.

Рассмотрим пример, показывающий работу указателей .

2 X, Y: Integer; // X и Y переменные целого типа

3 P: ^Integer; // P указатель на данные целого типа

5 X := 17; // присвоение значения 17 для X

6 P := @X; // определение адреса X в P

7 Y := P^; /разыменование Р; помещение результата в Y

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

Если вы добавите на форму два компонента Edit1, Edit2 и напишете следующий код

то вы можете убедиться, что значения X и Y одинаковы и равны 17.

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

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

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

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

Конечно, реальные и целые загружаются в другие форматы. Этот пример просто копирует сырые двоичные данные из R на I, не преобразовывая их.

Дополнительно к определению результата с помощью символа @ вы можете использовать стандартные программы, чтобы определить величину указателя.

В Object Pascal имеется ряд предопределенных типов указателей. Это, прежде всего, типы указателей на строки: PAnsiChar и PWideChar , представляющие собой соответственно указатели на значения AnsiChar и WideCha r. Имеется также родовой тип PChar , определяющий указатель на Char (т.е. пока указывающий на AnsiChar ). Эти указатели используются при работе со строками с нулевым символом в конце.

PAnsiString, Pstring — типы переменной, на которую указывает указатель AnsiString.

PByteArray тип переменной, на которую указывает указатель ByteArray (объявлен в модуле SysUtils ). Используется для доступа к динамически размещаемым массивам.

PCurrency тип переменной, на которую указывает указатель Currency.

PExtended тип переменной, на которую указывает указатель Extended.

POleVariant тип переменной, на которую указывает указатель OleVariant.

PShortString тип переменной, на которую указывает указатель ShortString.

PTextBuf тип переменной, на которую указывает указатель TextBuf (объявлен в модуле SysUtils ). Внутренний тип буфера в записи файлов TTextRec .

PVarRec тип переменной, на которую указывает указатель TVarRec (объявлен в модуле System).

PVariant тип переменной, на которую указывает указатель Variant.

PWideString тип переменной, на которую указывает указатель WideString.

PWordArray т ип переменной, на которую указывает указатель TWordArray (объявлен в модуле SysUtils ). Используется для доступа к динамически размещаемым массивам 2-байтных величин.

Объявление своего типизированного указателя на любой тип имеет вид:

Имеется предопределенная константа nil , которая обычно присваивается указателям, в данный момент ни на что не указывающим.

Чтобы получить доступ к данным, на которые указывает типизированный указатель, надо применить операцию его разыменования. Она записывается с помощью символа ^ , помещаемого после указателя.

Операция разыменования не применима к указателям типа pointer. Чтобы провести разыменование указателя pointer, надо сначала привести его к другому типу указателя.

Указатели PChar и PWideChar.

При работе со строками с нулевым символом в конце часто используют специальные типы указателей: PChar и PWideChar, которые являются указателями соответственно на массивы с элементами типов Char и WideChar с нулевым символом в конце.

Переменные этих типов часто требуется передавать в различные функции и процедуры в качестве параметров. К тому же, типы Char и WideChar существенно упрощают коды. Дело в том, что эти типы совместимы со строковыми константами. Например, объявление и использование переменной типа PChar может иметь вид

Это эквивалентно операторам

const SHello: array[0…7] of Char = ‘Привет!’#0;

согласитесь, первая запись гораздо короче.

Совместимость указателей типов PChar и PWideChar со строковыми константами позволяет в вызовах функций и процедур с параметрами соответствующих типов использовать просто строки символов.

Например, если некоторая процедура F требует параметр типа PChar, то обратиться к ней можно так:

Указатели типов PChar и PWideChar могут индексироваться точно так же, как строки и массивы символов. Например, P[0] — это первый символ строки, на которую указывает P.

В выражениях, операторах присваивания и при передаче параметров в функции и процедуры можно смешивать длинные строки (AnsiString) и строки с нулевым символом в конце типа PChar. Но иногда требуется явное приведение типа PChar к типу длинной строки. Например, операция склеивания (конкатенации) строк требует, чтобы хотя бы один ее операнд был типа строки.

Если же требуется склеить два объекта типа PChar, то это надо сделать с помощью приведения типа:

S := string(P1) + string(P2);

Когда вы объявляете константу указатель, вы должны проинициализировать эту величину. Есть три пути, чтобы сделать это: с @ оператором, с нулем и (если константа будет типом PChar) с литералом строки. Например, имеется глобальная переменная целого типа, вы можете объявить константу следующим образом:

const PI: ^Integer = @I;

Компилятор может решить эту проблему, поскольку глобальные переменные являются частью кодового сегмента. Так, функции и глобальные константы объявляются следующим образом:

const PF: Pointer = @MyFunction;

Поскольку литералы строки определены как глобальные константы, вы можете проинициализировать PChar константу с литералом строки:

const WarningStr: PChar = ‘Warning!’;

Процедуры GetMem и FreeMem.

GetMem(var P: Pointer [;Size: Integer]) — создает в динамической памяти новую динамическую переменную с заданным размером Size и присваивает ее адрес указателю P.

Переменная указателя P может указывать на данные любого типа.

FreeMem(var P: Pointer [; Size: Integer]) — освобождает динамическую переменную.

Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу.

GetMem(P4, SizeOf(ShortString)); <выделить блок памяти для P4>

FreeMem(P4); <освободить блок памяти >

С помощью процедуры GetMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей, в этом состоит ее основное отличие от процедуры New.

В данном случае для указателя P4 выделяется меньше памяти, чем может уместиться в переменной типа ShortString, и программист сам должен предотвратить выход строки за пределы выделенного участка.

В некоторых случаях возникает необходимость перевыделения динамической памяти, например, для того, чтобы разместить в динамической области больше данных.

Для этого предназначена следующая процедура:

ReallocMem(var P: Pointer; Size: Integer) — освобождает блок памяти по значению указателя Р и выделяет для указателя новый блок памяти заданного размера Size.

Указатель P может иметь значение nil, а параметр Size — значение 0, что влияет на работу процедуры:

если P = nil и Size = 0, процедура ничего не делает;

если P = nil и Size <> 0, процедура выделяет новый блок памяти заданного размера, что соответствует вызову процедуры GetMem;

если P <> nil и Size = 0, процедура освобождает блок памяти, адресуемый указателем P, и устанавливает указатель в значение nil; это соответствует вызову процедуры FreeMem, с той лишь разницей, что FreeMem не очищает указатель;

если P <> nil и Size <> 0, процедура переопределяет память для указателя P; размер нового блока определяется значением Size. Данные из прежнего блока копируются в новый блок; если новый блок больше прежнего, то приращенный участок остается неинициализированным и содержит случайные данные.

Создадим приложение, в котором можно осуществлять поиск изображений и просматривать их. Для Delphi 4 необходимо проделать следующие действия.

    1. Запустите Delphi.
    2. Сохраните файл модуля под именем ImageView_pas, а файл проекта под именем ImageView. dpr.
    3. Поместите на форму компонент Image со страницы Additional палитры компонентов.
    4. Поместите на форму компонент OpenDialog со страницы Dialogs палитры компонентов.
    5. Поместите на форму компонент BitButton со страницы Additional палитры компонентов. Вы уже знаете, как придать данной кнопке более красивый вид, можете проделать это. Для обработчика события щелчка по кнопке OnClick напишите следующий программный код:

    procedure TForm1.BitBtn1Click(Sender: TObject);

    if OpenDialog1.Execute then

    Image1.Top:= Form1.ClientRect.Top + (Form1.ClientHeight — Image1.Height)

    Image1.Left:= Form1.ClientRect.Left + (Form1.ClientWidth — Image1.Width)

  1. Для того чтобы изображения разных размеров были в окне полностью видимы, необходимо установить для компонента Image1 свойство AutoSize в True. А приведенные выше операторы позволят размещать изображение по центру формы. В этом программном коде размеры клиентской области устанавливаются несколько больше размеров компонента Image1, которые в свою очередь адаптируются к размеру изображения благодаря свойству AutoSize.
  2. Для того чтобы просматривать только необходимые вам изображения, используя свойство Filter компонента OpenDialog, определите следующие значения. Например, запишите в разделе Filter Mame *.bmp;*.ico;*.wmf, в разделе Filter запишите *.bmp;*.ico;*.wmf (рисунок 4). На рисунке 5 показана процедура поиска необходимого изображения. На рисунке 6 показан результат работы программы ImageView.

Если у вас установлена версия Delphi 5, то на странице палитры компонентов Dialogs имеется компонент OpenPictureDialog, который вызывает окно открытия и предварительного просмотра изображений. Если вы посмотрите свойство Filter этого компонента (рисунок 7), то вы увидите, что за вас поработали и определили, какие файлы вы можете просматривать. Добавьте на форму компонент Image и Button и напишите следующий программный код:

procedure TForm1.Button1Click(Sender: TObject);

if OpenPictureDialog1.Execute then

Осуществляя поиск необходимого изображения, в Delphi 5 диалоговое окно открытия файлов содержит окно предварительного просмотра изображений.

Для компонента Image определено событие OnProgress, с помощью данного обработчика можно получить дополнительную информацию во время выполнения длительной по времени операции загрузки большого по объему изображения.

procedure TForm1.Image1Progress(Sender: TObject; Stage: TProgressStage;

PercentDone: Byte; RedrawNow: Boolean; const R: TRect;

const Msg: String);

Параметр Stage содержит состояние процесса загрузки (psStarting — начало, psRunning — идет загрузка, psEnding — процесс завершен ). Параметр PercentDone приблизительно указывает процент выполненной работы.

С помощью параметра RedrawNow Windows сообщает, нужно ли сейчас выполнить прорисовку части изображения. Этот параметр имеет смысл, только если свойство IncrementalDisplay компонента содержит True. Параметр R – прямоугольник, нуждающийся в прорисовке. Параметр Msg содержит одно или более слов, уточняющих состояние процесса.

Обычно в обработчике события по сигналу psStarting создается индикатор процесса типа TProgressBar, по сигналам psRunning изменяется позиция индикатора, а в момент psEnding индикатор уничтожается.

Событие OnProgress создается только при загрузке некоторых типов изображений, например, подготовленных в формате JPEG (Joint Photographic Except Group — объединенная группа фотографических экспертов). Пример использования события OnProgress вы можете найти в папке Borland \ Delphi \ Help \ Examples \ JEPG. На рисунке 8 представлен результат демонстрационной работы программы Ipegproi.

  1. Марко Канту. Delphi 2 для Windows 95/NT. Москва. ООО «Малип». 1997г.
  2. Джон Матчо. Дэвид Р. Фолкнер. Delphi. Москва. БИНОМ. 1995г.
  3. Эндрю Возневич. Delphi. Освой самостоятельно. Москва. Восточная книжная компания. 1996г.
  4. К. Сурков, Д. Сурков, А. Вальвачев. Программирование в среде Delphi 2.0.

ООО «Попурри» 1997 г.

  • В.В.Фаронов. Delphi 5. Учебный курс. Москва. Издательство Нолидж. 2000г.
  • А. Я. Архангельский. Программирование в Delphi 5. Москва. ЗАО «Издательство Бином». 2000г.
  • Заметки о Pascal, Delphi и Lazarus

    Следует ожидать переводов разделов справочной системы Delphi, компиляций из учебников, переводы статей, «путевые заметки» и прочие интересности. Блог прежде всего ориентирован на студентов, но опытных людей я тоже буду рад видеть;-)

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

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

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

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

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

    В строке 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 целочисленной переменной:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Для включения/выключения арифметики указателей при работе со всеми видами типизированных указателей (то есть инкремент и декремент происходят с учетом размера элемента) следует пользоваться директивой компилятора

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

    Как определить «UCHAR * POINTER_32» и «VOID * POINTER_32» типы в Delphi?

    Задний план:

    Переводя IP_OPTION_INFORMATION32 и ICMP_ECHO_REPLY32 структуры для 64-битного компилятора я stucked с типами данных , чтобы использовать там. Определения структуры из ссылки:

    Структура IP_OPTION_INFORMATION32:

    Я хотел бы перевести этот путь (для Delphi XE2, 64-разрядные целевой платформы). Как вы можете видеть, я не знаю , какой тип использовать для OptionsData поля структуры:

    Структура ICMP_ECHO_REPLY32:

    Для Delphi XE2 64-битной целевой платформы я хотел бы написать:

    Вопрос:

    Как бы вы определили UCHAR * POINTER_32 и VOID * POINTER_32 тип в Delphi для 64-битной платформы цели? Насколько я знаю, нет 32-битного типа указателя для 64-битных целевой платформы и я просто не нравлюсь быть определенно , например , в качестве Int32 типа :-)

    Что является наиболее точным переводом для указанных типов?

    Вопрос о том, что POINTER_32 рассматривается в другом переполнением стека вопрос: POINTER_32 — что это такое, и почему?

    Вы могли бы использовать его при выполнении Interop с структурами, которые определены в другом процессе, тот, который имеет 32-битные указатели.

    Вы не имеете эквивалент __ptr32 в Delphi , так что вы не должны просто нет выбора, кроме как объявить его как 32 битное целое число. Я хотел бы использовать без знака типа.

    В этом случае UInt32 (или кардинал, DWORD и т.д. — все без знака 32 бит) будет в порядке. Все , что вам нужно сделать , это объявить без знака 32 бита в правильном положении структуры (записи). Процессор не будет заботиться, компилятор не будет заботиться (вы можете типаж указатель при необходимости) это на самом деле указатель или нет. Даже подписал 32 бит будет работать , если не будет делать некоторую математику со значением и флаги устройства обработки проверки. И еще одна вещь , в структуре переводов из C в Delphi: убедитесь , что первоначальное выравнивание было 4 байта (32 бит за единицу), потому что в record отличие от packed record (без выравнивания) по умолчанию не используя такой же выравнивание 4 байта. Иначе вы можете иметь проблемы, применяя свои записи для данных , таких как

    Указатели в Delphi

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

    Наиболее часто указатели используются при работе:

      с объектами в динамически распределяемой памяти. Динамически распределяемая память это создание новых объектов по мере надобности. По сути это некая структура данных, которая описывается один раз и по мере надобности вызывается в программе с присваиванием новых значений. В следствии можно освободить занятую область памяти;

    X^:=50;
    при работе с записями. Для реализации динамического размещения записи применяют самоадресуемую запись. Записи снабжаются полями, поля имеют указатель на следующую запись. Применяется для удобства перестановки записей или удаления. Для этого формируется список записей посредством цикла по указателям.

    Для первого случая выделение памяти можно осуществить при помощи процедуры:
    procedure New ); Во втором случае выделение памяти для новой
    записи воспользуемся New(pr), соответственно описать ее:

    В Object Pascal существует предопределенные типы указателей переменных: AnsiString, ByteArray, Currency Extended, Ole Variant, ShortString, TVarRec, Variant, WideString, TWordArray. Для обозначения типа указателя достаточно соответственно поставить «P» перед типом переменной
    (пример: PByteArray).

    Для объявления своего указателя на любой тип используется конструкция вида:
    type = ^ ;

    . указатель в функции delphi delphi указатель указатель в делфи

    Программирование на языке Delphi. Глава 2. Основы языка Delphi. Часть 4

    Оглавление


    Файлы


    Понятие файла

    С точки зрения пользователя файл — это именованная область данных на диске или любом другом внешнем носителе. В программе файл предстает как последовательность элементов некоторого типа. Так как размер одного файла может превышать объем всей оперативной памяти компьютера, доступ к его элементам выполняется последовательно с помощью процедур чтения и записи.

    Для файла существует понятие текущей позиции . Она показывает номер элемента, который будет прочитан или записан при очередном обращении к файлу. Чтение-запись каждого элемента продвигает текущую позицию на единицу вперед. Для большинства файлов можно менять текущую позицию чтения-записи, выполняя прямой доступ к его элементам.

    В зависимости от типа элементов различают три вида файла:

    • файл из элементов фиксированного размера; элементами такого файла чаще всего являются записи;
    • файл из элементов переменного размера ( нетипизированный файл ); такой файл рассматривается просто как последовательность байтов;
    • текстовый файл ; элементами такого файла являются текстовые строки.

    Для работы с файлом в программе объявляется файловая переменная . В файловой переменной запоминается имя файла, режим доступа (например, только чтение), другие атрибуты. В зависимости от вида файла файловая переменная описывается по-разному.

    Для работы с файлом, состоящим из типовых элементов переменная объявляется с помощью словосочетания file of , после которого записывается тип элемента:

    К моменту такого объявления тип TPerson должен быть уже описан (см. выше).

    Объявление переменной для работы с нетипизированным файлом выполняется с помощью отдельного слова file :

    Для работы с текстовым файлом переменная описывается с типом TextFile:

    Работа с файлами

    Наиболее часто приходится иметь дело с текстовым представлением информации, поэтому рассмотрим запись и чтение текстового файла.

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

    В результате этого действия поля файловой переменной F инициализируются начальными значениями. При этом в поле имени файла заносится строка ‘MyFile.txt’.

    Так как файла еще нет на диске, его нужно создать:

    Теперь запишем в файл несколько строк текста. Это делается с помощью хорошо вам знакомых процедур Write и Writeln:

    При работе с файлами первый параметр этих процедур показывает, куда происходит вывод данных.

    После работы файл должен быть закрыт:

    Рассмотрим теперь, как прочитать содержимое текстового файла. После инициализации файловой переменной (AssignFile) файл открывается с помощью процедуры Reset:

    Для чтения элементов используются процедуры Read и Readln, в которых первый параметр показывает, откуда происходит ввод данных. После работы файл закрывается. В качестве примера приведем программу, распечатывающую в своем окне содержимое текстового файла ‘MyFile.txt’:

    Так как обычно размер файла заранее не известен, перед каждой операцией чтения вызывается функция Eof, которая возвращает True, если достигнут конец файла.

    Внимание! Текстовые файлы можно открывать только для записи или только для чтения, но не для того и другого одновременно. Для того чтобы сначала записать текстовый файл, а потом прочитать, его нужно закрыть после записи и снова открыть, но уже только для чтения.

    Стандартные подпрограммы управления файлами

    Для обработки файлов в языке Delphi имеется специальный набор процедур и функций:

    • AssignFile (var F; FileName: string) — связывает файловую переменную F и файл, имя которого указано в FileName.
    • Reset (var F [: File; RecSize: Word ] ) — открывает существующий файл. Если открывается нетипизированный файл, то RecSize задает размер элемента файла.
    • Rewrite (var F [: File; RecSize: Word ] ) — создает и открывает новый файл.
    • Append (var F: TextFile) — открывает текстовый файл для добавления текста.
    • Read (F, V1 [, V2, . Vn ]) — начиная с текущей позиции, читает из типизированного файла подряд расположенные элементы в переменные V1, V2, . Vn.
    • Read (var F: TextFile; V1 [, V2, . Vn ] ) — начиная с текущей позиции, читает из текстового файла символы или строки в переменные V1, V2, . Vn.
    • Write (F, V1 [, V2, . Vn ]) — начиная с текущей позиции, записывает в типизированный файл значения V1, V2, . Vn.
    • Write (var F: TextFile; V1 [, V2, . Vn ] ) — начиная с текущей позиции указателя чтения-записи, записывает в текстовый файл значения V1, V2, . Vn.
    • CloseFile (var F) — закрывает ранее открытый файл.
    • Rename (var F; NewName: string) — переименовывает неоткрытый файл F любого типа. Новое имя задается в NewName.
    • Erase (var F) — удаляет неоткрытый внешний файл любого типа, заданный переменной F.
    • Seek (var F; NumRec: Longint) — устанавливает позицию чтения-записи на элемент с номером NumRec; F — типизированный или нетипизированный файл.
    • SetTextBuf (var F: TextFile; var Buf [; Size: Word]) — назначает текстовому файлу F новый буфер ввода-вывода Buf объема Size.
    • SetLineBreakStyle (var T: Text; Style: TTextLineBreakStyle) — устанавливает способ переноса строк в файле (одиночный символ #10 или пара символов #13#10).
    • Flush (var F: TextFile) — записывает во внешний файл все символы, переданные в буфер для записи.
    • Truncate (var F) — урезает файл, уничтожая все его элементы, начиная с текущей позиции.
    • IOResult : Integer — возвращает код, характеризующий результат (была ошибка или нет) последней операции ввода-вывода.
    • FilePos (var F): Longint — возвращает для файла F текущую файловую позицию (номер элемента, на которую она установлена, считая от нуля). Не используется с текстовыми файлами.
    • FileSize (var F): Longint — возвращает число компонент в файле F. Не используется с текстовыми файлами.
    • Eoln (var F: Text): Boolean — возвращает булевское значение True, если текущая позиция чтения-записи находится на маркере конца строки. Если параметр F не указан, функция применяется к стандартному устройству ввода с именем Input.
    • Eof (var F): Boolean — возвращает булевское значение True, если текущая позиция чтения-записи находится сразу за последним элементом, и False в противном случае.
    • SeekEoln (var F: Text): Boolean — возвращает True при достижении маркера конца строки. Все пробелы и знаки табуляции, предшествующие маркеру, пропускаются.
    • SeekEof (var F: Text): Boolean — возвращает значение True при достижении маркера конца файла. Все пробелы и знаки табуляции, предшествующие маркеру, пропускаются.

    Для работы с нетипизированными файлами используются процедуры BlockRead и BlockWrite. Единица обмена

    • BlockRead (var F: File; var Buf; Count: Word [; Result: Word] ) — считывает из файла F определенное число блоков в память, начиная с первого байта переменной Buf. Параметр Buf представляет любую переменную, используемую для накопления информации из файла F. Параметр Count задает число считываемых блоков. Параметр Result является необязательным и содержит после вызова процедуры число действительно считанных записей. Использование параметра Result подсказывает, что число считанных блоков может быть меньше, чем задано параметром Count.
    • BlockWrite (var F: File; var Buf; Count: Word [; Result: Word]) — предназначена для быстрой передачи в файл F определенного числа блоков из переменной Buf. Все параметры процедуры BlockWrite аналогичны параметрам процедуры BlockRead.
    • ChDir (const S: string) — устанавливает текущий каталог.
    • CreateDir (const Dir: string): Boolean — создает новый каталог на диске.
    • MkDir (const S: string) — аналог функции CreateDir. Отличие в том, что в случае ошибки при создании каталога функция MkDir создает исключительную ситуацию.
    • DeleteFile (const FileName: string): Boolean — удаляет файл с диска.
    • DirectoryExists (const Directory: string): Boolean — проверяет, существует ли заданный каталог на диске.
    • FileAge (const FileName: string): Integer — возвращает дату и время файла в числовом системно-зависимом формате.
    • FileExists (const FileName: string): Boolean — проверяет, существует ли на диске файл с заданным именем.
    • FileIsReadOnly (const FileName: string): Boolean — проверяет, что заданный файл можно только читать.
    • FileSearch (const Name, DirList: string): string — осуществляет поиск заданого файла в указанных каталогах. Список каталогов задается параметром DirList; каталоги разделяются точкой с запятой для операционной системы Windows и запятой для операционной системы Linux. Функция возвращает полный путь к файлу.
    • FileSetReadOnly (const FileName: string; ReadOnly: Boolean): Boolean — делает файл доступным только для чтения.
    • FindFirst/FindNext/FindClose
    • ForceDirectories (Dir: string): Boolean — создает новый каталог на диске. Позволяет одним вызовом создать все каталоги пути, заданного параметром Dir.
    • GetCurrentDir : string — возвращает текущий каталог.
    • SetCurrentDir (const Dir: string): Boolean — устанавливает текущий каталог. Если это сделать невозможно, функция возвращет значение False.
    • RemoveDir (const Dir: string): Boolean — удаляет каталог с диска; каталог должен быть пустым. Если удалить каталог невозможно, функция возвращет значение False.
    • RenameFile (const OldName, NewName: string): Boolean — изменяет имя файла. Если это сделать невозможно, функция возвращет значение False.
    • ChangeFileExt (const FileName, Extension: string): string — возвращает имя файла с измененным расширением.
    • ExcludeTrailingPathDelimiter (const S: string): string — отбрасывает символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он присутствует в конце строки.
    • IncludeTrailingPathDelimiter (const S: string): string — добавляет символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он отсутствует в конце строки.
    • ExpandFileName (const FileName: string): string — возвращает полное имя файла (с абсолютным путем) по неполному имени.
    • ExpandUNCFileName (const FileName: string): string — возвращает полное сетевое имя файла (с абсолютным сетевым путем) по неполному имени. Для операционной системы Linux эта функция эквивалентна функции ExpandFileName.
    • ExpandFileNameCase (const FileName: string; out MatchFound: TFilenameCaseMatch): string — возвращает полное имя файла (с абсолютным путем) по неполному имени, допуская несовпадения заглавных и строчных букв в имени файла для тех файловых систем, которые этого не допускают (например, файловая система ОС Linux).
    • ExtractFileDir (const FileName: string): string — выделяет путь из полного имени файла; путь не содержит в конце символ-разделитель каталогов.
    • ExtractFilePath (const FileName: string): string — выделяет путь из полного имени файла; путь содержит в конце символ-разделитель каталогов.
    • ExtractRelativePath (const BaseName, DestName: string): string — возвращает относительный путь к файлу DestName, отсчитанный от каталога BaseName. Путь BaseName должен заканчиваться символом-разделителем каталогов.
    • ExtractFileDrive (const FileName: string): string — выделяет имя диска (или сетевого каталога) из имени файла. Для операционной системы Linux функция возвращает пустую строку.
    • ExtractFileExt (const FileName: string): string — выделяет расширение файла из его имени.
    • ExtractFileName (const FileName: string): string — выделяет имя файла, отбрасывая путь к нему.
    • IsPathDelimiter (const S: string; Index: Integer): Boolean — проверяет, является ли символ S[Index] разделителем каталогов.
    • MatchesMask (const Filename, Mask: string): Boolean — проверяет, удовлетворяет ли имя файла заданной маске.

    Указатели


    Понятие указателя

    Все переменные, с которыми мы имели дело, известны уже на этапе компиляции. Однако во многих задачах нужны переменные, которые по мере необходимости можно создавать и удалять во время выполнения программы. С этой целью в языке Delphi организована поддержка так называемых указателей, для которых введен специальный тип данных Pointer.

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

    Переменная P занимает 4 байта и может содержать адрес любого участка памяти, указывая на байты со значениями любых типов данных: Integer, Real, string, record, array и других. Чтобы инициализировать переменную P, присвоим ей адрес переменной N. Это можно сделать двумя эквивалентными способами:

    В дальнейшем мы будем использовать более краткий и удобный второй способ.

    Если некоторая переменная P содержит адрес другой переменной N, то говорят, что P указывает на N. Графически это обозначается стрелкой, проведенной из P в N (рисунок 12 выполнен в предположении, что N имеет значение 10):

    Рисунок 12. Графическое изображение указателя P на переменную N

    Теперь мы можем изменить значение переменной N, не прибегая к идентификатору N. Для этого слева от оператора присваивания запишем не N, а P вместе с символом ^ :

    Символ ^ , записанный после имени указателя, называется оператором доступа по адресу . В данном примере переменной, расположенной по адресу, хранящемуся в P, присваивается значение 10. Так как в переменную P мы предварительно занесли адрес N, данное присваивание приводит к такому же результату, что и

    Однако в примере с указателем мы умышленно допустили одну ошибку. Дело в том, что переменная типа Pointer может содержать адреса переменных любого типа, не только Integer. Из-за сильной типизации языка Delphi перед присваиванием мы должны были бы преобразовать выражение P^ к типу Integer:

    Согласитесь, такая запись не совсем удобна. Для того, чтобы сохранить простоту и избежать постоянных преобразований к типу, указатель P следует объявить так:

    При такой записи переменная P по прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных. В данном случае указатель P называют типизированным , в отличие от переменных типа Pointer, которые называют нетипизированными указателями. При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип данных, а переменные-указатели просто объявлять с этим типом. Поэтому предыдущий пример можно модифицировать следующим образом:

    PInteger — это указательный тип данных . Чтобы отличать указательные типы данных от других типов, будем назначать им идентификаторы, начинающиеся с буквы P (от слова Pointer). Объявление указательного типа данных является единственным способом введения указателей на составные переменные, такие как массивы, записи, множества и другие. Например, объявление типа данных для создания указателя на некоторую запись TPerson может выглядеть так:

    Переменная P, описанная с типом данных PPerson, является указателем и может содержать адрес любой переменной типа TPerson. Впредь все указатели мы будем вводить через соответствующие указательные типы данных. Типом Pointer будем пользоваться лишь тогда, когда это действительно необходимо или оправдано.

    Динамическое распределение памяти

    После объявления в секции var указатель содержит неопределенное значение. Поэтому переменные-указатели, как и обычные переменные, перед использованием нужно инициализировать. Отсутствие инициализации указателей является наиболее распространенной ошибкой среди новичков. Причем если использование обычных неинициализированных переменных приводит просто к неправильным результатам, то использование неинициализированных указателей обычно приводит к ошибке «Access violation» (доступ к неверному адресу памяти) и принудительному завершению приложения.

    Один из способов инициализации указателя состоит в присваивании ему адреса некоторой переменной соответствующего типа. Этот способ мы уже рассмотрели. Второй способ состоит в динамическом выделении участка памяти под переменную соответствующего типа и присваивании указателю его адреса. Работа с динамическими переменными и есть основное назначение указателей. Размещение динамических переменных производится в специальной области памяти, которая называется Heap (куча). Ее размер равен размеру свободной памяти компьютера.

    Для размещения динамической переменной вызывается стандартная процедура

    Она выделяет требуемый по размеру участок памяти и заносит его адрес в переменную-указатель P. В следующем примере создаются 4 динамических переменных, адреса которых присваиваются переменным-указателям P1, P2, P3 и P4:

    Далее по адресам в указателях P1, P2, P3 и P4 можно записать значения:

    В таком контексте динамические переменные P1^, P2^, P3^ и P4^ ничем не отличаются от обычных переменных соответствующих типов. Операции над динамическими переменными аналогичны подобным операциям над обычными переменными. Например, следующие операторы могут быть успешно откомпилированы и выполнены:

    После работы с динамическими переменными необходимо освободить занимаемую ими память. Для этого предназначена процедура:

    Например, в приведенной выше программе явно не хватает следующих строк:

    После выполнения данных утверждений указатели P1, P2, P3 и P4 опять перестанут быть связаны с конкретными адресами памяти. В них будут случайные значения, как и до обращения к процедуре New. Не стоит делать попытки присвоить значения переменным P1^, P2^, P3^ и P4^, ибо в противном случае это может привести к нарушению нормальной работы программы.

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

    Поэтому следует четко придерживаться последовательности действий при работе с динамическими переменными:

    • создать динамическую переменную;
    • выполнить с ней необходимые действия;
    • разрушить динамическую переменную.

    Операции над указателями

    С указателями можно работать как с обычными переменными, например присваивать значения других указателей:

    После выполнения этого оператора оба указателя P1 и P3 будут указывать на один и тот же участок памяти. Однако будьте осторожны при операциях с указателями. Только что сделанное присваивание приведет к тому, что память, выделенная ранее для указателя P3, будет потеряна. Отдавая программе участок свободной памяти, система помечает его как занятый. После работы вся память, которая была выделена динамически, должна быть возвращена системе. Поэтому изменение значения указателя P3 без предварительного освобождения связанной с ним динамической переменной является ошибкой.

    Использование одинаковых значений в разных указателях открывает некоторые интересные возможности. Так после оператора P3 := P1 изменение значения переменной P3^ будет равносильно изменению значения P1^.

    В этом нет ничего удивительного, так как указатели P1 и P3 указывают на одну и ту же физическую переменную. Просто для доступа к ней могут использоваться два имени: P1^ и P3^. Такая практика требует большой осмотрительности, поскольку всегда следует различать операции над адресами и операции над данными, хранящимися в памяти по этим адресам.

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

    Чаще всего операции сравнения указателей используются для проверки того, связан ли указатель с динамической переменной. Если еще нет, то ему следует присвоить значение nil (зарезервированное слово):

    Установка P1 в nil однозначно говорит о том, что указателю не выделена динамическая память. Если всем объявленным указателям присвоить значение nil, то внутри программы можно легко выполнить тестирование наподобие этого:

    Процедуры GetMem и FreeMem

    Для динамического распределения памяти служат еще две тесно взаимосвязанные процедуры: GetMem и FreeMem. Подобно New и Dispose, они во время вызова выделяют и освобождают память для одной динамической переменной:

    • GetMem (var P: Pointer; Size: Integer) — создает в динамической памяти новую динамическую переменную c заданным размером Size и присваивает ее адрес указателю P. Переменная-указатель P может указывать на данные любого типа.
    • FreeMem (var P: Pointer [; Size: Integer] ) — освобождает динамическую переменную. Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу. Обращения к GetMem и FreeMem могут полностью соответствовать вызовам New и Dispose.

    Следующий отрывок программы даст тот же самый результат:

    С помощью процедуры GetMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей. В этом состоит ее основное отличие от процедуры New.

    В данном случае для указателя P4 выделяется меньше памяти, чем может уместиться в переменной типа ShortString, и программист сам должен обеспечить невыход строки за пределы выделенного участка.

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

    ReallocMem (var P: Pointer; Size: Integer) — освобождает блок памяти по значению указателя P и выделяет для указателя новый блок памяти заданного размера Size. Указатель P может иметь значение nil , а параметр Size — значение 0, что влияет на работу процедуры:

    • если P = nil и Size = 0, процедура ничего не делает;
    • если P = nil и Size <> 0, процедура выделяет новый блок памяти заданного размера, что соответствует вызову процедуры GetMem.
    • если P <> nil и Size = 0, процедура освобождает блок памяти, адресуемый указателем P и устанавливает указатель в значение nil . Это соответствует вызову процедуры FreeMem, с той лишь разницей, что FreeMem не очищает указатель;
    • если P <> nil и Size <> 0, процедура перевыделяет память для указателя P. Размер нового блока определяется значением Size. Данные из прежнего блока копируются в новый блок. Если новый блок больше прежнего, то приращенный участок остается неинициализированным и содержит случайные данные.

    Представление строк в памяти

    В некоторых случаях динамическая память неявно используется программой, например для хранения строк. Длина строки может варьироваться от нескольких символов до миллионов и даже миллиардов (теоретический предел равен 2 ГБ). Тем не менее, работа со строками в программе осуществляется так же просто, как работа с переменными простых типов данных. Это возможно потому, что компилятор автоматически генерирует код для выделения и освобождения динамической памяти, в которой хранятся символы строки. Но что стоит за такой простотой? Не идет ли она в ущерб эффективности? С полной уверенностью можем ответить, что эффективность программы не только не снижается, но даже повышается.

    Физически переменная строкового типа представляет собой указатель на область динамической памяти, в которой размещаются символы. Например, переменная S на самом деле представляет собой указатель и занимает всего четыре байта памяти (SizeOf(S) = 4):

    При объявлении этот указатель автоматически инициализируется значением nil. Оно показывет, что строка является пустой. Функция SetLength, устанавливающая размер строки, на самом деле резервирует необходимый по размеру блок динамической памяти и записывает его адрес в строковую переменную:

    За оператором присваивания строковых переменных на самом деле кроется копирование значения указателя, а не копирование блока памяти, в котором хранятся символы.

    Такой подход весьма эффективен как с точки зрения производительности, так и с точки зрения экономного использования оперативной памяти. Его главная проблема состоит в том, чтобы обеспечить удаление блока памяти, содержащего символы строки, когда все адресующие его строковые переменные прекращают свое существование. Эта проблема эффективно решается с помощью механизма подсчета количества ссылок (reference counting). Для понимания его работы рассмотрим формат хранения строк в памяти подробнее.

    Пусть в программе объявлены две строковые переменные:

    И пусть в программе существует оператор, присваивающий переменной S1 значение некоторой функции:

    Для хранения символов строки S1 по окончании ввода будет выделен блок динамической памяти. Формат этого блока после ввода значения ‘Hello’ показан на рисунке 13:

    Рисунок 13. Представление строковых переменных в памяти

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

    Если в программе встречается оператор присваивания значения одной строковой переменной другой строковой переменной,

    то, как мы уже сказали, копия строки в памяти не создается. Копируется только адрес, хранящийся в строковой переменной, и на единицу увеличивается количество ссылок на строку (рисунок 14).

    Рисунок 14. Результат копирования строковой переменной S1 в строковую переменную S2

    При присваивании переменной S1 нового значения (например, пустой строки):

    количество ссылок на предыдущее значение уменьшается на единицу (рисунок 15).

    Рисунок 15. Результат присваивания строковой переменной S1 нового значения (пустой строки)

    Блок динамической памяти освобождается, когда количество ссылок на строку становится равным нулю. Этим обеспечивается автоматическое освобождение неиспользуемой памяти.

    Интересно, а что происходит при изменении символов строки, с которой связано несколько строковых переменных? Правила семантики языка требуют, чтобы две строковые переменные были логически независимы, и изменение одной из них не влияло на другую. Это достигается с помощью механизма копирования при записи (copy-on-write).

    Например, в результате выполнения операторов

    получим следующую картину в памяти (рисунок 16):

    Рисунок 16. Результат изменения символа в строке S1

    Работая сообща, механизмы подсчета количества ссылок и копирования при записи позволяют наиболее эффективно работать со строками. Это как раз тот случай, когда простота и удобство сочетается с мощью и эффективностью.

    Все, что было сказано выше о представлении в памяти строк, относится только к строкам формата AnsiString. Строки формата WideString тоже хранятся в динамической памяти, но для них не поддерживаются механизм подсчета количества ссылок и механизм копирования по записи. Операция присваивания строковых переменных формата WideString означает выделение нового блока динамической памяти и полное копирование в него всех символов исходной строки. Что же касается коротких строк, то они целиком хранятся по месту объявления: или в области данных программы (если это глобальные переменные), или на стеке (если это локальные переменные). Динамическая память вообще не используется для хранения коротких строк.

    Динамические массивы

    Одним из мощнейших средств языка Delphi являются динамические массивы. Их основное отличие от обычных массивов заключается в том, что они хранятся в динамической памяти. Этим и обусловлено их название. Чтобы понять, зачем они нужны, рассмотрим пример:

    Задать размер массива A в зависимости от введенного пользователем значения невозможно, поскольку в качестве границ массива необходимо указать константные значения. А введенное пользователем значение никак не может претендовать на роль константы. Иными словами, следующее объявление будет ошибочным:


    На этапе написания программы невозможно предугадать, какие именно объемы данных захочет обрабатывать пользователь. Тем не менее, Вам придется ответить на два важных вопроса:

    • На какое количество элементов объявить массив?
    • Что делать, если пользователю все-таки понадобится большее количество элементов?

    Вы можете поступить следующим образом. В качестве верхней границы массива установить максимально возможное (с вашей точки зрения) количество элементов, а реально использовать только часть массива. Если пользователю потребуется большее количество элементов, чем зарезервировано Вами, то ему можно попросту вежливо отказать. Например:

    Такое решение проблемы является неоптимальным. Если пользователю необходимо всего 10 элементов, программа работает без проблем, но всегда использует объем памяти, необходимый для хранения 100 элементов. Память, отведенная под остальные 90 элементов, не будет использоваться ни Вашей программой, ни другими программами (по принципу «сам не гам и другому не дам»). А теперь представьте, что все программы поступают таким же образом. Эффективность использования оперативной памяти резко снижается.

    Динамические массивы позволяют решить рассмотренную проблему наилучшим образом. Размер динамического массива можно изменять во время работы программы.

    Динамический массив объявляется без указания границ:

    Переменная DynArray представляет собой ссылку на размещаемые в динамической памяти элементы массива. Изначально память под массив не резервируется, количество элементов в массиве равно нулю, а значение переменной DynArray равно nil.

    Работа с динамическими массивами напоминает работу с длинными строками. В частности, создание динамического массива (выделение памяти для его элементов) осуществляется той же процедурой, которой устанавливается длина строк — SetLength.

    Изменение размера динамического массива производится этой же процедурой:

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

    При уменьшении размера динамического массива лишние элементы теряютяся.

    При увеличении размера динамического массива добавленные элементы не инициализируются никаким значением и в общем случае их значения случайны. Однако если динамический массив состоит из элементов, тип которых предполагает автоматическую инициализацию пустым значением (string, Variant, динамический массив, др.), то добавленная память инициализируется нулями.

    Определение количества элементов производится с помощью функции Length:

    Элементы динамического массива всегда индексируются от нуля. Доступ к ним ничем не отличается от доступа к элементам обычных статических массивов:

    К динамическим массивам, как и к обычным массивам, применимы функции Low и High, возвращающие минимальный и максимальный индексы массива соответственно. Для динамических массивов функция Low всегда возвращает 0.

    Освобождение памяти, выделенной для элементов динамического массива, осуществляется установкой длины в значение 0 или присваиванием переменной-массиву значения nil (оба варианта эквивалентны):

    Однако Вам вовсе необязательно по окончании использования динамического массива освобождать выделенную память, поскольку она освобождается автоматически при выходе из области действия переменной-массива (удобно, не правда ли!). Данная возможность обеспечивается уже известным Вам механизмом подсчета количества ссылок.

    Также, как и при работе со строками, при присваивании одного динамического массива другому, копия уже существующего массива не создается.

    В приведенном примере, в переменную B заносится адрес динамической области памяти, в которой хранятся элементы массива A (другими словами, ссылочной переменной B присваивается значение ссылочной переменной A).

    Как и в случае со строками, память освобождается, когда количество ссылок становится равным нулю.

    Для работы с динамическими массивами вы можете использовать знакомую по строкам функцию Copy. Она возвращает часть массива в виде нового динамического массива.

    Не смотря на сильное сходство динамических массивов со строками, у них имеется одно существенное отличие: отсутствие механизма копирования при записи (copy-on-write).

    Нуль-терминированные строки

    Кроме стандартных строк ShortString и AnsiString, в языке Delphi поддерживаются нуль-терминированные строки языка C, используемые процедурами и функциями Windows. Нуль-терминированная строка представляет собой индексированный от нуля массив ASCII-символов, заканчивающийся нулевым символом #0. Для поддержки нуль-терминированных строк в языке Delphi введены три указательных типа данных:

    Типы PAnsiChar и PWideChar являются фундаментальными и на самом деле используются редко. PChar — это обобщенный тип данных, в основном именно он используется для описания нуль-терминированных строк.

    Ниже приведены примеры объявления нуль-терминированных строк в виде типизированных констант и переменных:

    Переменные типа PChar являются указателями, а не настоящими строками. Поэтому, если переменной типа PChar присвоить значение другой переменной такого же типа, то в результате получится два указателя на одну и ту же строку, а не две копии исходной строки. Например, в результате оператора

    переменная S3 получит адрес уже существующей строки ‘Object Pascal’.

    Для удобной работы с нуль-терминированными строками в языке Delphi предусмотрена директива $EXTENDEDSYNTAX . Если она включена ( ON ), то появляются следующие дополнительные возможности:

    • массив символов, в котором нижний индекс равен 0, совместим с типом PChar;
    • строковые константы совместимы с типом PChar.
    • указатели типа PChar могут участвовать в операциях сложения и вычитания с целыми числами; допустимо также вычитание (но не сложение!) указателей.

    В режиме расширенного синтаксиса допустимы, например, следующие операторы:

    В языке Delphi существует богатый набор процедур и функций для работы с нуль-терминированными строками (см. справочник по среде Delphi).

    Переменные с непостоянным типом значений


    Тип данных Variant

    В среде Delphi определен стандартный тип данных Variant, с помощью которого объявляются переменные с непостоянным типом значений. Такие переменные могут принимать значения разных типов данных в зависимости от типа выражения, в котором используются. Следующий пример хорошо демонстрирует мощь переменных с непостоянным типом значений:

    Значения переменных с типом Variant

    Переменные с непостоянным типом содержат целые, вещественные, строковые, булевские значения, дату и время, массивы и др. Кроме того, переменные с типом Variant принимают два специальных значения: Unassigned и Null.

    Значение Unassigned показывает, что переменная является нетронутой, т.е. переменной еще не присвоено значение. Оно автоматически устанавливается в качестве начального значения любой переменной с типом Variant.

    Значение Null показывает, что переменная имеет неопределенное значение. Если в выражении участвует переменная со значением Null, то результат всего выражения тоже равен Null.

    Переменная с типом Variant занимает в памяти 16 байт. В них хранятся текущее значение переменной (или адрес значения в динамической памяти) и тип этого значения.

    Тип значения выясняется с помощью функции

    Возвращаемый результат формируется из констант, перечисленных в таблице 2.10. Например, следующий условный оператор проверяет, содержит ли переменная строку (массив строк):

    Код типа


    Значение


    Описание


    varEmpty $0000 Переменная содержит значение Unassigned. varNull $0001 Переменная содержит значение Null. varSmallint $0002 Переменная содержит значение типа Smallint. varInteger $0003 Переменная содержит значение типа Integer. varSingle $0004 Переменная содержит значение типа Single. varDouble $0005 Переменная содержит значение типа Double. varCurrency $0006 Переменная содержит значение типа Currency. varDate $0007 Переменная содержит значение типа TDateTime. varOleStr $0008 Переменная содержит ссылку на строку формата Unicode в динамической памяти. varDispatch $0009 Переменная содержит ссылку на интерфейс IDispatch (интерфейсы рассмотрены в главе 6). varError $000A Переменная содержит системный код ошибки. varBoolean $000B Переменная содержит значение типа WordBool. varVariant $000C Элемент варьируемого массива содержит значение типа Variant (код varVariant используется только в сочетании с флагом varArray). varUnknown $000D Переменная содержит ссылку на интерфейс IUnknown (интерфейсы рассмотрены в главе 6). varShortint $0010 Переменная содержит значение типа Shortint varByte $0011 Переменная содержит значение типа Byte. varWord $0012 Переменная содержит значение типа Word varLongword $0013 Переменная содрежит значение типа Longword varInt64 $0014 Переменная содержит значение типа Int64 varStrArg $0048 Переменная содержит строку, совместимую со стандартом COM, принятым в операционной системе Windows. varString $0100 Переменная содержит ссылку на длинную строку. varAny $0101 Переменная содержит значение любого типа данных технологии CORBA Флаги varTypeMask $0FFF Маска для выяснения типа значения. varArray $2000 Переменная содержит массив значений. varByRef $4000 Переменная содержит ссылку на значение.

    Таблица 10. Коды и флаги варьируемых переменных

    позволяет вам преобразовать значение варьируемой переменной к нужному типу, например:

    Пока это все, что нужно знать о типе Variant, но мы к нему еще вернемся при обсуждении технологии COM Automation.

    Delphi + ассемблер

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

    Встроенный ассемблер

    Пользователю предоставляется возможность делать вставки на встроенном ассемблере в исходный текст на языке Delphi.

    К встроенному ассемблеру можно обратиться с помощью зарезервированного слова asm , за которым следуют команды ассемблера и слово end :

    На одной строке можно поместить несколько операторов ассемблера, разделенных двоеточием. Если каждый оператор размещен на отдельной строке, двоеточие не ставится.

    В языке Delphi имеется возможность не только делать ассемблерные вставки, но писать процедуры и функции полностью на ассемблере. В этом случае тело подпрограммы ограничивается словами asm и end (а не begin и end ), между которыми помещаются инструкции ассемблера. Перед словом asm могут располагаться объявления локальных констант, типов, и переменных. Например, вот как могут быть реализованы функции вычисления минимального и максимального значения из двух целых чисел:

    Обращение к этим функциям имеет привычный вид:

    Подключение внешних подпрограмм

    Программисту предоставляется возможность подключать к программе или модулю отдельно скомпилированные процедуры и функции, написанные на языке ассемблера или C. Для этого используется директива компилятора $LINK и зарезервированное слово external. Директива <$LINK >указывает подключаемый объектный модуль, а external сообщает компилятору, что подпрограмма внешняя.

    Предположим, что на ассемблере написаны и скомпилированы функции Min и Max, их объектный код находится в файле MINMAX.OBJ. Подключение функций Min и Max к программе на языке Delphi будет выглядеть так:

    В модулях внешние подпрограммы подключаются в разделе implementation .

    Итоги

    Все, что вы изучили, называется языком Delphi. Мы надеемся, что вам понравились стройность и выразительная сила языка. Но это всего лишь основа. Теперь пора подняться на следующую ступень и изучить технику объектно-ориентированного программирования, без которого немыслимо стать профессиональным программистом. Именно этим вопросом в рамках применения объектов в среде Delphi мы и займемся в следующей главе.

    Трюки с интерфейсами в Delphi

    Трюк 1. Умные Weak ссылки

    Для тех кто не в курсе — Weak (слабые) ссылки — ссылки, не увеличивающие счетчик. Допустим у нас есть дерево:
    Если бы внутри класса, реализующего интерфейс INode родитель и потомки хранились бы так:
    то дерево бы никогда не уничтожилось. Родитель держит ссылки на детей (и тем самым увеличивает им счетчик), а дети на родителя. Это классическая проблема циклических ссылок, и в этом случае прибегают к weak ссылкам. В новых XE делфях можно написать так:
    а в старых — хранят Pointer:
    Это позволяет обойти автоинкремент счетчиков, и теперь если мы потеряем указатель на родителя — все дерево прибьется, что и требовалось получить.

    У weak ссылок есть другая сторона. Если вдруг у вас уничтожился объект, а кто-то держит на него weak ссылку — вы не можете это отследить. По факту — у вас просто мусорный указатель, при обращении по которому будет ошибка. И это ужасно. Нужно лепить какую-то систему чистки этих самых ссылок.

    Но есть очень элегантное решение. И вот как это работает. Мы пишем интерфейс weak ссылки и класс, реализующий его:
    Тут обычный typecast до Pointer-а. Именно та weak ссылка, о которой я рассказывал выше. Но ключевой метод — IsAlive, который возвращает True — если объект на который ссылается weak ссылка — еще существует. Осталось только понять как красиво почистить FOwner.
    Пишем интерфейс:
    который возвращает weak ссылку и пишем класс, реализующий этот интерфейс:
    Мы просто добавили метод, раздающий всем одну weak ссылку. А поскольку сам объект всегда знает о своей weak ссылке — он просто чистит её в своем деструкторе. Осталось теперь только наследоваться от TWeaklyInterfacedObject вместо TInterfacedObject, и все. Никаких больше unsafe приведений типов, выстрелов в ногу, и нецензурной брани.

    Трюк 2. Механизм подписчиков

    Если вы еще не велосипедили систему плагинов в делфи и не использовали MVC паттернов — то вы счастливчик. В делфи все события — это просто один или два указателя на функцию(и инстанс). Поэтому если вы создали класс, сделали ему OnBlaBla свойство — то только кто-то один может узнать, что этот самый BlaBla наконец то произошел. Посему все начинают пилить свой механизм подписок, и часто тонут в отладке этих самых подписок.
    События основанные на интерфейсах обычно реализуют так. Делают отдельный евент интерфейс, к примеру:
    и передают его, вместо классического procedure of object; например в пару Subscribe/Unsubscribe методов:
    Когда код разрастается, а интерфейс IMouseEvents чуть-чуть меняется (например добавили метод) — начинает сильно напрягать рефакторинг. Например один и тот же IMouseEvents используется в IForm, IButton, IImage и прочей нечисти. Везде надо правильно поправить подписку, добавить обход по подписчикам и т.п.
    Я использую следующий трюк. Пишем интерфейс:
    Класс который будет реализовывать этот интерфейс (пусть это будет TBasePublisher) умеет только добавлять и удалять из списка какие-то интерфейсы. В дальнейшем мы пишем классы, которые я называю броадкастеры. Вот у нас есть евент интерфейс:
    Мы наследуемся от TBasePublisher и реализуем вот такой броадкастер:
    то есть сам броадкастер у нас реализует евент интерфейс, и в реализации просто рассылает всем подписчикам тот же евент. Преимущество — все реализовано в одном месте, оно не скомпилируется если вы хоть немного поменяете IGraphEvents. Теперь зоопарк IForm, IButton, IImage просто создают внутри себя TGraphEventsBroadcaster и вызывают его методы, как будто у IForm всего один подписчик.

    Трюк 3. Умные Weak ссылки + механизм подписчиков

    Но все что я описал выше про подписчиков — плохо. Дело в том, что тут сплошь и рядом будут циклические ссылки, вы замахаетесь разбираться с порядком финализации и отписыванием. Вы добавите слабые ссылки, но погрязнете в отладке мусорных ссылок. Вот тут то и пригодятся умные слабые ссылки, описанные в самом начале. Мы просто пишем вот такой интерфейс издателя (который принимает IWeakly из начала статьи):
    Внутри себя издатель TBasePublisher хранит массив слабых ссылок TWeakRefArr = array of IWeakRef;
    А броадкастер теперь только проверяет слабую ссылку на жизнеспособность, получает нормальную, и направляет евент в неё. Броадкастер поменялся вот так:
    Теперь нас абсолютно не заботит порядок отписывания. Если мы забыли отписаться — ничего страшного. Все стало прозрачно, как в дотнете и должно было быть.

    Трюк 4. Перегрузка в помощь

    Последний штрих:
    Я думаю он понятен без слов. Мы просто делаем MyForm.MyEvents + MySubscriber; — мы подписались. Вычли: MyForm.MyEvents — MySubscriber; — отписались.

    Статья была бы не полной, если бы я не предоставил пример того как это работает. Поэтому вот пример. В кратце:
    Программа создает 4 окна. На любом из окон можно рисовать мышкой. Нарисованные объекты добавляются в список, и через механизм подписок все окна уведомляются о изменении. Поэтому нарисованная фигура появляется на всех формах. На каждой форме можно выбрать собственную толщину линий с помощью трекбара.

    Как разрешить ошибку Delphi: Несовместимые типы: ‘PWideChar’ и ‘Pointer’

    У меня есть эта часть кода от Delphi 7:

    В Delphi XE4 он дает следующую ошибку компиляции:

    Как этот код должен быть обновлен для корректной работы в XE4?

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

    При отключенных указателях типа ваш код компилируется. С включенными указателями, отмеченными типом, ваш код не компилируется, что вам нужно, потому что ваш код недействителен.

    Теперь о типах, о которых идет речь. Они определены в блоке Windows следующим образом:

    Преимущество использования указателей с проверкой типов заключается в том, что компилятор может сказать вам, что то, что вы делаете, недействительно. Он знает, что lpRgnData^.Buffer[0] имеет тип Byte и поэтому @(lpRgnData^.Buffer[0]) имеет тип ^Byte . И он знает, что не совместим с PChar который является псевдонимом для PWideChar , то есть ^WideChar .

    Исправьте свой код, изменив тип PC на ^Byte или PByte .

    Указатели Delphi

    Указатель (pointer) представляет собой переменную, содержащую местоположение (адрес) участка памяти. В этой главе уже встречался указатель, когда рассматривался тип PChar. Тип указателя в Object Pascal называется Pointer, или нетипизированный указатель, поскольку он содержит адрес определенного места в памяти, а компилято ру ничего не известно о данных, располагающихся по этому адресу. Но применение таких “указателей вообще” противоречит концепции строгого контроля типов, по этому в основном придется работать с типизированными указателями, т.е. с указате лями на данные конкретного типа.

    Указатели — тема достаточно сложная для новичка, а приложения Delphi можно соз-

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

    Типизированные указатели объявляются в разделе Type программы с использова нием символа ^ (оператора указателя) или ключевого слова Pointer. Типизирован ные указатели позволяют компилятору отслеживать, с каким именно типом данных происходят действия и не выполняются ли при этом некорректные операции. Вот примеры объявлений типизированных указателей:

    PInt = ^Integer; // PInt – указатель на Integer

    Foo = record // Тип – запись

    PFoo = ^Foo; // PFoo – указатель на объект типа foo

    Программисты на языке C++ могут заметить схожесть оператора ^ Object Pascal и оператора * языка C++. Тип Pointer в Object Pascal соответствует типу void * языка C.

    Запомните, что переменная типа Pointer всегда содержит только адрес области па мяти. О выделении памяти для структуры, на которую будет указывать такой указатель, программист должен позаботиться сам (для этого можно воспользоваться одной из функций, перечисленных в табл. 2.6).

    Когда указатель не указывает ни на что (его значение равно 0), то о таком указателе говорят, что его значение равно Nil, а сам указатель называют нулевым, или пустым указателем.

    Если необходимо получить доступ к данным, на которые указатель указывает, то можно воспользоваться оператором ^, следующим за именем этой переменной. Такой метод называется разрешением указателя (dereferencing), а также косвенным доступом, взя тием значения, разыменованием и ссылкой. Ниже приведен пример работы с указателями.

    New(Rec); // Выделить память для новой записи Rec

    Rec^.I := 10; // Поместить в нее данные. Обратите внимание

    Rec^.S := ‘And now for something completely different.’;

    Dispose(Rec); // Не забывайте освобождать память!

    end.Когда использовать функцию New()

    Функция New() используется при выделении памяти для указателя на структуру дан- ных известного размера. Поскольку компилятору известен размер структуры, для кото- рой необходимо выделить память, то при выполнении функции New() будет распре- делено достаточное количество байтов, причем такой способ выделения более кор- ректен и безопасен, чем вызов функции GetMem() или AllocMem(). В то же время, никогда не используйте функцию New() для выделения памяти для типов Pointer или PChar, так как в этом случае компилятору не известно, какое количество памяти долж- но быть выделено. И не забывайте использовать функцию Dispose() для освобожде- ния памяти, выделенной с помощью функции New().

    Для выделения памяти структурам, размер которых на этапе компиляции еще не из-

    вестен, используются функции GetMem() и AllocMem(). Например, компилятор не может определить заранее, сколько памяти потребуется выделить для структур, зада- ваемых переменными типа PChar или Pointer, что связано с самой природой этого типа данных. Самое важное — не пытаться манипулировать количеством памяти, большим, чем было выделено реально, поскольку наиболее вероятным результатом таких действий будет ошибка доступа к памяти (access violation). Для освобождения памяти, выделенной с помощью вышеупомянутых функций, используйте функцию FreeMem(). Кстати, для выделения памяти лучше пользоваться функцией Al- locMem(), так как она всегда инициализирует выделенную память нулевыми значе- ниями.

    Один из аспектов работы с указателями в Object Pascal, который существенно от личается от работы с ними в языке C, — это их строжайшая типизация. Так, в приве денном ниже примере переменные a и b не совместимы по типу.

    В то же время в эквивалентном описании на языке C эти переменные вполне со

    int *bObject Pascal создает уникальный тип для каждого объявления указателя на тип (pointer to type), поэтому для совместимости по типу следует объявлять не только пе ременные, но и их тип:

    PtrInteger = ^Integer; // Создать именованный тип

    a, b: PtrInteger; // Теперь a и b совместимы по типу

    Источник: Тейксейра, Стив, Пачеко, Ксавье. Borland Delphi 6. Руководство разработчика. : Пер. с англ. — М. : Издательский дом “Вильямс”, 2002. — 1120 с. : ил. — Парал. тит. англ.

    Pointer — Тип Delphi

    Как следует из названия, переменные — указатели это особый тип переменных, которые не содержат значения, а указывают на них — на ту ячейку памяти, где они фактически располагаются. И хотя справедливо считается, что использование указателей может приводить к трудно контролируемым ошибкам в программе, всё же переменные — указатели это очень эффективный инструмент для управления объектами в оперативной памяти компьютера.

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

    Описание переменных — указателей

    Указатель описывается ключевым словом Pointer. По первой букве ключевого слова принято называть переменные — указатели с первой буквы P:

    Ключевым словом Pointer задаётся так называемый не типизированный указатель, по аналогии с не типизированным файлом. Не типизированный указатель содержит просто адрес некой ячейки памяти. Объект, располагающийся начиная с этой ячейки, соответственно может быть совершенно любого типа и размера.

    Также в Delphi существуют и типизированные указатели. Они могут указывать на объект соответствующего типа и размера. Именно «могут указывать», потому что это по прежнему адрес одной — первой ячейки области памяти, где располагается объект. И далее его использование в программе зависит от программиста!
    Итак, типизированный указатель описывается ключевым словом означающим данный тип, перед которым ставится значок ^ :

    Также можно описать любой свой тип, и задать переменную-указатель данного типа:

    Естественно, можно определить тип, для описания через него переменных — указателей. Делается это в том числе и потому, что, например, в процедурах и функциях в качестве параметров можно использовать только заранее описанный тип данных. Например, следующее описание задаёт функцию с параметром, являющимся указателем ранее описанного типа, результат которой также является указателем данного типа:

    Ну вот, надеюсь теперь вы не боитесь разнообразных вариантов описания таких переменных как указатели.

    Использование переменных — указателей

    1. Указателю можно присвоить значение другого указателя. В результате оба указателя будут указывать на одну и ту же ячейку памяти. Также указателю можно присвоить пустое значение с помощью ключевого слова nil :

    Указатель со значением nil не адресует никакой ячейки памяти и единственное, что с ним можно сделать — это сравнить с другим указателем или со значением nil .

    2. Значение типизированного указателя можно увеличить или уменьшить на размер области памяти, занимаемой объектом данного типа. Для этого служат операции инкремента и декремента:

    Попытка выполнить операциии inc либо dec с не типизированным указателем вызовет ошибку на этапе компиляции, так как компилятору неизвестно, насколько именно изменять значение указателя.

    3. Процедурой New можно создать область памяти соответствующего типа и присвоить её адрес указателю (инициировать указатель):

    Поскольку с областью памяти, созданной с помощью процедуры New , не связана ни одна переменная, но там содержится реальное используемое значение, то можно считать, что это значение связано с некой «безымянной переменной». Обращаться к ней по имени переменной невозможно, а можно оперировать только используя указатель.

    Также задать указателю адрес объекта можно с помощью операции, называемой «взятие адреса», которая обозначается значком @ . При этом создавать область памяти уже не нужно, так как она создана предварительным описанием данного объекта:

    4. Если область памяти уже создана и её адрес присвоен указателю, то в ячейку памяти, адресуемую данным указателем, можно записать значение объекта, соответствующего типу указателя. Для этого служит операция, обозначаемая также значком ^ , стоящим после имени указателя, например: P^ . Эта операция называется «разыменование указателя». Также с помощью этой операции со значением в данной ячейке памяти можно делать всё что нужно:

    С обычными переменными всё просто, но возникает вопрос, как получить значение по адресу указателя, если тип переменной — запись с несколькими полями? Аналогично:

    А теперь уберите стрелку возле PRec: PRec.S:=’Строка данных’; Вы увидите, что никакой ошибки ни компилятор, ни выполнение программы не показали! Выходит, выражения PRec^.S и PRec.S аналогичны.

    Далее, а как получить значение, если указатель это элемент массива, например:

    Ни PArray^[10] , ни PArray[10]^ не являются правильными выражениями. Ну конечно, нужно использовать скобки:

    5. Память, выделенную процедурой New, всегда нужно явно освобождать. Освободить область памяти, адресуемую указателем, инициированным с помощью New, можно процедурой Dispose :

    При выполнении процедуры Dispose указатель снова приобретает неопределённое значение, не равное даже nil, и его использование может привести к неопределённым результатам, даже к краху программы.

    6. Указатели особенно эффективны для манипуляций с объектами большого размера, например, записями с многочисленными полями. Для сортировки массива таких записей создаётся массив указателей, каждый из элементов которого указывает на соответствующий элемент массива записей. И вместо перемещения элемента массива записей перемещается соответствующий элемент массива указателей, на что требуется гораздо меньше времени.

    Удачи!
    Встретимся в следующем уроке!

    Источник: www.thedelphi.ru
    Автор: Савельев Александр
    Опубликовано: 4 Августа 2013
    Просмотров: 25773

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

    Как определить «UCHAR * POINTER_32» и «VOID * POINTER_32» типы в Delphi?

    Задний план:

    Переводя IP_OPTION_INFORMATION32 и ICMP_ECHO_REPLY32 структуры для 64-битного компилятора я stucked с типами данных , чтобы использовать там. Определения структуры из ссылки:

    Структура IP_OPTION_INFORMATION32:

    Я хотел бы перевести этот путь (для Delphi XE2, 64-разрядные целевой платформы). Как вы можете видеть, я не знаю , какой тип использовать для OptionsData поля структуры:

    Структура ICMP_ECHO_REPLY32:

    Для Delphi XE2 64-битной целевой платформы я хотел бы написать:

    Вопрос:

    Как бы вы определили UCHAR * POINTER_32 и VOID * POINTER_32 тип в Delphi для 64-битной платформы цели? Насколько я знаю, нет 32-битного типа указателя для 64-битных целевой платформы и я просто не нравлюсь быть определенно , например , в качестве Int32 типа :-)

    Что является наиболее точным переводом для указанных типов?

    Вопрос о том, что POINTER_32 рассматривается в другом переполнением стека вопрос: POINTER_32 — что это такое, и почему?

    Вы могли бы использовать его при выполнении Interop с структурами, которые определены в другом процессе, тот, который имеет 32-битные указатели.

    Вы не имеете эквивалент __ptr32 в Delphi , так что вы не должны просто нет выбора, кроме как объявить его как 32 битное целое число. Я хотел бы использовать без знака типа.

    В этом случае UInt32 (или кардинал, DWORD и т.д. — все без знака 32 бит) будет в порядке. Все , что вам нужно сделать , это объявить без знака 32 бита в правильном положении структуры (записи). Процессор не будет заботиться, компилятор не будет заботиться (вы можете типаж указатель при необходимости) это на самом деле указатель или нет. Даже подписал 32 бит будет работать , если не будет делать некоторую математику со значением и флаги устройства обработки проверки. И еще одна вещь , в структуре переводов из C в Delphi: убедитесь , что первоначальное выравнивание было 4 байта (32 бит за единицу), потому что в record отличие от packed record (без выравнивания) по умолчанию не используя такой же выравнивание 4 байта. Иначе вы можете иметь проблемы, применяя свои записи для данных , таких как

    Илон Маск рекомендует:  Что такое код itoa
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL