TObject.InstanceSize 返回 8,但 TObject 没有声明任何数据成员。根据 TObject.ClassType 的实现,前 4 个字节可以解释为指向对象的 TClass 元数据的指针。有人知道其他 4 个字节的开销是做什么用的吗?
编辑:显然这是特定于 D2009。在旧版本中,它只有 4 个字节。
TObject.InstanceSize 返回 8,但 TObject 没有声明任何数据成员。根据 TObject.ClassType 的实现,前 4 个字节可以解释为指向对象的 TClass 元数据的指针。有人知道其他 4 个字节的开销是做什么用的吗?
编辑:显然这是特定于 D2009。在旧版本中,它只有 4 个字节。
在 Delphi 2009 中,可以引用同步监视器。看:
class function TMonitor.GetFieldAddress(AObject: TObject): PPMonitor;
class function TMonitor.GetMonitor(AObject: TObject): PMonitor;
...在 System.pas
此外,还有一个指向 VMT 的指针。(虚拟方法表。)来自Delphi in a Nutshell:
TObject 类声明了几种方法和一个特殊的隐藏字段来存储对对象类的引用。此隐藏字段指向类的虚拟方法表 (VMT)。每个类都有一个唯一的 VMT,并且该类的所有对象共享该类的 VMT。
一个对象包含其所有字段的条目,以及用于保存指向虚拟方法表的指针的额外空间。VMT 不仅仅包含虚拟方法指针。我在我的网站上解释了更多关于 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
是隐藏字段的地址。我已经写了更多关于实现接口的类的布局。
万一有人想知道为什么 Craig Stuntz 的回答被接受了,请参阅他对该回答的最后评论:
看起来像是在 D2009 中添加的:http: //blogs.embarcadero.com/abauer/2008/02/19/38856有关详细信息,请参阅该帖子中的链接。
链接不再可用,但wayback-machine 有它:
https://web.archive.org/web/20160409224957/blogs.embarcadero.com/abauer/2008/02/19/38856