熟悉C/C++的朋友都知道,struct类型中的成员在内存中都是按顺序依次存放的,即按成员的声明顺序,并且通常是按成员中占用空间最大的成员进行对齐的。
然而,到了.net托管环境中,则有所不同。CLR为我们提供了两种不同的结构成员内存布局方式:LayoutKind.Sequential和LayoutKind.Explicit,分别实现常用的顺序布局和按偏移量精确布局。前者是CLR的默认值。我们可以在声明struct时加上修饰:[StructLayout(LayoutKind.Sequential)]来告诉CLR要采用的内存布局方式为顺序。采用这种布局方式声明的struct在内存中和非托管环境中声明的struct一致,所以,通常在和非托管Dll进行交互调用的时候,应将struct声明成顺序式的。看下面的示例:
[StructLayout(LayoutKind.Sequential)]
struct S1 //16byte
{
int i; //4byte
double b; //8byte
}
按顺序布局的S1本来只占用了12个byte的内存空间,但是,当我们用sizeof(S1)测试的时候,发现它竟然占用了16个byte的空间,这是因为LayoutKind.Sequential(默认)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)。显然,这种方式浪费了一定的内存空间。
然而,按偏移量来布局的方式也有一定的不足,当偏移量计算不准的时候,就会造成数据丢失。请看下面的示例:
[StructLayout(LayoutKind.Explicit)]
struct S2
{
[FieldOffset(0)] int i;
[FieldOffset(0)] double b;
}
S2中两个成员的内存位置偏移量都是0,这就意味着它们占用了相同的部分内存空间。当修改其中一个的值时,必然导致另一个的值也发生变化。因为,偏移量的计算应当非常小心才是。以下是一段来MSND上的比较好的例子:
using System.Runtime.InteropServices ;
[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)] public class MySystemTime { [FieldOffset(0)]public ushort wYear; [FieldOffset(2)]public ushort wMonth; [FieldOffset(4)]public ushort wDayOfWeek; [FieldOffset(6)]public ushort wDay; [FieldOffset(8)]public ushort wHour; [FieldOffset(10)]public ushort wMinute; [FieldOffset(12)]public ushort wSecond; [FieldOffset(14)]public ushort wMilliseconds; }
细心的朋友可能发现:代码中用的是class面不是struct,这说明class和struct其实没有本质的区别。再看一个使用顺序方式的比较好的例子:
[StructLayout(LayoutKind.Sequential)]
public struct POINT {
public POINT(int xx, int yy) { x=xx; y=yy; } //构造函数
public int x;
public int y;
public override string ToString() {
String s = String.Format( "({0},{1}) ", x, y);
return s;
}
}
补充说明:
其实,.net中还有第三种布局方式:[StructLayout(LayoutKind.Auto)]。这种方式下,CLR会对结构体中的字段顺序进行调整使之占用尽可能少的内存,也就是说,CLR会自动将struct中占用空间多的排在前面,占用空间少的排在后面,并进行4byte的内存对齐,这样下来,可以相比顺序方式节省一定的内存,但还是比精确定义偏移量的方式多浪费一些内存。
有关.net托管环境下struct类型的内存布局的问题,更多信息可以阅读网友happyhippy的博文:
http://www.cnblogs.com/happyhippy/archive/2007/04/12/710927.aspx
《完》
※※※※※※※※※※※※※※--我的程序人生--※※※※※※※※※※※※※※