额外ldnull和tail的用途是什么。在F#实现与C#中?
以下C#函数:额外ldnull和tail的用途是什么。在F#实现与C#中?,c#,f#,tail-recursion,cil,tail-call-optimization,C#,F#,Tail Recursion,Cil,Tail Call Optimization,以下C#函数: T ResultOfFunc<T>(Func<T> f) { return f(); } let resultOfFunc func = func() 但等效的F#函数: T ResultOfFunc<T>(Func<T> f) { return f(); } let resultOfFunc func = func() 汇编如下: IL_0000: nop IL_0001: ldarg
T ResultOfFunc<T>(Func<T> f)
{
return f();
}
let resultOfFunc func = func()
但等效的F#函数:
T ResultOfFunc<T>(Func<T> f)
{
return f();
}
let resultOfFunc func = func()
汇编如下:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: tail.
IL_0005: callvirt 04 00 00 0A
IL_000A: ret
(两者都处于释放模式)。开头有一个额外的nop,我对此不太好奇,但有趣的是额外的ldnull
和尾部。
说明
我的猜测(可能是错误的)是,如果函数是void
,则必须使用ldnull
,因此它仍然返回某个(unit
),但这并不能解释尾部指令的用途。如果函数确实在堆栈上推送了一些东西,那么会发生什么呢?它不是被一个额外的空值卡住了而没有弹出吗?C和F版本有一个重要的区别:C函数没有任何参数,但F版本有一个unit
类型的参数。该单位
值显示为ldnull
(因为null
被用作唯一单位
值的表示,()
)
如果要将第二个函数转换为C#,它将如下所示:
T ResultOfFunc<T>( Func<Unit, T> f ) {
return f( null );
}
T结果函数(函数f){
返回f(空);
}
至于.tail
指令,这就是所谓的“tail调用优化”。
在常规函数调用期间,将返回地址推送到堆栈(CPU堆栈)上,然后调用函数。当函数完成时,它执行“return”指令,将返回地址从堆栈中弹出,并在堆栈中传输控制。
但是,当函数A
调用函数B
,然后立即返回函数B
的返回值时,CPU可以跳过堆栈上的额外返回地址,执行到B
的“跳转”而不是“调用”。这样,当B
执行“return”指令时,CPU将从堆栈中弹出返回地址,该地址将不会指向A
,而是指向首先调用A
的人。
另一种思考方式是:functionA
在返回之前不调用functionB
,而是调用functionB
,从而授予返回B
的荣誉
因此,实际上,这种神奇的技术允许我们在不消耗堆栈上的一点的情况下进行调用,这意味着您可以执行任意多个这样的调用,而不会冒堆栈溢出的风险。这在函数式编程中非常重要,因为它允许高效地实现递归算法
它被称为“尾部调用”,因为调用B
发生在A
的尾部,也就是说,我怀疑在这种情况下,该函数已转换为尾部调用-新函数将使用旧oneNote的堆栈空间,这可能会导致性能下降,请参见以下问题: