【发布时间】:2009-03-24 20:01:34
【问题描述】:
TObject.InstanceSize 返回 8,但 TObject 没有声明任何数据成员。根据 TObject.ClassType 的实现,前 4 个字节可以解释为指向对象的 TClass 元数据的指针。有谁知道另外 4 个字节的开销是干什么用的?
编辑:显然这是特定于 D2009。在旧版本中,它只有 4 个字节。
【问题讨论】:
标签: delphi object delphi-2009 tobject
TObject.InstanceSize 返回 8,但 TObject 没有声明任何数据成员。根据 TObject.ClassType 的实现,前 4 个字节可以解释为指向对象的 TClass 元数据的指针。有谁知道另外 4 个字节的开销是干什么用的?
编辑:显然这是特定于 D2009。在旧版本中,它只有 4 个字节。
【问题讨论】:
标签: delphi object delphi-2009 tobject
在 Delphi 2009 中,有the ability to have a reference to a synchronization monitor。见:
class function TMonitor.GetFieldAddress(AObject: TObject): PPMonitor;
class function TMonitor.GetMonitor(AObject: TObject): PMonitor;
...在 System.pas 中
此外,还有一个指向 VMT 的指针。 (虚拟方法表。)From Delphi in a Nutshell:
TObject 类声明了几个 方法和一个特殊的隐藏字段 存储对对象的引用 班级。这个隐藏字段指向 类的虚拟方法表 (VMT)。 每个班级都有一个独特的 VMT 和所有 该类的对象共享 班级的 VMT。
【讨论】:
一个对象包含其所有字段的条目,加上额外的空间来保存指向虚拟方法表的指针。 VMT 不仅仅包含虚拟方法指针。我在我的网站上解释了more about the VMT,包括一张图表。
显然,Delphi 2009 除了 VMT 指针之外还引入了另一个隐藏字段来保存同步监视器。可以通过一些简单的代码来判断是加在类的开头还是结尾:
type
TTest = class
FField: Integer;
end;
var
obj: TTest;
ObjAddr, FieldAddr: Cardinal;
begin
Assert(TTest.InstanceSize = 12);
obj := TTest.Create;
ObjAddr := Cardinal(obj);
FieldAddr := Cardinal(@(obj.FField));
writeln(FieldAddr - ObjAddr);
end.
如果它打印值 4,则监控字段必须位于对象的末尾,因为 4 仅说明 VMT 指针的大小。如果它打印值 8,则监视字段必须位于开头,与 VMT 指针相邻。
我希望您会在开始时找到监视器。否则,这意味着后代对象的布局不仅仅是附加了所有新字段的基础对象的布局。这意味着监视字段的偏移量取决于对象的运行时类型,这使得实现更加复杂。
当一个类实现一个接口时,对象布局包括更多的隐藏字段。这些字段包含指向对象的接口引用值的指针。当你有一个对象的 IUnknown 引用时,它所持有的指针与指向对象的 VMT 字段的指针不同,这是普通对象引用所拥有的。 IUnknown 指针值将是隐藏字段的地址。我写了more about the layout of classes that implement interfaces。
【讨论】:
如果有人想知道为什么 Craig Stuntz 的回答被接受,请参阅他对该回答的最后评论:
看起来像是在 D2009 中添加的:http://blogs.embarcadero.com/abauer/2008/02/19/38856 查看该帖子中的链接以获取完整的详细信息。
链接不再可用,但回程机器有它:
https://web.archive.org/web/20160409224957/blogs.embarcadero.com/abauer/2008/02/19/38856
【讨论】: