【问题标题】:Why the size of a Delphi record is not increased when a procedure is included?为什么包含过程时 Delphi 记录的大小没有增加?
【发布时间】:2013-01-16 03:00:10
【问题描述】:

我有两条具有相同字段的记录,其中一条有一组程序。为什么两条记录的大小相同?

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TData = record
    Age : Byte;
    Id  : Integer;
  end;

  TData2 = record
    Age : Byte;
    Id  : Integer;
    procedure foo1;
    procedure foo2;
    procedure foo3;
  end;

procedure TData2.foo1;
begin

end;

procedure TData2.foo2;
begin

end;

procedure TData2.foo3;
begin

end;

begin
  try
    Writeln('SizeOf(TData) = '+ IntToStr(SizeOf(TData)));
    Writeln('SizeOf(TData2) = '+ IntToStr(SizeOf(TData2)));
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

【问题讨论】:

  • 添加方法永远不会增加实例大小,它适用于记录实例和对象。
  • @DavidHeffernan - 不,它没有。它只增加类元数据,实例大小不会改变。除了通过字段增加实例大小的唯一方法是通过接口。
  • @Serg 你是对的,我错了。对不起。我认为每个实例都有一份 VMT 副本。我假设是因为虚拟方法拦截器。但这可以通过创建 VMT 的副本来实现。显然我需要在开口之前检查我的事实。
  • @DavidHeffernan - 是的,其他来自字段的实例包含指向 vtables 的指针 - 类 VMT 和用于接口的 vtables。此外,自 Delphi 2009 以来,还有一个隐藏字段可以实现监视器锁定。
  • '从 Delphi 2009 开始,还有一个隐藏字段可以实现监控锁'——又一次,有人在 Embarcadero 射杀了前 Java 开发人员。

标签: delphi delphi-xe2


【解决方案1】:

那是因为记录本身只携带构成记录的数据,没有过程或函数。过程和函数是一种语法糖,以避免将记录本身作为参数传递:self 变量是由为你编译器。

您在记录中声明的每个方法都有另一个记录本身的参数,例如:

  TData2 = record
    Age : Byte;
    Id  : Integer;
    procedure Foo1;
    procedure Foo2(SomeParam: Integer);
  end;

更改为等同于:

  PData2 = ^TData2;

  TData2 = record
    Age : Byte;
    Id  : Integer;
  end;

  procedure TData2_Foo1(Self: PData2);
  procedure TData2_Foo2(Self: PData2; SomeParam: Integer);

结束您拨打的每个电话也会更改,例如:

var
  Data: TData2;
begin
  Data.Foo1;
  Data.Foo2(1);
end;

被更改为等同于:

var
  Data: TData2;
begin
  TData2_Foo1(@Data);
  TData2_Foo1(@Data, 1);
end;

我手头没有 Delphi 来检查参数是添加在参数列表的开头还是末尾,但我希望你能明白。

当然,这没有真正的语法,因为它是由编译器在运行中完成的,因此,例如,过程名称不会改变。我这样做是为了让我的答案易于理解。

【讨论】:

  • 关于函数/方法调用的内存结构,请参阅docwiki.embarcadero.com/RADStudio/XE3/en/Program_Control,其中明确指出“方法使用与普通过程和函数相同的调用约定,只是每个方法都有一个额外的隐式参数 Self,即对调用该方法的实例或类的引用。Self 参数作为 32 位指针传递。根据寄存器约定,Self 的行为就像在所有其他参数之前声明一样。因此,它总是在EAX 寄存器"
  • @ArnaudBouchez:我假设对于 64 位目标,传递给方法的 Self 指针将是 64 位指针。
  • 我认为这对于 Classes 是一样的吗?
  • @Leonardo 不完全是。来自类的方法获取额外参数,但虚拟方法调用不是在编译时解析,而是在运行时使用 VMT。 Records 没有 VMT,不支持虚拟方法甚至继承。
  • @jachguate - 在运行时?这是我无法想象的(现在我想它是有道理的,例如混合对象的集合。)
【解决方案2】:

程序不占用空间。编译器会正确连接它们。对于每条记录,它们的地址在运行时不需要在内存中。如果您查看 TData2 在内存中的表示,您将找不到过程。

【讨论】:

  • 这有点不精确。程序确实需要内存。代码占用内存。该答案重复了问题中所述的事实,而没有解释原因。答案是代码地址是静态的,在编译时就知道了,所以不需要存储在记录中。
猜你喜欢
  • 1970-01-01
  • 2019-11-15
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 2019-10-23
  • 2015-12-06
  • 2011-01-06
  • 1970-01-01
相关资源
最近更新 更多