前言
内存空间是程序的灵魂,没有了它,这个程序只能是一堆废代码。本篇来以.Net8的JIT第一个加载的C#函数StelemRef(它在System.Private.CoreLib.dll)为例,看下.Net8 PreView3里面是如何分配内存空间的大小的。
概括
一.分配要素
StelemRef的C#原型(为了便于阅读,代码经过提炼):
[DebuggerHidden]
[StackTraceHidden]
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private unsafe static void StelemRef(Array array, IntPtr index, object obj)
{
ref object ptr = ref Unsafe.As<CastHelpers.ArrayElement[]>(array)[(int)(checked((IntPtr)(unchecked((long)index))))].Value;
void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
if (obj == null) {//省略}
if (elementType != (void*)RuntimeHelpers.GetMethodTable(obj) && !(array.GetType() == typeof(object[]))) {//省略}
CastHelpers.WriteBarrier(ref ptr, obj);
}
它的IL代码(为了便于阅读,代码经过提炼):
.method private hidebysig static void StelemRef(class System.Array 'array',native int index,object obj) cil managed
{
.locals (object& V_0,
void* V_1,
bool V_2,
bool V_3,
bool V_4)
//为了避免过多代码干扰,此处上下省略一万行
IL_0002: call !!0
IL_0016: call valuetype
IL_002d: call valuetype
IL_0040: call void
IL_0050: callvirt instance class System.Type
IL_005a: call class System.Type
IL_005f: call bool System.Type::op_Equality(class
IL_006f: call void
IL_0074: nop
IL_0075: ret
} // end of method CastHelpers::StelemRef
可以看到这个StelemRef函数的IL代码有三个参数,五个本地变量(.locals)。以及8个call的调用。这些都是分配的内存空间的必要元素。
二.分配的方式
1.参数
首先这个三个参数,array数组类型,index整型,obj的object类型,它们在JIT里面不参与内存空间的计算,所以被排除了。
2.本地变量
五个本地变量,object& V_0是个object类型,占8个字节,V_1是void* 类型占8字节,V_2是bool占四个字节,V_3是bool占四个字节,V_4是bool占四个字节。那么本地变量的占据的空间是8+8+4+4+4=0x1C(28)个字节。
3.call
再来看下8个call占用的空间,第一个call和最后一个call分别分配了4字节的空间2*4==8,中间的六个call每个8字节空间6*8==48,那么8个call总共占据的空间是56个字节。
4.预留空间
在第一个call和第二个call之间预留了8字节,在最后一个call(也就是第8个call)后面预留了44个字节。
5.画像
它的整体画像如下所示:
28 (0-7索引,三个参数及五个本地变量的空间) +
4 (9索引,因为前面三个参数和五个本地变量占据了8(0-7)个索引,而第八个索引为空,所以此处是9,(第一个call占据的空间)) +
8 (无索引,第一个call和第二个call中间的预留空间) +
48 (a-f索引 第二个call和第七个call占据的空间,总共六个call,每个八字节) +
4 (0x10 index(索引) 最后一个call占据的空间) +
(4 +32 +8) (这后面的全都是预留的空间)
那么28+4+8+48+4+4+32+8=136(0x88个字节)
三.观察机器码的分配
000002255E3020B8 55 push rbp
000002255E3020B9 57 push rdi
000002255E3020BA 48 81 EC 88 00 00 00 sub rsp,88h
000002255E3020C1 48 8D AC 24 90 00 00 00 lea rbp,[rsp+90h]
000002255E3020C9 C5 D8 57 E4 vxorps xmm4,xmm4,xmm4
000002255E3020CD C5 F9 7F 65 A0 vmovdqa xmmword ptr [rbp-60h],xmm4
以上是StelemRef函数的机器头部代码,第三行 sub rsp,88h。这个88h就是上面计算的内存空间0x88,它进入的时候给StelemRef函数分配内存的栈空间的大小0x88。