Under The Hood in .NET——使用C++/CLI实现托管版的sizeof (引言)
最近在讲授C#培训课程中谈到托管对象内存的布局(Layout)和托管对象大小的问题时,一位学员问了这样一个问题 “既然每一个对象都是有大小的,为什么C#语言没有提供sizeof操作符?”
当然这个问题准确的问法应该是“为什么C#语言的sizeof操作符不能应用于class上?”,因为实际上C#是提供有sizeof操作符的,只不过只能在unsafe代码中对struct类型使用。而更进一步的问题便是“如何获取托管对象的大小?”
这个问题表面看起来并不大,但是仔细思考起来却很有意思,如果真的深究下去,恐怕需要把.NET挖个底朝天。正好我在SSCLI(Shared Source Common Language Infrastructure)上有过一段研究心得,于是便打算写一组文章来探讨这个问题,希望能够满足那些喜欢 “Under The Hood”的朋友的胃口。
有关“Under The Hood”的解释,可以参见孙展波先生这里的一篇blog:http://blog.joycode.com/zhanbos/archive/2004/06/10/24208.aspx.当然也可以到这里http://blogs.msdn.com/matt_pietrek/ 寻找“Under The Hood”的创始人Matt Pietrek。
在回答这些问题之前,首先要谈谈“既然每一个对象都是有大小的”这句话,因为我发现并不是每一个程序员对此都有非常清晰的理解。C++程序员一般对此有比较好的观念,但是很多C#程序员、VB.NET程序员,Java程序员对这样的观念很淡薄,甚至许多“搞了很长时间面向对象”的程序员压根就不知道对象还有所谓大小这回事,这也是我在讲授C#/VB.NET培训课程时要着力强调它的原因。
因为要搞清楚对象的大小,必须搞清楚对象的Layout。而只有对对象的Layout非常清楚,才能彻底理清栈、托管堆、值类型、引用类型、参数传递,虚方法调用(多态)、垃圾收集。。。。等等这些编程中的核心问题。我以前在面试C#/.NET开发人员时,就把“一个class中定义有一个int、一个byte、和一个string,那么这个class的实例对象有多大”这样的问题作为对一个.NET程序员的“终极测试题”——如果这个问题回答得令人满意,基本上再问其他问题都显得多余。我甚至偏执地认为,只有“清楚了解对象Layout的程序员”才是“真正懂面向对象的程序员”。
言归正传,现在开始具体谈谈如何来获取一个托管对象的大小——注意这里说的是“一个托管对象的大小”而不是“一个类型的大小”,后面我会解释为什么要这样说。在这之前,先来看看C#和C++/CLI目前的sizeof操作符在值类型上的行为,它对后面探讨托管对象的sizeof多少有一点帮助。
C#和C++/CLI中的sizeof操作符
C#和C++/CLI都提供了sizeof操作符,比如在C++/CLI(C#不支持在一个含有引用类型字段的值类型上使用sizeof操作符)中,我们可以这样来计算一个值类型的大小:
value struct MyValue
![]()
...{
![]()
Byte data1;
![]()
int data2;
![]()
String^ data3;
![]()
Byte data4;
![]()
};
![]()
![]()
![]()
int main()
![]()
...{
![]()
int size=sizeof(MyValue);
![]()
Console::WriteLine(size);
![]()
}
这里的输出结果为12,单位为bytes。和ISO-C++下class的结果不同。其原因为CLR对值类型的Layout做了优化调整,事实上对于上面的声明顺序,CLR在内存中调整如下:
value struct MyValue
![]()
...{
![]()
String^ data3; // 4bytes 指针
![]()
int data2; // 4bytes 整数
![]()
Byte data1; //1byte 整数
![]()
Byte data4; //1byte 整数
![]()
};
![]()
![]()
由于
alignment的作用,最后两个Byte字段后面要再填充两个bytes的空间,因此结果为4+4+1+1+2=12——这种做法显然是比较优化的算法,能够最大程度地节省一个栈对象的内存开销(ISO-C++按字段声明顺序来排列Layout,结果为16)。顺便提一句,从编译出的元数据来看,编译器自动在MyValue上应用了如下Attribute:
[StructLayout(LayoutKind::Sequential)] // 顺序排列Layout
但是CLR并没有理会这一Attribute,而是自我主张使用了如下Attribute:
[StructLayout(LayoutKind:: Auto)] // 自动排列Layout,优化方案
不过sizeof只能在值类型上使用,而不能在引用类型上使用,计算的也是这些值类型在栈上分配的“裸对象”的成本——而非box到托管堆上的多态对象。
C#和C++/CLI不允许在托管类型上使用sizeof,并不代表托管对象没有大小。那么,托管对象的大小如何计算呢?
预知详情,下回分解:)
posted on 2005-07-01 17:52:00 by lijianzhong 评论(21) 阅读(5841)




}