Архив рубрики «Delphi»

Указатели в Delphi. Часть вторая.

20.04.2009

Операция @

Операция @ позволяет получить номер ячейки (адрес) переменной. В верхнем примере @i будет равно 54564786.

Операция p := @i занесёт данные об адресе переменной i в указатель p.

Значок ^.

Когда значок ^ стоит после названия переменной-указателя (p^), он обозначает, что указатель разыменовывают. Как я писал выше, обращаются не к данным в переменной p, а к данным, на которые указывает переменная p.
То есть p^ и i - одно и то же. Для нас. Но не для компилятора. Что бы компилятор понял, что можно делать с p^, что бы он знал, на что собственно указывает указатель (понятно, что он всегда указывает на какую-то ячейку памяти, но там может находиться начало любой переменной или объекта, а может не находится вообще ничего интересно, либо мусор, либо может быть ‘середина’ переменной, либо вообще кусок кода, а не данные. То есть, что в поинтер положили, на то он и указывает. А уж что конкретно там лежит, на что он указывает - известно только Богу. Если вы - хороший программист, то еще и вам), он должен быть типизированным. То есть он должен быть не pointer, а, например, PInteger. Тогда p^ будет распознано компилятором именно как переменная типа integer.

Если мы предварительно инициализировали указатель p адресом переменной i, написав p := @i, то после этого мы можем к i обращаться как i и как p^.

С ним (с p^) можно делать любые операции, которые возможны с integer’ом. При этом сам указатель p не меняется, меняются данные в переменной, на которую он указывает, то есть в переменной i.

Кроме этого значок ^ может использоваться в описании типа указателей в секции ‘type’, вот несколько строк из модуля system.pas:


type
PAnsiString = ^AnsiString;
end;

Здесь описывается тип PAnsiString. Тип PAnsiString описывается, как указатель на AnsiString. Именно этой строкой компилятору дают понять, что все типизированные указатели типа PAnsiString могут указывать только на переменные типа AnsiString. Это позволяет компилятору проверят верность передачи параметров в процедуры и верность работы с разыменованными указателями.

Операции сложения/вычитания с указателями.

Так как, собственно, указатели - это самые обычные 32-х разрядные числа, ничто не мешает их складывать и вычитать. В компиляторе Делфи, однако, есть ограничение - такие операции возможны только с указателями типа PChar.

Например, с указателем p: pchar можно написать p := p + 10;
Что при этом произойдет. Допустим, указатели изначально указывал на ячейку 54564786. После команды p := p + 10; он будет указывать на ячейку 54564796. Вполне вероятно, что в этой ячейке может содержаться 11-й символ массива символов s. Так же вероятно (в случае, если размер s меньше 11-ти), что в этой ячейке будет либо мусор, либо уже другая переменная, либо эту ячейку вообще нельзя читать и/или писать. Тогда, в лучшем случае можно получить т.н. AV - acess violation, в худшем - этот же AV рано или поздно появится у заказчика, который работает на другом конце Земли. Поэтому в случае применения операций сложения/вычитания с указателями желательно каким-то образом следить за границами массивов.

Inc(p) инкрементирует указатель. То есть добавляет к нему единицу, ‘продвигая’ его на SizeOf(Type) ячеек вверх. Инкремент (Inc) и декремент (Dec) возможен с любыми типизированными указателями.

Указатели в Delphi. Часть первая.

20.04.2009

Примем платформу - Intel + Windows. Считаем, что указатель 32 бита, память - линейна и непрерывна, её 4 гигабайта. Как менеджер памяти винды и делфи это разруливают, что бы оно почти так было в реальном приложении - это их проблемы. За это им уплачено.
Тогда всё просто. Есть т.н. ячейки памяти. Их, соответственно, 4 с немногим миллиарда. Они пронумерованы с нулевой по 4-х миллиардную. Все ячейки - 8-ми битные (1 байт).
Допустим, есть 32-х битная переменная i, первый байт из четырёх которой лежит в ячейке (по адресу) номер 54564786.
Допустим, также есть некоторая переменная p типа pointer. Она - 32-х битная, то есть занимает 4 байта. И лежит где-то в этой же памяти.
Так вот, если в переменную p записать число 54564786, это и будет указатель на переменную i. Затем, ‘получив’ число 54564786 из переменной p можно добраться до переменной i, ‘взяв’ данные по адресу 54564786 - операция называется ‘разыменовывание указателя’.

Nil.

Изначально в инициализированных переменных содержаться нули. Если в переменную p до того, как из неё будут читать, ничего не писали, то говорят, что указатель не инициализирован. Т.е. указывает ‘в никуда’, хотя на самом деле указывает на 0-ю ячейку). Если попробовать разыменовать указатель, который указывает на ячейку номер 0, это приведет к исключению.

Как это работает.

Была создана специальная область памяти - ловушка (64 кб), находящаяся в самом начале адресного пространства. Т.е. от ячейки номер 0 и выше. Создана, что бы ловить ошибки при работе с неинициализированными или неверно инициализированными указателями. При попытке доступа в эту область происходит исключение.

Виды указателей.

Бывают двух видов - типизированные и нетипизированные. Нетипизированный - это обычный Pointer. Типизированные - все остальные. Например, PChar - указатель на Char. Разницы между ними принципиальной нет. Это те же 4 байта, в которые записан адрес переменной. Только, например, в случае PChar, это может быть не любая переменная, а только переменная типа Char.
Реализуется это на уровне компилятора. После того, как компилятор собрал проект в экзешник, информация о том, что p - это был указатель на Byte, или на Char, или на Integer, нигде не сохраняется.

Естественно, в памяти переменную типа Char от переменной типа Byte отличить невозможно. Соответственно, указатели PByte и PChar (как и любые другие) в памяти - это совершенно одинаковые и ничем не выдающиеся переменные.