val*_*ron 7 .net c# struct cil interface
考虑:
interface I { void M(); }
struct S: I { public void M() {} }
// in Main:
S s;
I i = s;
s.M();
i.M();
Run Code Online (Sandbox Code Playgroud)
以及IL for Main:
.maxstack 1
.entrypoint
.locals init (
[0] valuetype S s,
[1] class I i
)
IL_0000: nop
IL_0001: ldloc.0
IL_0002: box S
IL_0007: stloc.1
IL_0008: ldloca.s s
IL_000a: call instance void S::M()
IL_000f: nop
IL_0010: ldloc.1
IL_0011: callvirt instance void I::M()
IL_0016: nop
IL_0017: ret
Run Code Online (Sandbox Code Playgroud)
First(IL_000a),S::M()使用值类型调用this.Next(IL_0011),使用引用(盒装)类型调用它.
这是如何运作的?
我可以想到三种方式:
I::M编译了两个版本,用于value/ref类型.在vtable中,它存储一个用于ref类型,但静态调度的调用使用一个用于值类型.这是丑陋的,不太可能,但可能.this,然后调用实际方法.这听起来效率低下,因为所有方法的参数都必须通过两次调用来复制.callvirt.效率更低:所有人都会callvirt受到轻微的惩罚.简而言之,在方法本身中, 的值struct始终通过指针访问。这意味着该方法不会像作为struct普通参数传递一样运行,它更像是一个ref参数。这也意味着该方法不知道它是否对装箱值进行操作。
长答案:
首先,如果我编译你的代码,则s.M();不会生成任何代码。JIT 编译器足够智能,可以内联方法,内联空方法不会产生任何代码。所以,我所做的就是申请[MethodImpl(MethodImplOptions.NoInlining)]以S.M避免这种情况。
现在,这是您的方法生成的本机代码(省略函数序言和结尾):
// initialize s in register AX
xor eax,eax
// move s from register AX to stack (SP+28h)
mov qword ptr [rsp+28h],rax
// load pointer to MethodTable for S to register CX
mov rcx,7FFDB00C5B08h
// allocate memory for i on heap
call JIT_TrialAllocSFastMP_InlineGetThread (07FFE0F824C10h)
// copy contents of s from stack to register C
movsx rcx,byte ptr [rsp+28h]
// copy from register CX to heap
mov byte ptr [rax+8],cl
// copy pointer to i from register AX to register SI
mov rsi,rax
// load address to c on stack to register CX
lea rcx,[rsp+28h]
// call S::M
call 00007FFDB01D00C8
// copy pointer to i from register SI to register CX
mov rcx,rsi
// move address of stub for I::M to register 11
mov r11,7FFDB00D0020h
// ???
cmp dword ptr [rcx],ecx
// call stub for I::M
call qword ptr [r11]
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,call最终都会调用相同的代码(这只是一条ret指令)。第一次,CX 寄存器指向堆栈分配s(上述代码中的 SP+28h),第二次指向堆分配i(调用堆分配函数后的 AX+8)。