Документируется ли обработка компилятором неявных переменных интерфейса? - PullRequest
86 голосов
/ 13 октября 2011

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

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

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

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 

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

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

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

Обновление 1

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

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

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

Ответы [ 2 ]

15 голосов
/ 11 ноября 2013

Если есть какая-либо документация об этом поведении, вероятно, это будет в области создания временных переменных компилятором для хранения промежуточных результатов при передаче результатов функции в качестве параметров.Рассмотрим следующий код:

procedure UseInterface(foo: IInterface);
begin
end;

procedure Test()
begin
    UseInterface(Create());
end;

Компилятор должен создать неявную временную переменную для хранения результата Create при его передаче в UseInterface, чтобы убедиться, что интерфейс имеет время жизни> = время жизни объекта.Используйте интерфейсный вызов.Эта неявная временная переменная будет расположена в конце процедуры, которой она принадлежит, в этом случае в конце процедуры Test ().

Возможно, ваш случай назначения указателя может попасть в тот же сегмент, что иПередача промежуточных значений интерфейса в качестве параметров функции, так как компилятор не может «видеть», куда идет значение.

Напоминаю, что в этой области за последние годы было несколько ошибок.Давным-давно (D3? D4?) Компилятор вообще не ссылался на промежуточное значение.Это работало большую часть времени, но попало в ситуацию с псевдонимами параметров.Я думаю, что после того, как это было решено, было проведено наблюдение за const params.Всегда было желание перенести избавление от интерфейса промежуточного значения как можно скорее после оператора, в котором он был необходим, но я не думаю, что это когда-либо было реализовано в оптимизаторе Win32, поскольку компилятор просто не был установлендля обработки утилизации при выписке или детализации блока.

0 голосов
/ 18 февраля 2015

Вы не можете гарантировать, что компилятор не решит создать временную невидимую переменную.

И даже если вы это сделаете, отключенная оптимизация (или даже кадры стека?) Может испортить ваш отлично проверенный код.

И даже если вам удастся просмотреть ваш код при всех возможных комбинациях параметров проекта - компиляция кода под чем-то вроде 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;

Это простое, но эффективное правило.

...