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

на главную - закладки

Жанры

О чём не пишут в книгах по 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));

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

Долгий путь домой

Русич Антон
Вселенная EVE Online
Фантастика:
космическая фантастика
попаданцы
6.20
рейтинг книги
Долгий путь домой

Убивать чтобы жить 6

Бор Жорж
6. УЧЖ
Фантастика:
боевая фантастика
космическая фантастика
рпг
5.00
рейтинг книги
Убивать чтобы жить 6

Возвышение Меркурия. Книга 3

Кронос Александр
3. Меркурий
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 3

Фиктивная жена

Шагаева Наталья
1. Братья Вертинские
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Фиктивная жена

Здравствуй, 1985-й

Иванов Дмитрий
2. Девяностые
Фантастика:
альтернативная история
5.25
рейтинг книги
Здравствуй, 1985-й

Барон Дубов 4

Карелин Сергей Витальевич
4. Его Дубейшество
Фантастика:
юмористическое фэнтези
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Барон Дубов 4

Лекарь для захватчика

Романова Елена
Фантастика:
попаданцы
историческое фэнтези
фэнтези
5.00
рейтинг книги
Лекарь для захватчика

Идеальный мир для Лекаря 24

Сапфир Олег
24. Лекарь
Фантастика:
городское фэнтези
попаданцы
5.00
рейтинг книги
Идеальный мир для Лекаря 24

Ваше Сиятельство 9

Моури Эрли
9. Ваше Сиятельство
Фантастика:
боевая фантастика
попаданцы
стимпанк
аниме
фэнтези
5.00
рейтинг книги
Ваше Сиятельство 9

Господин следователь

Шалашов Евгений Васильевич
1. Господин следователь
Детективы:
исторические детективы
5.00
рейтинг книги
Господин следователь

Академия проклятий. Книги 1 - 7

Звездная Елена
Академия Проклятий
Фантастика:
фэнтези
8.98
рейтинг книги
Академия проклятий. Книги 1 - 7

Полковник Гуров. Компиляция (сборник)

Макеев Алексей Викторович
Полковник Гуров
Детективы:
криминальные детективы
шпионские детективы
полицейские детективы
боевики
крутой детектив
5.00
рейтинг книги
Полковник Гуров. Компиляция (сборник)

Первый среди равных. Книга V

Бор Жорж
5. Первый среди Равных
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Первый среди равных. Книга V

Землянка для двух нагов

Софи Ирен
Фантастика:
космическая фантастика
5.00
рейтинг книги
Землянка для двух нагов