Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
额外ldnull和tail的用途是什么。在F#实现与C#中?_C#_F#_Tail Recursion_Cil_Tail Call Optimization - Fatal编程技术网

额外ldnull和tail的用途是什么。在F#实现与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

以下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.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
的人。
另一种思考方式是:function
A
在返回之前不调用function
B
,而是调用function
B
,从而授予返回
B
的荣誉

因此,实际上,这种神奇的技术允许我们在不消耗堆栈上的一点的情况下进行调用,这意味着您可以执行任意多个这样的调用,而不会冒堆栈溢出的风险。这在函数式编程中非常重要,因为它允许高效地实现递归算法


它被称为“尾部调用”,因为调用
B
发生在
A

的尾部,也就是说,我怀疑在这种情况下,该函数已转换为尾部调用-新函数将使用旧oneNote的堆栈空间,这可能会导致性能下降,请参见以下问题: