C# IL和参数
IL有一些操作码用于使用参数进行操作,例如C# IL和参数,c#,clr,cil,il,C#,Clr,Cil,Il,IL有一些操作码用于使用参数进行操作,例如Ldarg.0,Ldarg.1等等 我知道在执行调用操作码之前,这些参数会被推到堆栈上,在某些情况下,Ldarg.0用于获取对此的引用(例如成员) 我的问题是:当启动调用时,这些参数存储在哪里?调用方堆栈的副本是否可以从执行的调用中访问 我在哪里可以找到关于那个主题的更多信息 更新 我知道虚拟机是抽象的,JIT编译器会处理这些问题,但是让我们想象一下,如果IL被解释,就像在.NET Micro Framework上一样,可能是一个很好的起点 例如,第I.
Ldarg.0
,Ldarg.1
等等
我知道在执行调用
操作码之前,这些参数会被推到堆栈上,在某些情况下,Ldarg.0
用于获取对此
的引用(例如成员)
我的问题是:当启动调用时,这些参数存储在哪里?调用方堆栈的副本是否可以从执行的调用中访问
我在哪里可以找到关于那个主题的更多信息
更新
我知道虚拟机是抽象的,JIT编译器会处理这些问题,但是让我们想象一下,如果IL被解释,就像在.NET Micro Framework上一样,可能是一个很好的起点
例如,第I.12.4.1节规定:
CIL代码生成器发出的指令包含足够的信息,供CLI的不同实现使用不同的本机调用约定。所有方法调用初始化方法状态区域(见§I.12.3.2),如下所示:
- 输入方法时是否初始化本地变量和内存池内存
- 每个参数的类型和参数数组的长度(有关变量参数列表,请参见下文)
- 每个局部变量的类型和局部变量数组的长度
callvirt
(作为一个示例)的描述如下:
callvirt
在调用方法之前,从计算堆栈中弹出对象和参数。如果方法具有返回值,则在方法完成时将其推送到堆栈上。在被叫方,obj参数作为参数0访问,arg1作为参数1访问,依此类推
现在,这一切都处于规范级别。实际的实现很可能决定只让函数调用继承当前方法堆栈的前n个元素,这意味着参数已经在正确的位置。MSIL使用虚拟机的规范。传递给方法的参数的心智模型是它们存在于数组中。其中,Ldarg从该数组中选择一个元素来访问方法参数,并将其推送到计算堆栈上。Opcodes.Ldarg_0是更通用的Opcodes.Ldarg IL指令的缩写版本,它通过始终拾取元素0来节省两个字节。对于第二个参数,Opcodes.Ldarg_1也有相同的想法。当然,非常常见的是,Ldarg只有在方法有4个以上参数时才会变得“昂贵”。强调双引号,这不是你所担心的那种费用 运行时参数的实际存储非常不同。这取决于您使用的抖动,不同的体系结构使用不同的方式传递参数。通常,前几个参数通过cpu寄存器传递,其余参数通过cpu堆栈传递。像x64或ARM这样的处理器有很多寄存器,所以使用寄存器传递的参数比x86多。受该体系结构的规则控制。IL(现在称为CIL,公共中间语言,而不是MSIL)描述了虚拟堆栈机器上的操作。JIT编译器接受IL指令并将其编译成机器代码 调用方法时,JIT编译器必须遵守调用约定。此约定指定如何将参数传递给被调用的方法,如何将返回值传递回调用方,以及谁负责从堆栈中删除参数(调用方或被调用方)。在本例中,我使用调用约定,但实际的JIT编译器使用其他约定 一般方法 具体细节取决于实现,但.NET和Mono JIT编译器用于将CIL编译为机器代码的一般方法如下:
ldarg.1 // Load argument 1 on the stack
ldarg.3 // Load argument 3 on the stack
add // Pop value2 and value1, and push (value1 + value2)
call int32 MyMethod(int32) // Pop value and call MyMethod, push result
ret // Pop value and return
ldarg.1 %reg0 <- // Load argument 1 in %reg0
ldarg.3 %reg1 <- // Load argument 3 in %reg1
add %reg0 <- %reg0, %reg1 // %reg0 = (%reg0 + %reg1)
// Call MyMethod(%reg0), store result in %reg0
call int32 MyMethod(int32) %reg0 <- %reg0
ret <- %reg0 // Return %reg0
mov %reg0, [addr_of_arg1] // Move argument 1 in %reg0
mov %reg1, [addr_of_arg3] // Move argument 3 in %reg1
add %reg0, %reg1 // Add %reg1 to %reg0
push %reg0 // Push %reg0 on the real stack
call [addr_of_MyMethod] // Call the method
add esp, 4
mov %reg0, eax // Move the return value into %reg0
mov eax, %reg0 // Move %reg0 into the return value register EAX
ret // Return
mov eax, [addr_of_arg1] // Move argument 1 in EAX
mov ecx, [addr_of_arg3] // Move argument 3 in ECX
add eax, ecx // Add ECX to EAX
push eax // Push EAX on the real stack
call [addr_of_MyMethod] // Call the method
add esp, 4
mov ecx, eax // Move the return value into ECX
mov eax, ecx // Move ECX into the return value register EAX
ret // Return