编译器处理隐式接口变量是否记录在文件中? [英] Is the compiler treatment of implicit interface variables documented?

查看:87
本文介绍了编译器处理隐式接口变量是否记录在文件中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我问了一个类似的问题关于隐含界面变量不久以前。

I asked a similar question about implicit interface variables not so long ago.

这个问题的根源是我的代码中的一个错误,因为我没有意识到存在由编译器当所有的过程完成时,该变量已经完成。这反过来又引起了一个bug,因为变量的使用寿命比我预期的要长。

The source of this question was a bug in my code due to me not being aware of the existence of an implicit interface variable created by the compiler. This variable was finalized when the procedure that owned it finished. This in turn caused a bug due to the lifetime of the variable being longer than I had anticipated.

现在,我有一个简单的项目来说明编译器的一些有趣的行为:

Now, I have a simple project to illustrate some interesting behaviour from the compiler:

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 参数传递到创建。对于 StoreToLocal 的整理,可以调用 IntfClear

StoreToLocal is compiled just as you would imagine. The local variable I, the function's result, is passed as an implicit var parameter to Create. The tidy up for StoreToLocal results in a single call to IntfClear. No surprises there.

但是, StoreViaPointerToLocal 的对待方式不同。编译器创建一个隐含的局部变量,它传递给创建。当创建返回时,执行到 P ^ 的分配。这样可以使用两个局部变量保留对该接口的引用。 StoreViaPointerToLocal 的整理结果导致两次调用 IntfClear

However, StoreViaPointerToLocal is treated differently. The compiler creates an implicit local variable which it passes to Create. When Create returns, the assignment to P^ is performed. This leaves the routine with two local variables holding references to the interface. The tidy up for StoreViaPointerToLocal results in two calls to 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 

我可以猜测为什么编译器在做这个。当可以证明分配给结果变量不会引发异常(即如果变量是局部变量),那么它直接使用结果变量。否则它会使用一个隐含的本地地址,并在函数返回后复制该接口,从而确保在异常情况下我们不会泄露引用。

I can guess as to why the compiler is doing this. When it can prove that assigning to the result variable will not raise an exception (i.e. if the variable is a local) then it uses the result variable directly. Otherwise it uses an implicit local and copies the interface once the function has returned thus ensuring that we don't leak the reference in case of an exception.

但是我找不到在文档中的任何声明。这很重要,因为界面生命周期很重要,作为一个程序员,你需要能够有时候影响它。

But I cannot find any statement of this in the documentation. It matters because interface lifetime is important and as a programmer you need to be able to influence it on occasion.

所以,有没有人知道这个行为是否有任何文档?如果没有人有更多的知识吗?如何处理实例字段,我还没有检查。当然,我可以自己尝试一下,但我正在寻找一个更正式的声明,并且总是倾向于依赖于通过反复试验而实现的实现细节。

So, does anybody know if there is any documentation of this behaviour? If not does anyone have any more knowledge of it? How are instance fields handled, I have not checked that yet. Of course I could try it all out for myself but I'm looking for a more formal statement and always prefer to avoid relying on implementation detail worked out by trial and error.

更新1

为了回答Remy的问题,当我需要在界面后面完成对象后再进行另一个定稿,这对我来说很重要。

To answer Remy's question, it mattered to me when I needed to finalize the object behind the interface before carrying out another finalization.

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

像这样写的没关系。但是在真正的代码中,我有一个第二个隐含的地方,在GIL被释放并被炸毁之后被定案。我通过将Acquire / Release GIL中的代码解压缩成一个单独的方法来解决问题,从而缩小了接口变量的范围。

As written like this it is fine. But in the real code I had a second implicit local which was finalized after the GIL was released and that bombed. I solved the problem by extracting the code inside the Acquire/Release GIL into a separate method and thus narrowed the scope of the interface variable.

推荐答案

如果有这种行为的任何文档,那么在将函数结果作为参数传递时,可能会在编译器生成临时变量的过程中保留中间结果。考虑这个代码:

If there is any documentation of this behavior, it will probably be in the area of compiler production of temporary variables to hold intermediate results when passing function results as parameters. Consider this code:

procedure UseInterface(foo: IInterface);
begin
end;

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

编译器必须创建一个隐式临时变量来保存创建的结果,因为它被传递到UseInterface ,以确保接口的使用寿命> = UseInterface调用的生命周期。该隐式临时变量将被处理在拥有它的过程的结尾,在这种情况下在Test()过程的结尾。

The compiler has to create an implicit temp variable to hold the result of Create as it is passed into UseInterface, to make sure that the interface has a lifetime >= the lifetime of the UseInterface call. That implicit temp variable will be disposed at the end of the procedure that owns it, in this case at the end of the Test() procedure.

可能是你的指针分配情况可能与传递中间接口值作为函数参数落入同一个bucket中,因为编译器无法看到值在哪里。

It's possible that your pointer assignment case may fall into the same bucket as passing intermediate interface values as function parameters, since the compiler can't "see" where the value is going.

我记得那里多年来一直是这个领域的几个bug。很久以前(D3?D4?),编译器根本没有引用计数中间值。它在大多数时间工作,但在参数别名情况下遇到麻烦。一旦被解决了,有一个关于const params的跟进,我相信。总是希望将中间值接口的处理尽可能快地移植到需要的语句之后,但是我认为在Win32优化器中并没有实现,因为编译器没有被设置处理处理报告或块粒度。

I recall there have been a few bugs in this area over the years. Long ago (D3? D4?), the compiler didn't reference count the intermediate value at all. It worked most of the time, but got into trouble in parameter alias situations. Once that was addressed there was a follow up regarding const params, I believe. There was always a desire to move disposal of the intermediate value interface up to as soon as possible after the statement in which it was needed, but I don't think that ever got implemented in the Win32 optimizer because the compiler just wasn't set up for handling disposal at statement or block granularity.

这篇关于编译器处理隐式接口变量是否记录在文件中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆