Указатели в Delphi

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

 

Но прежде чем что-то объяснять, рассмотрим, зачем нужны указатели. Давайте вспомним про процедуры, а именно, как происходит их вызов. Допустим, у вас есть процедура с именем муРгос, у которой есть два параметра: число и строка. Как происходит вызов такой процедуры и как ей передаются эти параметры? Очень просто. Сначала параметры принимаются в стек (напомню, что стек — это область памяти для хранения временных или локальных переменных). Первым заносится первый параметр, затем второй и после этого вызывается процедура. Прежде чем процедура начнет свое выполнение, она извлекает эти параметры из стека в обрат­ном порядке.

 

Теперь вспомним о наших параметрах. Первый — это число, которое будет за­нимать 2 байта. Когда происходит его запись в стек, оно займет там свои положен­ные 2 байта. Второй параметр — строка. Каждый символ строки — это отдельный байт. Допустим, что строка состоит из 10 символов. Это значит, что для передачи такой строки в процедуру в стеке понадобится 10 байт плюс 1 байт для указания конца строки или ее размера (это зависит от типа строки). Всего для передачи в процедуру понадобится в стеке как минимум 12 байт. Это не так уж и много, по­этому такое можно себе позволить.

 

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

А если нам нужно передать в процедуру фотографию размером в 3 мегабайта? Что ее тоже копировать в стек? Несколько фотографий высокого качества — и стек закончится.

 

Выход из сложившейся ситуации достаточно прост. Можно не передавать стро­ку, а только передать указатель на область памяти, где находится эта строка. Лю­бой указатель занимает всего 4 байта, а это уже существенная экономия. Мы про­сто даем нашей процедуре понять, где найти строку.

Указатель в Delphi объявляется как Pointer. Например, давайте объявим пере­менную р типа указатель:

var р:Pointer

Для того чтобы получить адрес переменной или объекта, необходимо перед его именем поставить знак Например, у вас есть строка str, и чтобы присвоить ее адрес в указатель р, надо выполнить следующее: p:=@str. Теперь в указателе нахо­дится адрес строки. Если вы будете напрямую читать указатель, то увидите адрес, а для того чтобы увидеть содержащиеся по этому адресу данные, надо разымено­вывать указатель. Для этого надо написать р^. Итак, мы пришли к следующему:

  • р: = @str — получить адрес строки;
  • р — указатель на строку;
  • р^ — данные, содержащиеся по адресу, указанному в р.

Давайте создадим маленькое приложение, которое будет работать с указателя­ми. Для этого на форму надо поместить кнопку с заголовком "Работа со ссылками" и строку ввода.

Для события — нажатие кнопки Работа со ссылками — напишем код, пред­ставленный в листинге:

 

 

В этом примере в первой строке мы присваиваем указателю р ссылку на стро­ку str. После этого меняем содержимое строки. В последней строке выводится содержащийся по адресу р текст. Для этого приходится явно указывать, что по адресу р находится именно строка string (р^). Вспомните приведение схожих типов. Таким же образом можно приводить строку string к pchar. Здесь указы­вается, что по адресу переменной р находится строка, а не какой-нибудь другой тип данных. Это необходимо, потому что данные, расположенные по определен­ному адресу, могут иметь совершенно любой тип. Как видите, "жесткое" указа­ние типа похоже на преобразование типов, поэтому никаких проблем с этим не должно возникнуть.

 

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

 

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

 

Любой переменной-указателю можно присвоить нулевое значение, только это не число 0, a nil — нулевой указатель, например, p:=nil (в принципе, nil это тот же О, просто используется для обнуления указателей). Когда вы присваиваете нулевое зна­чение, то как бы уничтожаете ссылку. Точно так же, если переменной-объекту при­своить нулевое значение, вы его уничтожите. Никогда не обнуляйте переменные ука­затели, которые указывают на существующие объекты. Сначала уничтожьте объекты (освободите память), а потом можно указателю присвоить nil.

 

А зачем указателям присваивать нулевое значение? В принципе, на память это не влияет, но поможет с точки зрения безопасности. Дело в том, что после освобо­ждения памяти, указатель содержит какое-то число (какой-то адрес), но данные по этому адресу уже не действительны. Если обратиться по ним, то может произойти ошибка. Чтобы было видно, что указатель недействительный, после освобождения обнуляйте указатель.

 

До Windows 2000 в программах Windows можно было использовать один инте­ресный трюк— после освобождения памяти, ее еще можно было использовать в течение короткого промежутка времени (пока другая программа не выделит себе этот же участок). Но потом разработчики посчитали, что это небезопасно, и запре­тили использование памяти после ее освобождения.

 

СОВЕТ. В Windows нет сборщиков мусора, которые существуют в Java, и если объект не освободит память, то будет происходить ее потеря. Бывают случаи, когда обнуле­ние указателя реально освобождает память, но это происходит далеко не всегда. Именно поэтому обнулять можно только объекты СОМ (о них мы поговорим отдель­но). Их обнуление ведет к освобождению памяти.

dle

Помоги проекту! Расскажи друзьям об этом сайте: