加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

delphi – 是否编译器处理隐式接口变量记录?

发布时间:2020-12-15 05:26:36 所属栏目:大数据 来源:网络整理
导读:我问了一个类似于 question的隐性接口变量不久前。 这个问题的根源是我的代码中的一个错误,因为我没有意识到存在一个由编译器创建的隐式接口变量。当拥有它的过程完成时,此变量已完成。这又导致了一个错误,由于变量的生命周期比我预期的更长。 现在,我有
我问了一个类似于 question的隐性接口变量不久前。

这个问题的根源是我的代码中的一个错误,因为我没有意识到存在一个由编译器创建的隐式接口变量。当拥有它的过程完成时,此变量已完成。这又导致了一个错误,由于变量的生命周期比我预期的更长。

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

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

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

但我在文档中找不到任何声明。这很重要,因为接口生命是重要的,作为一个程序员,你需要能够影响它的场合。

所以,有没有人知道是否有这种行为的任何文档?如果没有人有更多的知识吗?如何处理实例字段,我还没有检查过。当然,我可以尝试一切为自己,但我正在寻找一个更正式的声明,总是宁愿避免依赖于实施细节通过试验和错误。

更新1

为了回答Remy的问题,当我需要在执行另一个完成操作之前完成接口背后的对象时,对我来说很重要。

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

像这样写就好了。但在实际代码中,我有一个第二个隐式局部,在GIL被释放和被轰炸后被最终确定。我解决了这个问题,通过将Acquire / Release GIL中的代码提取到一个单独的方法中,从而缩小了接口变量的范围。

解决方法

如果有这种行为的任何文档,它可能在编译器生产临时变量的领域,以保存中间结果时传递函数结果作为参数。考虑这个代码:
procedure UseInterface(foo: IInterface);
begin
end;

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

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

可能你的指针赋值情况可能和传递中间接口值作为函数参数落在同一个桶中,因为编译器不能“看到”值将去哪里。

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读