C#-StructLayoutAttribute(结构体布局)
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++中默认的处理方式相同(8+8+8=24),即按照结构中占用空间最大的成员进行对齐(Align)。10byte的数据却占有了24byte,严重地浪费了内存,所以如果我们正在创建一个与非托管代码没有任何互操作的struct类型,最好还是不要使用默认的StructLayoutAttribute(LayoutKind.Sequential)特性。
2.[StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Explicit)]
struct BadStruct
{
[FieldOffset()]
public bool i; //1Byte
)]
public double c;//8byte
)]
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 ;
e.i = true;
Console.WriteLine(e.c);输出的结果不再是0了,而是4.94065645841247E-324,这是因为e.c和e.i共享同一个byte,执行“e.i = true;时”也改变了e.c,CPU在按照浮点数的格式解析e.c时就得到了这个结果.所以在运用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)));
}
// 测试结果:
c:
b:即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)。
C#-StructLayoutAttribute(结构体布局)的更多相关文章
- 如何用vs查看结构体布局
今天遇到一个问题: 假设在每个系统的structA 结构不同,我们在windbg看了以后直接拿来用,自己定义成结构体,如何来验证这个结构体内存布局是否和windbg一致. 当然笨办法是自己一个个成员数 ...
- go语言之行--结构体(struct)详解、链表
一.struct简介 go语言中没有像类的概念,但是可以通过结构体struct实现oop(面向对象编程).struct的成员(也叫属性或字段)可以是任何类型,如普通类型.复合类型.函数.map.int ...
- 飞控入门之C语言结构体、枚举
结构体 先来说明一下,结构体存在的意义.比如说有一只猫,要在C语言程序中综合描述它,那么可以这样说,它的体重是float类型的,颜色是char类型的,它的一些食物名字是一个数组,那么如果分开定义这些变 ...
- VC中结构体的内存布局
看了 VC++中内存对齐 这篇文章,感觉说复杂了,根据我的总结,要算出结构体的内存大小和偏移量,只要清楚结构体各成员的内存布局就行了,下面介绍一下我总结的规则,有不对之处,欢迎回复. 1.实际PACK ...
- C语言结构体及其内存布局
code[class*="language-"], pre[class*="language-"] { color: rgba(51, 51, 51, 1); ...
- C#调用C++ dll时,结构体引用传参的方法
写了一个C++的LogLog Logit 四参数等算法的接口dll,给C#调用,但是发现传参有问题 如 extern "C" _declspec(dllexport) bool ...
- C#结构体和字节数组的转换函数
在通信过程中,一般我们都会操作到字节数组.特别是希望在不同语言编程进行操作的时候. 虽然C#提供了序列化的支持,不用字节数组也行.但操作字节数组肯定会碰到. 一般都会采用结构来表示字节数组.但结构 ...
- .NET C#基础(5):结构体 - 高性能代码的基石
0. 文章目的 本文面向有一定.NET C#基础知识的学习者,介绍C#中结构体定义.使用以及特点. 1. 阅读基础 了解C#基本语法 了解.NET中的栈与托管堆 2. 值类型 2.1 .N ...
- C# Struct结构体里数组长度的指定
typedef struct Point{ unsigned short x; unsigned short y; }mPoint;//点坐标 typedef struct Line{ mPoint ...
随机推荐
- 【Linux】使用awk批量杀进程
pkill 杀一个用户的所有进程 pkill -u bingo 批量杀进程 使用ps -ef|grep aaa 查出相关进程 使用grep -v grep 过滤掉grep本身产生的进程 使用awk 打 ...
- 二十七 Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP
设置代理ip只需要,自定义一个中间件,重写process_request方法, request.meta['proxy'] = "http://185.82.203.146:1080&quo ...
- Hessian序列化
当子类定义了和父类同名的属性时,经过hessian传输,会导致该属性值丢失.因为hessian发送二进制数据时,子类数据在前,父类数据在后.接收二进制数据时,子类数据在前,父类数据在后.所以对于同名字 ...
- 最近ES遇到discover老是失败问题,ping主节点和node节点正常,抓包发现了大量的retransmission、tcp out of order、dup ack问题。
最近ES遇到discover老是失败问题,ping主节点和node节点正常,抓包发现了大量的retransmission.tcp out of order.dup ack问题. Explanation ...
- vue elementui二级联动下拉选项demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- py-faster-rcnn:在windows上配置
0.先说一下本机配置 opencv2+cuda7.5+cudnn+anaconda,这些基础的之前都是配置好了的,python环境建议使用anaconda,用到的库基本都有了,好像没有easydict ...
- Visual Studio 2017再现C语言经典例题(一)
1.编写一个程序,输入a.b.c这3个值,输出其中最大者. 2.将“China”译成密码.密码规律:用原来的字母后面第4个字母代替原来的字母.例如,字母A后面第4个字母是E,用E代替A,因此,Chin ...
- CMDB配置资源管理数据库(理解)
CMDB是运维自动化的基础,它为日志系统,发布系统,监控系统等运维系统(ELK,zabbix,open-falcon)提供接口函数, 第一种方式:Agent方法实现,agent不能直接访问数据库,因为 ...
- Redis数据结构:跳跃表
1. 跳跃表是有序集合(zset)的底层实现之一: 2. 由zskiplist和zskiplistNode组成: 3. 每个跳跃表节点的层数都是1-32之间的随机数(每创建一个节点的时候,程序会随机生 ...
- C++内存分配与对象构造的分离
在C++中,我们基本用new(delete)操作符分配(释放)内存.new操作符为特定类型分配内存,并在新分配的内存中构造该类型的一个对象.new表达式自动运行合适的构造函数来初始化每个动态分配的类类 ...