Чтение онлайн

на главную

Жанры

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Значение

''
(пустая строка) для строки
AnsiString
означает, что память для нее вообще не выделена, а указатель имеет значение
nil
. Для типа
PChar
пустая строка — это ненулевой указатель на символ
#0
. Нулевой указатель также может рассматриваться как пустая строка, но не всегда — иногда это рассматривается как отсутствие какого бы то ни было значения, даже пустого (аналог NULL в базах данных). Чтобы решить это противоречие, функция
_LStrToPChar
проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не
nil
, а указатель на символ
#0
,
который специально для этого размещен в сегменте кода. Таким образом, для пустой строки
PChar(S) <> Pointer(S)
, потому что приведение строки
AnsiString
к указателю другого типа — это нормальное приведение типов без дополнительной обработки значения.

3.3.5. Побочное изменение

Из-за того, что две одинаковые строки

AnsiString
разделяют одну область памяти, на неожиданные эффекты можно натолкнуться, если модифицировать содержимое строки в обход стандартных механизмов. Следующий код (листинг 3.30, пример SideChange на компакт-диске) иллюстрирует такую ситуацию.

Листинг 3.30. Побочное изменение переменной
S2
при
изменении
S1

procedure TForm1.Button1Click(Sender: TObject);

var

 S1, S2: string;

 P: PChar;

begin

 S1 := 'Test';

 UniqueString(S1);

 S2 := S1;

 P := PChar(S1);

 P[0] := 'F';

 Label1.Caption := S2;

end;

В этом примере требует комментариев процедура

UniqueString
. Она обеспечивает то, что счетчик ссылок на строку будет равен единице, т.е. для этой строки делается уникальная копия. Здесь это понадобилось для того, чтобы строка
S1
хранилась в динамической памяти, а не в сегменте кода, иначе мы получили бы Access violation, как и во втором случае рассмотренного ранее примера Constants (см. листинг 2.17).

В результате работы этого примера на экран будет выведено не Test, a Fest, хотя значение

S2
, казалось бы, не должно меняться, потому что изменения, которые мы делаем, касаются только
S1
. Но более внимательный анализ подсказывает объяснение: после присваивания
S2 := S1
счетчик ссылок строки становится равным двум, а сама строка разделяется двумя указателями:
S1
и
S2
. Если бы мы попытались изменить непосредственно
S2
, то сначала была бы создана копия этой строки, а потом сделаны изменения в этой копии, а оригинал, на который указывала бы
S2
, остался без изменений. Но, использовав
PChar
, мы обошли механизм копирования, поэтому строка осталась в единственном экземпляре, и изменения затронули не только
S1
, но и
S2
.

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

UniqueString
и только потом работать с ней через
PChar
, если в этом есть необходимость.

Рассмотрим еще один пример, практически не отличающийся от предыдущего (листинг 3.31).

Листинг 3.31. Отсутствие побочного изменения переменной
S2
при изменении
S1

procedure TForm1.Button2Click(Sender: TObject);

var

 S1, S2: string;

 P: PChar;

begin

 S1 := 'Test';

 UniqueString(S1);

 S2 := S1;

 P := @S1[1];

 P[0] := 'F';

 Label1.Caption := S2;

end;

В

этом случае на экран будет выведено Test, т.е. побочного изменения переменной не произойдёт, хотя переменная
S1
по прежнему изменяется в обход стандартных механизмов Delphi.

Вся разница между двумя примерами заключается в том, как получается указатель на строку. В первом примере он является результатом приведения типа строки к

PChar
, а во втором — операции взятия адреса первого символа строки. По идее, это должно приводить к одинаковому результату, однако компилятор, зная, что указатель получается, возможно, для того, чтобы с его помощью менять содержимое строки, вставляет сюда неявный вызов
UniqueString
. В результате этого для
S1
выделяется в динамической памяти другая область, чем для
S2
, и манипуляции с содержимым
S1
больше не затрагивают
S2
.

Неявный вызов

UniqueString
при обращении к символу строки по индексу выполняется всегда, когда у компилятора есть основания ожидать изменения строки. Это снижает производительность, т.к. многие вызовы
UniqueString
оказываются излишними. Например, если выполняется посимвольная модификация строки в цикле,
UniqueString
будет вызываться на каждой итерации цикла, хотя достаточно одного вызова — перед началом цикла. Поэтому в тех случаях, когда производительность критична, посимвольную модификацию строки лучше выполнять низкоуровневыми методами, обращаясь к символам через указатели и обеспечив уникальность строки самостоятельно. Что же касается скорости получения указателя, то тут наиболее быстрым является приведение переменной типа
AnsiString
к типу
Pointer
, т.к. это вообще не приводит к генерации дополнительного кода. Приведение к типу
PChar
работает медленнее потому, что выполняется неявный вызов функции
_LStrToPChar
, а получение адреса первого символа снижает производительность из-за неявного вызова
UniqueString
.

Примечание

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

3.3.6. Нулевой символ в середине строки

Хотя символ

#0
и добавляется в конец каждой строки
AnsiString
, он уже не является признаком ее конца, т.к. длина строки хранится отдельно. Это позволяет размещать символы
#0
и в середине строки. Но нужно учитывать, что полноценное преобразование такой строки в
PChar
невозможно — это иллюстрируется примером Zero на компакт-диске (листинг 3.32).

Листинг 3.32. Потеря остатка строки после символа
#0

procedure TForm1.Button1Click(Sender: TObject);

var

 S1, S2, S3: string;

 P: PChar;

begin

 S1 := 'Test'#0'Test';

 S2 := S1;

 UniqueString(S2);

 P := PChar(S1);

 S3 := P;

 Label1.Caption := IntToStr(Length(S2));

Поделиться:
Популярные книги

Дайте поспать! Том III

Матисов Павел
3. Вечный Сон
Фантастика:
фэнтези
5.00
рейтинг книги
Дайте поспать! Том III

Путь Чести

Щукин Иван
3. Жизни Архимага
Фантастика:
фэнтези
боевая фантастика
6.43
рейтинг книги
Путь Чести

Энфис 2

Кронос Александр
2. Эрра
Фантастика:
героическая фантастика
рпг
аниме
5.00
рейтинг книги
Энфис 2

Сумеречный Стрелок 2

Карелин Сергей Витальевич
2. Сумеречный стрелок
Фантастика:
городское фэнтези
попаданцы
аниме
5.00
рейтинг книги
Сумеречный Стрелок 2

Сводный гад

Рам Янка
2. Самбисты
Любовные романы:
современные любовные романы
эро литература
5.00
рейтинг книги
Сводный гад

Книга пяти колец. Том 3

Зайцев Константин
3. Книга пяти колец
Фантастика:
фэнтези
попаданцы
аниме
5.75
рейтинг книги
Книга пяти колец. Том 3

Диверсант

Вайс Александр
2. Фронтир
Фантастика:
боевая фантастика
космическая фантастика
5.00
рейтинг книги
Диверсант

Ученичество. Книга 2

Понарошку Евгений
2. Государственный маг
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Ученичество. Книга 2

Титан империи 3

Артемов Александр Александрович
3. Титан Империи
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Титан империи 3

Последний попаданец 9

Зубов Константин
9. Последний попаданец
Фантастика:
юмористическая фантастика
рпг
5.00
рейтинг книги
Последний попаданец 9

Измена. Право на сына

Арская Арина
4. Измены
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. Право на сына

Неестественный отбор.Трилогия

Грант Эдгар
Неестественный отбор
Детективы:
триллеры
6.40
рейтинг книги
Неестественный отбор.Трилогия

Хозяйка дома на холме

Скор Элен
1. Хозяйка своей судьбы
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Хозяйка дома на холме

Гром над Академией. Часть 2

Машуков Тимур
3. Гром над миром
Фантастика:
боевая фантастика
5.50
рейтинг книги
Гром над Академией. Часть 2