关于尾部调用,我的一点贡献
下文摘自我翻译《Expert .NET 2.0 IL Assembler》第13章中的一节,这本书是IL的权威之作,作者就是IL开发Team的:
尾部调用
尾部调用类似于方法调用(jmp),二者都是放弃当前方法,并将参数传递到尾部调用(跳转)的方法上。然而,由于尾部调用的参数必须在计算堆栈上被显式加载(与跳转不同,尾部调用丢弃了当前方法的堆栈帧[1](stack frame),它保护了栈的框架并可以使用已经被加载的参数),与跳转不同,尾部调用不需要被调用方法的全部签名匹配调用方法的签名;而只要求返回类型是相同的或兼容的。尾部调用在大规模的递归方法实现中是非常有用的;调用方的栈帧在尾部调用的过程中会被丢弃,因此,不管递归有多么的深,栈的溢出都是没有风险的。对于功能性语言来说这是很重要的,它们会使用递归来代替循环。
尾部调用是通过在call、callvirt或calli指令前加上前缀tail.指令来识别的:
l tail. (0xFE 0x14) 将随后的调用指令标注为尾部调用。该指令不使用参数和栈。和其他前缀指令unaligned.和volatite.一样,ILAsm要求该指令与其后随的指令之间至少有一个空格符分隔。
跳转方法和尾部调用的区别在于尾部调用指令对(tail call pair)原则上是可验证的,只要该指令后面紧跟着ret指令(这取决于调用参数的可验证性)。和其他前缀指令一样,跳过前缀并直接转移到前缀指令(如call、callvirt或calli)是非法的。
[1] 译注:stack frame,即堆栈帧,是堆栈中的一块区域,它保存着一个函数的返回地址,和该函数内部使用的局部数据(Local Data),它是由函数入口处的SUB ESP、48h之类的语句来建立的。
详细内容参见http://www.zaoxue.com/article/tech-32942.htm。
此外,给出尾部调用的几个异常:
0x80131899 |
Can not pass byref to a tail call. |
0x |
Missing ret. |
0x8013189B |
Void ret type expected for tail call. |
0x |
Tail call return type not compatible. |
0x8013189D |
Stack not empty after tail call. |