Задокументирована ли обработка компилятором неявных переменных интерфейса?

Не так давно я задал аналогичный вопрос о неявных переменных интерфейса.

Источником этого вопроса была ошибка в моем коде из-за того, что я не знал о существовании неявной интерфейсной переменной, созданной компилятором. Эта переменная была завершена, когда процедура, которой она принадлежала, завершилась. Это, в свою очередь, вызвало ошибку из-за того, что время жизни переменной было больше, чем я ожидал.

Теперь у меня есть простой проект, иллюстрирующий интересное поведение компилятора:

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 . Когда Создать возвращается, выполняется присвоение 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 

Я могу догадаться, почему компилятор это делает. Когда он может доказать, что присвоение переменной результата не вызовет исключения (т. Е. Если переменная является локальной), он напрямую использует переменную результата. В противном случае он использует неявный local и копирует интерфейс после возврата функции, что гарантирует, что мы не потеряем ссылку в случае исключения.

Но я не могу найти никаких утверждений об этом в документации. Это важно, потому что время жизни интерфейса очень важно, и вы, как программист, должны иметь возможность повлиять на него время от времени.

Итак, кто-нибудь знает, есть ли какая-либо документация об этом поведении? Если нет, то кто-нибудь еще об этом знает? Как обрабатываются поля экземпляра, я еще не проверял. Конечно, я мог бы попробовать все на себе, но я ищу более формальное заявление и всегда предпочитаю не полагаться на детали реализации, разработанные методом проб и ошибок.

Обновление 1

Чтобы ответить на вопрос Реми, для меня имело значение, когда мне нужно было завершить объект, стоящий за интерфейсом, перед выполнением другой финализации.

begin
  AcquirePythonGIL;
  try
    PyObject := CreatePythonObject;
    try
      //do stuff with PyObject
    finally
      Finalize(PyObject);
    end;
  finally
    ReleasePythonGIL;
  end;
end;

Как написано это нормально. Но в реальном коде у меня был второй неявный локальный код, который был завершен после того, как GIL был выпущен, и это провалилось. Я решил проблему, выделив код из GIL Acquire / Release в отдельный метод и, таким образом, сузил область действия переменной интерфейса.

86
задан Community 23 May 2017 в 11:52
поделиться