P: PChar;
begin
S:= '';
P:= PChar(S);
if Pointer(S) = P then Label1.Caption: = 'Равно'
else Label1.Caption:= 'He равно';
end;
От предыдущего он отличается только тем, что строка S
имеет пустое значение. Тем не менее на экране мы увидим Не равно. Связано это с тем, что приведение строки AnsiString
к типу PChar
на самом деле не является приведением типов. Это скрытый вызов функции _LStrToPChar
, и сделано так для того, чтобы правильно обрабатывать пустые строки.
Значение ''
(пустая строка) для строки AnsiString
означает, что память для нее вообще не выделена, а указатель имеет значение nil
. Для типа PChar
пустая строка — это ненулевой указатель на символ #0
. Нулевой указатель также может рассматриваться как пустая строка, но не всегда — иногда это рассматривается как отсутствие какого бы то ни было значения, даже пустого (аналог NULL в базах данных). Чтобы решить это противоречие, функция _LStrToPChar
проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не nil
, а указатель на символ #0
, который специально для этого размещен в сегменте кода. Таким образом, для пустой строки PChar(S) <> Pointer(S)
, потому что приведение строки AnsiString
к указателю другого типа — это нормальное приведение типов без дополнительной обработки значения.
3.3.5. Побочное изменение
Из-за того, что две одинаковые строки AnsiString
разделяют одну область памяти, на неожиданные эффекты можно натолкнуться, если модифицировать содержимое строки в обход стандартных механизмов. Следующий код (листинг 3.30, пример SideChange на компакт-диске) иллюстрирует такую ситуацию.
S2
при изменении
S1procedure 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).
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.