1

这个问题很大程度上是基于我以前在此处找到的帖子。

我正在尝试使用反射重新创建 SOS.dll 的一些功能。特别是ObjSizeandDumpObject命令。我使用反射来查找所有字段,然后如果这些字段是原始类型,我将原始类型的大小添加到对象的整体大小中。如果该字段是一个值类型,那么我递归地调用原始方法并沿着参考树向下走,直到找到所有原始类型字段。

我一直在获得比 SOS.dll ObjSize 命令大两倍左右的对象大小。我发现的一个原因是我的反射代码似乎正在寻找 SOS 忽略的字段。例如在字典中,SOS 查找以下字段:

  • 水桶
  • 条目
  • 数数
  • 版本
  • 自由清单
  • 自由计数
  • 比较器
  • 钥匙
  • 价值观
  • _syncRoot
  • m_siInfo

但是,我的反射代码找到了以上所有内容,并且还发现:

  • 版本名称
  • 哈希大小名称
  • 键值对名称
  • 比较器名称

此外,我对 SOS ObjSize 和 DumpObject 命令中发现的不一致感到困惑。我知道 DumpObject 不会查看引用类型的大小。但是,当我在上面提到的字典上调用对象大小时,我得到:

  • 字典 - 532B

然后我在 Dictionary 上调用 DumpObject 来获取它的引用类型的内存地址。然后,当我在它的引用类型上调用 Objsize 时,我得到:

  • 水桶 - 40
  • 条目 - 364
  • 比较器 - 12
  • 钥匙 - 492
  • (其余为空或原始)

顶级字典上的 ObjSize 不应该大致是字典内字段上所有 ObjSize 的总和吗?为什么反射会找到更多的 DumpObject 字段?关于为什么我的反射分析返回的数字大于 SOS.dll 的任何想法?

此外,我在上面链接的主题中提出的一个问题从未得到答案。我在问在评估对象的内存大小时是否应该忽略属性。普遍的共识是忽略它们。但是,我发现了一个很好的示例,说明属性的支持字段何时不会包含在从 Type.GetFields() 返回的集合中。在查看 String 的引擎盖时,您有以下内容:

对象包含名为 FirstChar 的属性 对象包含名为 Chars 的属性 对象包含名为 Length 的属性 对象包含名为 m_stringLength 的字段 对象包含名为 m_firstChar 的字段 对象包含名为 Empty 的字段 对象包含名为 TrimHead 的字段 对象包含名为 TrimTail 的字段 对象包含名为 TrimBoth 的字段 对象包含名为 charPtrAlignConst 的字段对象包含名为 alignConst 的字段,m_firstChar并且m_stringLength是属性的支持字段,FirstCharLength字符串的实际内容保存在 Chars 属性中。这是一个索引属性,可以对其进行索引以返回字符串中的所有字符,但我找不到保存字符串字符的相应字段。

关于为什么会这样的任何想法?或者如何获取索引属性的支持字段?索引属性是否应该包含在内存大小中?

4

1 回答 1

5

好吧,您的反射代码已损坏。您提到的 4 个成员(版本名称等)不是字段,它们是私有常量。我猜你正在使用 Type.GetMembers() 而不是 Type.GetFields() 并且没有正确检查返回的 MemberInfo.MemberType 。只需使用 GetFields() 代替。

请注意,您永远无法获得正确大小的托管对象。对象的布局是不可发现的。大小不是字段的总和,字段是对齐的。非常类似于 StructLayout.Pack 属性。对齐会在布局中产生漏洞,即所谓的“填充字节”。当类对象存储在数组中时,以及最后的额外填充以使字段对齐。

CLR 实际上利用了布局不可发现这一事实。如果后面的字段适合其他两个字段之间的填充,它将交换字段。如果您知道对齐规则,则生成比您得到的更小的对象。试图逆向工程这是一项危险的工作,它还取决于架构(x86 vs x64 vs Arm)。

SOS.dll 没有这个问题,它可以直接访问 CLR 为类维护的内部数据。托管代码的禁区。

于 2013-01-23T18:00:48.023 回答