Не так давно я задал аналогичный вопрос о неявных переменных интерфейса.
Источником этого вопроса была ошибка в моем коде из-за того, что я не знал о существовании неявной интерфейсной переменной, созданной компилятором. Эта переменная была завершена после завершения процедуры, которой она принадлежала. Это, в свою очередь, вызвало ошибку из-за того, что время жизни переменной было больше, чем я ожидал.
Теперь у меня есть простой проект, иллюстрирующий интересное поведение компилятора:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
компилируется так, как вы можете себе представить. Локальная переменная I
, результат функции, передается как неявный var
параметр в Create
. Приведение в порядок StoreToLocal
результатов за один вызов IntfClear
. Никаких сюрпризов.
Однако StoreViaPointerToLocal
трактуется иначе. Компилятор создает неявную локальную переменную, которой он передает Create
. При Create
возврате выполняется присвоение P^
. Это оставляет подпрограмму с двумя локальными переменными, содержащими ссылки на интерфейс. Приведение в порядок StoreViaPointerToLocal
приводит к двум вызовам IntfClear
.
Скомпилированный код StoreViaPointerToLocal
выглядит так:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
Я могу догадаться, почему компилятор это делает. Когда он может доказать, что присвоение переменной результата не вызовет исключения (т. Е. Если переменная является локальной), он использует переменную результата напрямую. В противном случае он использует неявную локальную переменную и копирует интерфейс после возврата функции, что гарантирует отсутствие утечки ссылки в случае исключения.
Но я не могу найти никаких утверждений об этом в документации. Это важно, потому что время жизни интерфейса важно, и вам, как программисту, нужно иметь возможность время от времени на него влиять.
Итак, кто-нибудь знает, есть ли документация об этом поведении? Если нет, то кто-нибудь еще об этом знает? Как обрабатываются поля экземпляра, я еще не проверял. Конечно, я мог бы попробовать все это сам, но я ищу более формальное заявление и всегда предпочитаю не полагаться на детали реализации, разработанные методом проб и ошибок.
Обновление 1
Чтобы ответить на вопрос Реми, мне было важно, когда мне нужно было завершить объект, стоящий за интерфейсом, прежде чем выполнять еще одну финализацию.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Как написано так, это нормально. Но в реальном коде у меня был второй неявный локальный код, который был завершен после того, как GIL был выпущен и провалился. Я решил проблему, выделив код внутри GIL Acquire / Release в отдельный метод и таким образом сузив область действия переменной интерфейса.
источник
procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Ответы:
Если есть какая-либо документация по этому поведению, она, вероятно, будет относиться к области создания компилятором временных переменных для хранения промежуточных результатов при передаче результатов функции в качестве параметров. Рассмотрим этот код:
procedure UseInterface(foo: IInterface); begin end; procedure Test() begin UseInterface(Create()); end;
Компилятор должен создать неявную временную переменную для хранения результата Create, когда он передается в UseInterface, чтобы гарантировать, что интерфейс имеет время жизни> = время жизни вызова UseInterface. Эта неявная временная переменная будет удалена в конце процедуры, которой она принадлежит, в данном случае в конце процедуры Test ().
Возможно, что ваш случай присвоения указателя может попасть в ту же корзину, что и передача промежуточных значений интерфейса в качестве параметров функции, поскольку компилятор не может «видеть», куда идет значение.
Я помню, что за эти годы в этой области было несколько ошибок. Давным-давно (D3? D4?) Компилятор вообще не считал промежуточное значение. Он работал большую часть времени, но возникали проблемы в ситуациях с псевдонимами параметров. Я считаю, что после того, как это было решено, последовали дальнейшие шаги по поводу параметров const. Всегда было желание перенести удаление интерфейса промежуточного значения как можно скорее после оператора, в котором он был необходим, но я не думаю, что это когда-либо было реализовано в оптимизаторе Win32, потому что компилятор просто не был установлен вверх для обработки удаления при деталировании инструкции или блока.
источник
Вы не можете гарантировать, что компилятор не решит создать временную невидимую переменную.
И даже если вы это сделаете, отключенная оптимизация (или даже кадры стека?) Может испортить ваш идеально проверенный код.
И даже если вам удастся просмотреть свой код при всех возможных комбинациях параметров проекта - компиляция кода под что-то вроде Lazarus или даже новой версии Delphi вернет ад.
Лучше всего было бы использовать правило «внутренние переменные не могут пережить рутинную работу». Обычно мы не знаем, будет ли компилятор создавать какие-то внутренние переменные или нет, но мы знаем, что любые такие переменные (если они созданы) будут завершены, когда подпрограмма существует.
Следовательно, если у вас есть такой код:
// 1. Some code which may (or may not) create invisible variables // 2. Some code which requires release of reference-counted data
Например:
Lib := LoadLibrary(Lib, 'xyz'); try // Create interface P := GetProcAddress(Lib, 'xyz'); I := P; // Work with interface finally // Something that requires all interfaces to be released FreeLibrary(Lib); // <- May be not OK end;
Тогда вам нужно просто обернуть блок «Работа с интерфейсом» в подпрограмму:
procedure Work(const Lib: HModule); begin // Create interface P := GetProcAddress(Lib, 'xyz'); I := P; // Work with interface end; // <- Releases hidden variables (if any exist) Lib := LoadLibrary(Lib, 'xyz'); try Work(Lib); finally // Something that requires all interfaces to be released FreeLibrary(Lib); // <- OK! end;
Это простое, но действенное правило.
источник