Object是什么
Object是什么
.Net程序员们每天都在和Object在打交道
如果你问一个.Net程序员什么是Object,他可能会信誓旦旦的告诉你"Object还不简单吗,就是所有类型的基类"
这个答案是对的,但是不足以说明Object真正是什么
在这篇文章我们将会通过阅读CoreCLR的源代码了解Object在内存中的结构和实际到内存中瞧瞧Object
Object在内存中的结构
为了便于理解后面的内容,我先用一张图说明Object在内存中的结构
.Net中的Object包含了这三个部分
- 指向头部的指针
- 指向类型信息的指针
- 字段内容
微软有一张更全的图(说明的是.Net Framework的结构,但是基本和.Net Core一样)
Object的源代码解析
Object的定义(摘要)
源代码: https://github.com/dotnet/coreclr/blob/master/src/vm/object.h
class Object
{
PTR_MethodTable m_pMethTab;
}
PTR_MethodTable的定义,DPTR是一个指针的包装类,你可以先理解为MethodTable*的等价
源代码: https://github.com/dotnet/coreclr/blob/master/src/vm/common.h
typedef DPTR(class MethodTable) PTR_MethodTable;
在Object的定义中我们只看到了一个成员,这个成员就是指向类型信息的指针,那其他两个部分呢?
这是获取指向头部的指针的函数,我们可以看到这个指针刚好放在了Object的前面
PTR_ObjHeader GetHeader()
{
LIMITED_METHOD_DAC_CONTRACT;
return dac_cast<PTR_ObjHeader>(this) - 1;
}
这是获取字段内容的函数,我们可以看到字段内容刚好放在了Object的后面
PTR_BYTE GetData(void)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
return dac_cast<PTR_BYTE>(this) + sizeof(Object);
}
我们可以看到Object中虽然只定义了指向类型信息的指针,但运行时候前面会带指向头部的指针,并且后面会带字段内容
Object在内存中拥有不定的长度,并且起始地址是分配到的内存地址+一个指针的大小
Object结构比较特殊,所以这个对象的生成也需要特殊的处理,关于Object的生成我将在后面的篇幅中介绍
Object中定义的m_pMethTab
还保存了额外的信息,因为这是一个指针值,所以总会以4或者8对齐,这样最后两个bit会总是为0
.Net利用了这两个闲置的bit,分别用于保存GC Pinned和GC Marking,关于这里我也将在后面的篇幅中介绍
ObjHeader的源代码解析
ObjHeader的定义(摘要)
源代码: https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.h
class ObjHeader
{
// !!! Notice: m_SyncBlockValue *MUST* be the last field in ObjHeader.
#ifdef _WIN64
DWORD m_alignpad;
#endif // _WIN64
Volatile<DWORD> m_SyncBlockValue; // the Index and the Bits
}
m_alignpad
是用于对齐的(让m_SyncBlockValue
在后面4位),值应该为0m_SyncBlockValue
的前6位是标记,后面26位是对应的SyncBlock
在SyncBlockCache
中的索引SyncBlock
的作用简单的来说就是用于线程同步的,例如下面的代码会用到SyncBlock
var obj = new object();
lock (obj) { }
ObjHeader
只包含了SyncBlock
,所以你可以看到有的讲解Object结构的文章中会用SyncBlock
代替ObjHeader
关于SyncBlock
更具体的讲解还可以查看这篇文章
MethodTable的源代码解析
MethodTable的定义(摘要)
源代码: https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.h
class MethodTable
{
// Low WORD is component size for array and string types (HasComponentSize() returns true).
// Used for flags otherwise.
DWORD m_dwFlags;
// Base size of instance of this class when allocated on the heap
DWORD m_BaseSize;
WORD m_wFlags2;
// Class token if it fits into 16-bits. If this is (WORD)-1, the class token is stored in the TokenOverflow optional member.
WORD m_wToken;
// <NICE> In the normal cases we shouldn't need a full word for each of these </NICE>
WORD m_wNumVirtuals;
WORD m_wNumInterfaces;
#ifdef _DEBUG
LPCUTF8 debug_m_szClassName;
#endif //_DEBUG
// Parent PTR_MethodTable if enum_flag_HasIndirectParent is not set. Pointer to indirection cell
// if enum_flag_enum_flag_HasIndirectParent is set. The indirection is offset by offsetof(MethodTable, m_pParentMethodTable).
// It allows casting helpers to go through parent chain natually. Casting helper do not need need the explicit check
// for enum_flag_HasIndirectParentMethodTable.
TADDR m_pParentMethodTable;
PTR_Module m_pLoaderModule; // LoaderModule. It is equal to the ZapModule in ngened images
PTR_MethodTableWriteableData m_pWriteableData;
union {
EEClass * m_pEEClass;
TADDR m_pCanonMT;
};
// m_pPerInstInfo and m_pInterfaceMap have to be at fixed offsets because of performance sensitive
// JITed code and JIT helpers. However, they are frequently not present. The space is used by other
// multipurpose slots on first come first served basis if the fixed ones are not present. The other
// multipurpose are DispatchMapSlot, NonVirtualSlots, ModuleOverride (see enum_flag_MultipurposeSlotsMask).
// The multipurpose slots that do not fit are stored after vtable slots.
union
{
PTR_Dictionary * m_pPerInstInfo;
TADDR m_ElementTypeHnd;
TADDR m_pMultipurposeSlot1;
};
union
{
InterfaceInfo_t * m_pInterfaceMap;
TADDR m_pMultipurposeSlot2;
};
// 接下来还有一堆OPTIONAL_MEMBERS,这里省去介绍
}
这里的字段非常多,我将会在后面的篇幅一一讲解,这里先说明MethodTable中大概有什么信息
- 类型的标记,例如
StaticsMask_Dynamic
和StaticsMask_Generics
等 (m_dwFlags)- 如果类型是字符串或数组还会保存每个元素的大小(ComponentSize),例如string是2 int[100]是4
- 类型需要分配的内存大小 (m_BaseSize)
- 类型信息,例如有哪些成员和是否接口等等 (m_pCanonMT)
可以看出这个类型就是用于保存类型信息的,反射和动态Cast都需要依赖它
实际查看内存中的Object
对Object的初步分析完了,可分析对了吗?让我们来实际检查一下内存中Object是什么样子的
VisualStudio有反编译和查看内存的功能,如下图
这里我定义了MyClass
和MyStruct
类型,先看Console.WriteLine(myClass)
这里把第一个参数设置到rcx并且调用Console.WriteLine
函数,为什么是rcx请看查看参考链接中对fastcall
的介绍rbp + 0x50 = 0x1fc8fde110
跳到内存中以后可以看到选中的这8byte是指向对象的指针,让我们继续跳到0x1fcad88390
这里我们可以看到MyClass
实例的真面目了,选中的8byte是指向MethodTable
的指针
后面分别是指向StringMember的指针和IntMember的内容
在这里指向ObjHeader的指针是一个空指针,这是正常的,微软在代码中有注释This is often zero
这里是StringMember指向的内容,分别是指向MethodTable
的指针,字符串长度和字符串内容
这里是MyClass
的MethodTable
,m_BaseSize
是32
有兴趣的可以去和MethodTable
的成员一一对照,这里我就不跟下去了
让我们再看下struct是怎么处理的
可以看到只是简单的把值复制到了堆栈空间中(rbp是当前frame的堆栈基础地址)
让我们再来看下Console.WriteLine
对于struct是怎么处理的,这里的处理相当有趣
因为需要装箱,首先会要来一个箱子,箱子放在了rbp+30h
把MyStruct
中的值复制到了箱子中,rax+8
的8是把值复制到MethodTable
之后
复制后,接下来把这个箱子传给Console.WriteLine
就和MyClass
一样了
另外再附一张实际查看ComponentSize
的图
彩蛋
看完了.Net中对Object的定义,让我们再看下Python中队Object的定义
源代码: https://github.com/python/cpython/blob/master/Include/object.h
#define PyObject_HEAD PyObject ob_base; // 每个子类都需要把这个放在最开头
typedef struct _object {
#ifdef Py_TRACE_REFS
struct _object *_ob_next; // Heap中的前一个对象
struct _object *_ob_prev; // Heap中的后一个对象
#endif
Py_ssize_t ob_refcnt; // 引用计数
struct _typeobject *ob_type; // 指向类型信息
} PyObject;
定义不一样,但是作用还是类似的
参考
http://stackoverflow.com/questions/20033353/clr-implementation-of-virtual-method-calls-via-pointer-to-base-class
http://stackoverflow.com/questions/9808982/clr-implementation-of-virtual-method-calls-to-interface-members
http://stackoverflow.com/questions/1589669/overhead-of-a-net-array
https://en.wikipedia.org/wiki/X86_calling_conventions
https://github.com/dotnet/coreclr/blob/master/src/vm/object.inl
https://github.com/dotnet/coreclr/blob/master/src/vm/object.h
https://github.com/dotnet/coreclr/blob/master/src/vm/object.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.h
https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.inl
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.h
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/class.h
https://github.com/dotnet/coreclr/blob/master/src/inc/daccess.h
https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/dacfn.cpp
写在最后
因为是刚开始阅读coreclr的代码,如果有误请在留言中指出
接下来有时间我将会着重阅读和介绍这些内容
- Object的生成和销毁
- Object继承的原理(MethodTable)
- Object同步的原理(ObjHeader, SyncBlock)
- GC的工作方式
- DACCESS
敬请期待
Object是什么的更多相关文章
- CoreCLR源码探索(一) Object是什么
.Net程序员们每天都在和Object在打交道 如果你问一个.Net程序员什么是Object,他可能会信誓旦旦的告诉你"Object还不简单吗,就是所有类型的基类" 这个答案是对的 ...
- JavaScript Object对象
目录 1. 介绍:阐述 Object 对象. 2. 构造函数:介绍 Object 对象的构造函数. 3. 实例属性:介绍 Object 对象的实例属性:prototype.constructor等等. ...
- javascript之Object.defineProperty的奥妙
直切主题 今天遇到一个这样的功能: 写一个函数,该函数传递两个参数,第一个参数为返回对象的总数据量,第二个参数为初始化对象的数据.如: var o = obj (4, {name: 'xu', age ...
- c# 基础 object ,new操作符,类型转换
参考页面: http://www.yuanjiaocheng.net/webapi/config-webapi.html http://www.yuanjiaocheng.net/webapi/web ...
- APEX:对object中数据进行简单处理?
在Salesforce中,常常要对各种数据进行处理,已满足业务逻辑.本篇文章会介绍如何实现从object获取数据,然后将取得的数据进行一系列简单处理. 第一步:SongName__c 是一个新建的ob ...
- 笔记:Memory Notification: Library Cache Object loaded into SGA
笔记:Memory Notification: Library Cache Object loaded into SGA在警告日志中发现一些这样的警告信息:Mon Nov 21 14:24:22 20 ...
- Selenium的PO模式(Page Object Model)[python版]
Page Object Model 简称POM 普通的测试用例代码: .... #测试用例 def test_login_mail(self): driver = self.driver driv ...
- a different object with the same identifier value was already associated with the session:
hibernate操作: 实例化两个model类,更新时会提示 a different object with the same identifier value was already assoc ...
- CSharpGL - Object Oriented OpenGL in C#
Object Oriented OpenGL in C#
随机推荐
- java: web应用中不经意的内存泄露
前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...
- 关于开启.NET在线提升教育培训的通知! - 可在此页面观看在线直播!
年前在线公开课程通知: 近期在开启VIP课程,隔天讲一次,年前其它时间插空讲公开课,主题:设计模式系列 1:培训 - 大概不会讲的内容: 1:不讲系列. 2:不讲入门. 3:不讲我不懂的! 2:培训 ...
- myeclipse学习总结一(在MyEclipse中设置生成jsp页面时默认编码为utf-8编码)
1.每次我们在MyEclispe中创建Jsp页面,生成的Jsp页面的默认编码是"ISO-8859-1".在这种情况下,当我们在页面中编写的内容存在中文的时候,就无法进行保存.如下图 ...
- C# DateTime日期格式化
在C#中DateTime是一个包含日期.时间的类型,此类型通过ToString()转换为字符串时,可根据传入给Tostring()的参数转换为多种字符串格式. 目录 1. 分类 2. 制式类型 3. ...
- 自定义Inspector检视面板
Unity中的Inspector面板可以显示的属性包括以下两类:(1)C#以及Unity提供的基础类型:(2)自定义类型,并使用[System.Serializable]关键字序列化,比如: [Sys ...
- 9、 Struts2验证(声明式验证、自定义验证器)
1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法.有效的. Struts2 的输入验证 基于 XWork Validation Framework 的声明式验证: ...
- UWP开发之Mvvmlight实践七:如何查找设备(Mobile模拟器、实体手机、PC)中应用的Log等文件
在开发中或者后期测试乃至最后交付使用的时候,如果应用出问题了我们一般的做法就是查看Log文件.上章也提到了查看Log文件,这章重点讲解下如何查看Log文件?如何找到我们需要的Packages安装包目录 ...
- 数据的双向绑定 Angular JS
接触AngularJS许了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同.为此,记录了一些思考,给自己回顾,也供他人参考. 初步大致有以下几个方面: 数据双向绑定 ...
- CSS 3学习——transition 过渡
以下内容根据官方规范翻译以及自己的理解整理. 1.介绍 这篇文档介绍能够实现隐式过渡的CSS新特性.文档中介绍的CSS新特性描述了CSS属性的值如何在给定的时间内平滑地从一个值变为另一个值. 2.过渡 ...
- 【干货分享】流程DEMO-事务呈批表
流程名: 事务呈批表 业务描述: 办公采购.会议费用等事务的申请.流程发起时,会检查预算,如果预算不够,将不允许发起费用申请,如果预算够用,将发起流程,同时占用相应金额的预算,但撤销流程会释放相应金 ...