procedure TForm1.Button1Click(Sender: TObject);
var
CI: TWndClass;
S: string;
procedure DoGetClassInfo;
begin
GetClassInfo(hInstance, PChar('TForm' + IntToStr(1)), CI);
end;
begin
DoGetClassInfo;
S:= 'abcde' + IntToStr(2);
Label1.Caption:= CI.lpszClassName;
end;
Что будет выведено на экран в результате выполнения этого кода? Так как класс называется "TForm1", логично предположить, что именно это и будет выведено. На самом деле мы увидим abcde2
— ту строку, которая присвоена переменнойS
.Разберемся, как значение переменной S
CI.lpszClassName
. Согласно MSDN поле lpszClassName
имеет тип LPCTSTR(PChar)
, и в него функция GetClassInfo
заносит указатель на строку, содержащую имя оконного класса. Но нигде не сказано, в какой области памяти должна располагаться эта строка.Функция GetClassInfo
lpszClassName
.В приведенном примере в качестве аргумента GetClassInfo
string
, приведенное к PChar
, которое не может быть вычислено на этапе компиляции, поэтому компилятор генерирует код, вычисляющий данное выражение. Этот код размещает вычисленное выражение в динамической памяти, и в GetClassInfo
передаётся указатель на эту строку.Все строковые выражения, вычисленные подобным образом, должны удаляться из памяти, чтобы не было утечек. Компилятор помещает код, освобождающий эту память, в эпилог той функции, в которой встретилось выражение. В данном случае — в эпилог локальной процедуры DoGetClassInfo
Освободившуюся память менеджер памяти не сразу возвращает системе придерживает, чтобы иметь возможность быстрее выделить память при следующем запросе. Таким образом, после завершения работы DoGetClassInfo
CI.lpszClassName
), по-прежнему принадлежит процессу, но менеджер памяти полагает ее свободной и считает себя вправе использовать ее по своему усмотрению.Когда присваивается значение переменной S
CI.lpszClassName
по-прежнему содержит этот адрес, обращение к этому полю возвращает новую строку, которая присвоена переменной S
.В Delphi до 7-й версии включительно описанный эффект наблюдается при любой длине строки, присваиваемой переменной S
S
, слишком сильно отличается по размеру от 'TForm1'
для этой строки выделяется память в другой области, и подмены не происходит.Если в данном примере не выносить вызов функции GetClassInfo
DoGetClassInfo
, а вызывать ее напрямую из Button1Click
, описанного эффекта не будет, потому что в этом случае освобождение памяти, занятой для вычисленного имени класса, будет производиться в эпилоге Button1Click
, и на момент присваивания значения переменной S
эта память будет считаться занятой, поэтому для S
менеджер памяти выделит другую область.Принципиально и то, что в обоих случаях (в функции GetClassInfo
S
) используются не строковые литералы, а выражения, вычисляемые только на этапе выполнения программы. Строковые литералы размещаются компилятором в сегменте кода, и указатели, переданные в GetClassInfo
и присвоенные переменной S
, будут указывать не на динамическую память, а на эти литералы, и подмены не произойдет.