C# 尾巴。ILAsm中的前缀–;有使用的例子吗?
ECMA-335,III.2.4规定了可在递归函数中使用的C# 尾巴。ILAsm中的前缀–;有使用的例子吗?,c#,.net,f#,clr,ilasm,C#,.net,F#,Clr,Ilasm,ECMA-335,III.2.4规定了可在递归函数中使用的tail.前缀。然而,我在C#和F#代码中都找不到它的用法。是否有在中使用的示例?您不会在当前MS C#编译器生成的任何代码中找到它。您可以在F#编译器生成的代码中找到它,但由于几乎相反的原因,它没有您预期的那么多 现在,首先纠正你陈述中的一个错误: ECMA-335,III.2.4规定了尾部。可以在递归函数中使用的前缀 严格来说,这不是事实。tail.前缀可用于tail调用;不是所有递归函数都是尾部递归,也不是所有尾部调用都是递归的一部
tail.
前缀。然而,我在C#和F#代码中都找不到它的用法。是否有在中使用的示例?您不会在当前MS C#编译器生成的任何代码中找到它。您可以在F#编译器生成的代码中找到它,但由于几乎相反的原因,它没有您预期的那么多
现在,首先纠正你陈述中的一个错误:
ECMA-335,III.2.4规定了尾部。可以在递归函数中使用的前缀
严格来说,这不是事实。tail.
前缀可用于tail调用;不是所有递归函数都是尾部递归,也不是所有尾部调用都是递归的一部分
尾部调用是对函数(包括OOP方法)的任何调用,其中该代码路径中的最后一个操作是进行该调用,然后返回它返回的值,或者如果调用的函数未返回值,则仅返回该值。因此:
int DoSomeCalls(int x)
{
if(A(x))
return B(x);
if(DoSomeCalls(x * 2) > 3)
{
int ret = C(x);
return ret;
}
return D(DoSomeCalls(x-1));
}
在这里,对B
和D
的调用是尾部调用,因为调用之后唯一要做的事情就是返回它们返回的值。对C
的调用不是尾部调用,但是可以通过直接返回来删除对ret
的冗余分配,从而轻松地将其转换为尾部调用。对A
的调用不是尾部调用,对DoSomeCalls
的调用也不是尾部调用,尽管它们是递归的
现在,正常的函数调用机制依赖于实现,但通常涉及将调用后可能需要的充实值保存到堆栈中,将参数与当前指令位置(返回)一起放入堆栈和/或寄存器中,移动指令指针,然后,当指令指针移回调用完成后的位置时,从寄存器或堆栈读取返回值。通过尾部调用,可以跳过很多步骤,因为被调用的into函数可以使用当前堆栈帧,然后直接返回到前面的调用方
tail.
前缀请求通过调用完成此操作
虽然这不一定与递归有关,但您谈论递归是正确的,因为在递归情况下消除尾部调用的好处比其他情况更大;当实际使用函数调用机制时,在堆栈空间中进行O(n)的调用会变成堆栈空间中的O(1),同时降低每项恒定时间成本(因此在这方面仍然是O(n),但O(n)时间意味着需要n×k秒,我们有更小的k)。在许多情况下,这可能是有效的调用与抛出StackOverflowException
的调用之间的区别
现在,在ECMA-335中,有一些案例说明了如何跟踪。
可能并不总是被尊重。尤其是§III.2.4中的文本规定:
还可能存在特定于实现的限制,以防止出现尾部。前缀在某些情况下不被遵守
从最宽松的角度来看,我们可以将其解释为在所有情况下都可以防止它
相反,允许抖动应用所有方式的优化,包括执行尾部调用消除,即使tail没有请求它。
因此,实际上有四种方法可以消除IL中的尾部调用:
tail.
前缀,并将其兑现(不保证)尾。
前缀,但要让jitter决定以任何方式应用它(甚至更不保证)jmp
IL指令,这实际上是尾部调用消除的一种特殊情况(C#从未使用过,因为它会产生无法验证的代码以获得通常相对较小的增益,尽管由于相对简单,有时手工编码可能是最简单的方法)尾部。
C有一个通用的方法,即不严重优化生成的代码。已经进行了一些优化(特别是消除死代码),但大部分情况下,因为优化工作可能只是重复抖动所做的工作(甚至会妨碍他们)优化的缺点(更多的复杂性意味着更多可能的bug,而IL会让许多开发人员更加困惑)使用tail.
是一个典型的例子,因为有时坚持使用tail调用的成本实际上比使用.NET节省的成本要高,所以如果抖动已经在尝试解决什么时候是个好主意,那么C#编译器很有可能会让事情变得更糟,其他的都一样
还值得注意的是,对于C风格语言(如C#)最常见的编码风格:
let rec even n =
if n = 0 then
true
else
odd (n-1)
and odd n =
if n = 1 then
true
else
even (n-1)
void ClearAllNodes(Node node)
{
if(node != null)
{
node.Value = null;
ClearAllNodes(node.Next)
}
}
void ClearAllNodes(Node node)
{
start:
if(node != null)
{
node.Value = null;
node = node.Next;
goto start;
}
}
void ClearAllNodes(Node node)
{
while(node != null)
{
node.Value = null;
node = node.Next;
}
}