探索C#中值类型/引用类型为空时所占用的内存大小
前言
又看到一个比较让人害怕的面试题,实例化一个C#的空class会占用多少内存空间。 。。。为什么会有这种问题,当时第一反应肯定不是零,至少要像C++那样有一个区别地址的偏移吧,不然找都找不到,所以就猜了1。 结果果然不出我所料,我蒙错了
,不会就学。 找了一大圈,终于找到一个讲的全面的帖子:https://docs.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the-clr-creates-runtime-objects
下面的内容基本就是围绕这篇文章来说的,也可以直接去看原文。
正文
环境:.Net Framework 4.7.2
IDE:Rider 2019.3.3
编译环境:Debug prefer x86(32bit)
值类型
值类型相较于引用类型好理解多了,他直接分配到栈上,不需要GC,不需要引用机制。所以它空间的计算也是最简单的。直接计算其中包含的字段空间即可(有一些特性会影响内存的编排,不过不在本篇文章讨论范围内了)。 计算机中的内存通常以字节的形式组织,在对象地址位置可用的最小内存为1字节。所以空值类型的对象大小是1
字节。
引用类型
先来看代码
1 | using System; |
它的运行时堆栈是这样的
引用类型变量(如smallObj)以固定大小(4字节)存储在栈上,并指向在托管堆上分配的对象实例的地址。 smallObj的实例包含指向相应类型的MethodTable的TypeHandle(类型对象指针)和syncblk index(同步块索引,用来做线程同步的,这里就不详细讲了,大家可以去原文查看)。
每种声明的类型都有一个MethodTable
,并且同一类型的所有对象实例
都指向同一MethodTable
。其中包含大量信息,包含有关类型的信息(接口,抽象类,具体类,COM Wrapper和代理),实现的接口数,用于方法调用的接口映射,方法表中的插槽数以及表的信息,指向实现的插槽数量(包括虚函数的实现也在这个MethodTable中)。 当前的GC实现对于一个空类来说,需要至少12个字节的对象实例。如果一个类没有定义任何实例字段,它将产生4个字节的开销(用于分配到栈上来对他进行引用)。其余部分(8个字节)的将由同步块索引和类型对象指针占用。 所以对于一个空引用类型来说,他所占用的空间大小就是12
。
总结
32位系统下(一个指针的大小为4字节)
一个空值类型占用内存为1字节。
一个空引用类型占用空间为12字节,其中包括4字节的栈内存分配,4字节的类型对象指针,4字节的同步块索引。
或许有些朋友要问,为什么引用类型分配在栈上指针算在其所占内存,而值类型不算呢?这是因为我们引用类型在C#中需要进行GC托管,这就需要遍历每个引用类型的内存实例的引用,就算这个引用只在一次栈帧中存在,GC的时候也会照例执行,所以栈帧中的引用是要算在引用类型所占内存中的。
踩过的坑(针对引用类型)
- 使用sizeof操作得到的只是指向对象的指针,也就是说全是4。
- Marshal.SizeOf得到的大小也不是正确的对象大小,具体原因未知。
推荐一个可以查看reference/value type的库
https://github.com/sidristij/dotnetex
饭后甜点1,C++空类型的空间占用
我们都知道,C++当中并没有对位C#的值类型和引用类型概念,只有一般数据类型,指针,引用,这几个概念。所以概括起来也比较容易一些。 即所有空数据类型所占用的内存大小都是1
字节! 这是因为,对象需要有不同的地址。有了不同的地址,就可以比较指针和对象的身份。 如果不为空的话,编译器会对其进行优化,会是正常的字段大小相加+内存对齐的结果。也就是说,如果class A{int i;};。它的大小就是4字节。
饭后甜点2,C++非空类型的空间占用
非空类型就比较麻烦了,涉及内存对齐的问题。 下面是一个笔试题
1 |
|
这是我当时做的总结:
-
S1
- float占4个字节
- short占2个字节
- int占4个字节
- char占1个字节
-
S2
- double占8个字节
- S1展开另算
- char占1个
- int占4个
结构体在内存中的存放按单元进行存放,每个单元的大小取决于结构体中最大基本类型的大小,为了优化性能会自动进行内存对齐,整个结构体的大小必须是最大成员变量类型所占字节数的整数倍 #pragma pack(16)定义补齐方式为16字节(在这里其实和32位默认的对齐方式也没差,用S1的double作为最宽长度即可)
所以,S2就是 先算S2自带的:double(8)+char(1->4)+int(4)=16 再单独算S1:float(4)+short(2->4)+int(4)+char(1->4)=16
所以S2所占总空间为32
对于C++类,结构体分配内存拓展阅读: https://blog.csdn.net/chen1234520nnn/article/details/83341266 https://www.cnblogs.com/-zhangnian/p/6422559.html https://www.cnblogs.com/linuxAndMcu/p/10389096.html https://blog.csdn.net/GAMEloft9/article/details/47440941