.net托管环境下struct实例字段的内存布局(Layout)和大小(Size)

在C/C++中,struct类型中的成员的一旦声明,则实例中成员在内存中的布局(Layout)顺序就定下来了,即与成员声明的顺序相同,并且在默认情况下总是按照结构中占用空间最大的成员进行对齐(Align);当然我们也可以通过设置或编码来设置内存对齐的方式。

然而在.net托管环境中,CLR提供了更自由的方式来控制struct中Layout:我们可以在定义struct时,在struct上运用StructLayoutAttribute特性来控制成员的内存布局。默认情况下,struct实例中的字段在栈上的布局(Layout)顺序与声明中的顺序相同,即在struct上运用[StructLayoutAttribute(LayoutKind.Sequential)]特性,这样做的原因是结构常用于和非托管代码交互的情形。如果我们正在创建一个与非托管代码没有任何互操作的struct类型,我们很可能希望改变C#编译器的这种默认规则,因此LayoutKind除了Sequential成员之外,还有两个成员Auto和Explicit,给StructLayoutAttribute传入LayoutKind.Auto可以让CLR按照自己选择的最优方式来排列实例中的字段;传入LayoutKind.Explicit可以使字段按照我们的在字段上设定的FieldOffset来更灵活的设置字段排序方式,但这种方式也挺危险的,如果设置错误后果将会比较严重。下面就看几个示例,算下四个struct各占多少Byte?

1.[StructLayout(LayoutKind.Sequential)]

struct StructDeft//C#编译器会自动在上面运用[StructLayout(LayoutKind.Sequential)]
...{
bool i; //1Byte
double c;//8byte
bool b; //1byte
}

sizeof(StructDeft)得到的结果是24byte!啊哈,本身只有10byte的数据却占有了24byte的内存,这是因为默认(LayoutKind.Sequential)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)。10byte的数据却占有了24byte,严重地浪费了内存,所以如果我们正在创建一个与非托管代码没有任何互操作的struct类型,最好还是不要使用默认的StructLayoutAttribute(LayoutKind.Sequential)特性。

2.[StructLayout(LayoutKind.Explicit)]

[StructLayout(LayoutKind.Explicit)]
struct BadStruct
...{
[FieldOffset(0)]
public bool i; //1Byte
[FieldOffset(0)]
public double c;//8byte
[FieldOffset(0)]
public bool b; //1byte
}

sizeof(BadStruct)得到的结果是9byte,显然得出的基数9显示CLR并没对结构体进行任何内存对齐(Align);本身要占有10byte的数据却只占了9byte,显然有些数据被丢失了,这也正是我给struct取BadStruct作为名字的原因。如果在struct上运用了[StructLayout(LayoutKind.Explicit)],计算FieldOffset一定要小心,例如我们使用上面BadStruct来进行下面的测试:

StructExpt e = new StructExpt();
e.c = 0;
e.i = true;
Console.WriteLine(e.c);

输出的结果不再是0了,而是4.94065645841247E-324,这是因为e.c和e.i共享同一个byte,执行“e.i = true;时”也改变了e.c,CPU在按照浮点数的格式解析e.c时就得到了这个结果。(有关浮点数讨论可以参考我以前写的《精确判断一个浮点数是否等于0》)。所以在运用LayoutKind.Explicit时千万别吧FieldOffset算错了:)

3.[StructLayout(LayoutKind.Auto)]

sizeof(StructAuto)得到的结果是12byte。下面来测试下这StructAuto的三个字段是如何摆放的:

unsafe
...{
StructAuto s = new StructAuto();
Console.WriteLine(string.Format("i:{0}", (int)&(s.i)));
Console.WriteLine(string.Format("c:{0}", (int)&(s.c)));
Console.WriteLine(string.Format("b:{0}", (int)&(s.b)));
}
// 测试结果:
i:1242180
c:1242172
b:1242181

即CLR会对结构体中的字段顺序进行调整,将i调到c之后,使得StructAuto的实例s占有尽可能少的内存,并进行4byte的内存对齐(Align),字段顺序调整结果如下图所示:

4.空struct实例的Size

struct EmptyStruct...{}

无论运用上面LayoutKind的Explicit、Auto还是Sequential,得到的sizeof(EmptyStct)都是1byte。

结论:

默认(LayoutKind.Sequential)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align);

使用LayoutKind.Explicit的情况下,CLR不对结构体进行任何内存对齐(Align),而且我们要小心就是FieldOffset;

使用LayoutKind.Auto的情况下,CLR会对结构体中的字段顺序进行调整,使实例占有尽可能少的内存,并进行4byte的内存对齐(Align)。

时间: 2024-10-14 23:22:31

.net托管环境下struct实例字段的内存布局(Layout)和大小(Size)的相关文章

内存对齐与ANSI C中struct型数据的内存布局 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-3032209.html 当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密. 首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的

一起talk C栗子吧(第一百三十一回:C语言实例--C程序内存布局三)

各位看官们,大家好.上一回中咱们说的是C程序内存布局的样例,这一回咱们继续说该样例.闲话休提,言归正转.让我们一起talk C栗子吧. 看官们,关于C程序内存布局的样例,我们在前面的两个章回都介绍过了,这一回我们将对前面章回中的内容进行总结和提示. 内存布局总结 C程序的内存布局主要有四个分区:代码区,数据区(data和bss).堆区和栈区.能够使用readelf -S filename查看各个分区的内存地址.这四个分区在内存中从低地址空间開始依次向高地址延伸.我们再次使用前面章回中的图直观地展

查看struct或class的内存布局

适用于VC编译器(Visual Studio) 附加选项: /d1 reportSingleClassLayout[foo] 例如CItem(注意后面没有空格) /d1 reportSingleClassLayoutCItem

.net环境下跨进程、高频率读写数据

原文:.net环境下跨进程.高频率读写数据 一.需求背景 1.最近项目要求高频次地读写数据,数据量也不是很大,多表总共加起来在百万条上下. 单表最大的也在25万左右,历史数据表因为不涉及所以不用考虑, 难点在于这个规模的热点数据,变化非常频繁. 数据来源于一些检测设备的采集数据,一些大表,有可能在极短时间内(如几秒钟)可能大部分都会变化, 而且主程序也有一些后台服务需要不断轮询.读写某种类型的设备,所以要求信息交互时间尽可能短. 2.之前的解决方案是把所有热点数据,统一加载到共享内存里边,到也能

[原]我在Windows环境下的首个Libevent测试实例

libevent对Windows环境也有很好的支持,不过初次学习和编译libevent简单实例,总是有一些陌生感的,只有成功编译并测试了一个实例,才会有恍然大悟的感觉.下面将要讲到的一个实例是我从网上抄过来的,原创文章地址为:http://www.felix021.com/blog/read.php?2068,表示感谢! 在给出我的第一个测试成功的例子代码之前,简要介绍一下libevent入门的基本知识.原文中作者有一段话是这样的: “基本的socket编程是阻塞/同步的,每个操作除非已经完成或

.Net 环境下C# 通过托管C++调用本地C++ Dll文件

 综述 : 本文章介绍.Net 环境下C# 通过托管C++调用本地C++ Dll文件, 示例环境为:VS2010, .Net4.0, Win7. 具体事例为测试C++, C#, 及C#调用本地C++Dll文件进行浮点运算效率的一部分. 如果需要查看三者的效率, 请继续阅读下面的文章. a 创建本地CPP类库 1. 创建本地CPP的Dll ---->EfficiencyNativeCPPDLL 2. 点击下一步 注意选择为DLL(D)项, 然后选择完成. 3.书写DLL文件 3.1 Efficie

Tomcat:云环境下的Tomcat设计思路——Tomcat的多实例安装

Cloud现在是一个热门的技术,Tomcat是学习Java的人一般都会接触的Web服务器,如果在Cloud环境下使用Tomcat,又当如何呢?不可避免的,要安装多个Tomcat了,这里称之为Tomcat实例,进而通过云平台来多Tomcat进行管理. 如何在一台机器上安装多个Tomcat实例呢? 这个问题想必使用过Tomcat的人都知道: 1)  下载Tomcat.zip 2)  解压,后的Tomcat拷贝1份,每一份就称之为一个实例,并修改Tomcat实例的tomcat_home/conf/se

Windows环境下修改Oracle实例监听IP地址

Windows环境下修改Oracle实例监听IP地址. 配置文件路径:<ORACLE_HOME>\NETWORK\ADMIN 如:C:\Oracle11gR2\product\11.2.0\dbhome_1\NETWORK\ADMIN 一.修改配置文件:listener.ora # listener.ora Network Configuration File: D:\Oracle11gR2\product\11.2.0\dbhome_1\network\admin\listener.ora

Linux环境下安装单实例MySQL 5.7

一.安装环境 1.操作系统版本:CentOS 7.5 2.MySQL版本:5.7.22(社区版) 3.MySQL安装包:mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz 二.安装步骤 1.检查系统中是否安装了其他版本的MySQL(root用户) 首先检查系统中是否安装了其他版本的MySQL,使用以下命令进行检查: yum list installed | grep mysql yum list installed | grep mariadb MariaDB数