最近在讲授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,并不代表托管对象没有大小。那么,托管对象的大小如何计算呢?
预知详情,下回分解:)
打印 | 张贴于 2005-07-01 17:52:00 | Tag:C++/CLI




}
留言反馈
"由于alignment的作用,最后两个Byte字段后面要再填充两个bytes的空间",这句我没有明白,alignment是为了什么?那填充又为什么是填2个bytes呢?
唉,我看不懂楼主写的得到对象大小的..谁能解释解释
#include <cstdio>
struct MyValue
{
short data1;
int data2;
char* data3;
char data4;
};
int main()
{
MyValue *s = new MyValue;
printf("%d\n",sizeof(s));
delete[] s;
return 0;
}
完全同意!高级语言存在的正是其“抽象”意义:) 但是探讨“under the hood”是一部分程序员的责任,否则我们就成了金字塔下永远享受“高级抽象”的程序员了:)
To Flier Lu
你们inside IL & CLR小组的文章真的很不错,希望今后看到更多。
To zz,
抱歉,一段时间spam太多,可能没有仔细分拣--我仔细找了。希望今后发信到jianzhong.lee (#) gmail.com。您的问题我在下一篇帖子里仔细回答。谢谢!
Marshal.SizeOf不是用来做这个的,返回的是marshal后的unmanaged type的大小——前提是这个类型能够被marshal到unmanaged世界中,否则还会报告错误。
To KevinLee,
您的做法真是非常好,干净利落,也是我即将在下文中谈的其中一个做法。
To rIPPER,
.NET抽象目标带来的后果是“系统自身对对象的size了如指掌,但是做基于.NET的应用开发则无须了解任何对象的size”。 但是就像“理解操作系统、内存管理等原理,能够让我们比平常的使用者更好地使用软件”一样,“理解对象的size,进而理解对象的layout,理解对象各种行为的成本”可以让我们更好地驾驭CLR,从而写出更正确、更快的程序来。
如果各位网友看见了,也不要见笑,不耻下问嘛!!
李老师您好!
我是C#的初学者,我以前是搞C++的,自从NET发布以来C#语言以语法简洁,功能强大,有一流的IDE环境的
支持让我写C#代码很容易,特别是在VS2005中写代码与VBNET一样方便。基于对C#的热爱同时也对C#寄以很
大的希望.可能是对C#的希望太大了,由此产生了许多许多疑问,这些疑问作为一个初学者来说希望得到一
个权威和肯定的回答!!以下是我的问题
先看在安装VS.NET2003中对C#的简介:
"Microsoft C# .NET 是一种现代的、面向对象的类型安全语言。程序员可以使用它快速生成用于新的
Microsoft .NET 平台的各种应用程序。C#(读作“C sharp”)旨在帮助 C++ 程序员快速进行开发,
同时又保留了 C 和 C++ 所见长的功能和控制力。"(有些网友问C++的控制力有那些?我想可能会举出许多!)
还有:在MS的宣传及其它资料及书上都把C#说是为NET定身量作的,开发NET程序C#是首选,
当然我不怀疑这些说法。我在WEBCAST中,听你讲的C++/CLI中,他说C++/CLI是NET中功能最强大的语
言,它对NET的操纵能力比其它语言要强的多,如对元数据的操作等等,我也是这么认为.每个程序员都希望
自已使用的语言工具有很强大的功能,一个团队为了大家交流都认同大家用同一种语言,这就意味着一个团队
要用一种语言实现几乎所有的开发工作。当然以前的C++能满足我们团队的要求,因为C++功能太强大了,覆盖面
太多了,我们都喜欢。再从C#的宣传中,说C#是具有C++所见长的控制力和行动力的,这正合我们的意,又有
C++所具有的功能,又有这么简洁(不是简单)的语法,又有这么好的IDE帮助我们快速编写代码,何乐而不
为呢?对C++/CLI的了解后,对比一下现在C#的功能,这就让我们举其不
定了,我热爱的C#难道MS对它失去了信心?精力是不是将要放在C++/CLI上面?有了VBNET为何又有C#(功能
是一样的,为什么要分散精力?)?为什么C#的进步或功能的加强不是很大(从C#1到C#2)?在MS,C#有没有
一下发展计划,MS要把C#打造成一个什么样的语言?如果C#发展为大而全的开发语言有何不可??进入64位时代后是不是C#也要
发展成象32时代中的VB,只是作点简单,快速,集成软件等的应用,关键的应用还得用C++???.....很多的疑问了!!
我现在在用C++我有必要转向C#吗?。。。。
李老师帮助一下!!
谢谢!
祝工作顺利!
在C++中我们是会经常用到Sizeof的,当然.Net我不熟悉。
比如:
MyStruct Foo;
ZeroyMemory(&Foo, sizeof(MyStruct));
当然,基本上,涉及到sizeof的操作基本上都会牵扯到指针和内存操作。
{
// mask the alignment bits because this methos is called during GC
MethodTable* mT = (MethodTable*)((size_t)GetMethodTable()&~3);
return mT->GetBaseSize() + (GetNumComponents() * mT->GetComponentSize());
}
HRESULT ProfToEEInterfaceImpl::GetObjectSize(ObjectID objectId, ULONG *pcSize)
{
// Get the object pointer
Object *pObj = reinterpret_cast<Object *>(objectId);
// Get the size
if (pcSize)
*pcSize = (ULONG) pObj->GetSize();
// Indicate success
return (S_OK);
}
COM_METHOD CorProfInfo::GetObjectSize(
/* [in] */ ObjectID objectId,
/* [out] */ ULONG *pcSize)
{
if (objectId == NULL)
return (E_INVALIDARG);
return (g_pProfToEEInterface->GetObjectSize(objectId, pcSize));
}
Marshal.SizeOf...