可能重复:
.net / C#为什么不消除尾部递归?采用以下C#代码:
using System; namespace TailTest { class MainClass { public static void Main (string[] args) { Counter(0); } static void Counter(int i) { Console.WriteLine(i); if (i < int.MaxValue) Counter(++i); } } }
C#编译器(我的 无论如何)会将Counter方法编译为以下CIL:
.method private static hidebysig default void Counter (int32 i) cil managed { .maxstack 8 IL_0000: ldarg.0 IL_0001: call void class [mscorlib]System.Console::WriteLine(int32) IL_0006: ldarg.0 IL_0007: ldc.i4 2147483647 IL_000c: bge IL_0019 IL_0011: ldarg.0 IL_0012: ldc.i4.1 IL_0013: add IL_0014: call void class TailTest.MainClass::Counter(int32) IL_0019: ret }
上述代码的问题是,它将导致堆栈溢出(在我的硬件上大约为i = 262000)。为解决此问题,某些语言会执行此操作
我的理解是,.NET 4 JIT的64位实现现在可以执行TCO并避免了TCO,这称为递归调用消除或拖尾调用优化(TCO)。 上面的CIL这样的代码溢出了,但是32位的JIT却没有,Mono也没有。有趣的是,JIT(在时间和资源压力下)执行了TCO,而C#编译器却没有。 问题是为什么C#编译器本身不更了解TCO?
有一条CIL指令告诉JIT执行TCO。例如,C#co mpiler可能会生成以下CIL:
.method private static hidebysig default void Counter (int32 i) cil managed { .maxstack 8 IL_0000: ldarg.0 IL_0001: call void class [mscorlib]System.Console::WriteLine(int32) IL_0006: ldarg.0 IL_0007: ldc.i4 2147483647 IL_000c: bge IL_001c IL_0011: ldarg.0 IL_0012: ldc.i4.1 IL_0013: add IL_0014: tail. IL_0017: call void class TailTest.MainClass::Counter(int32) IL_001c: ret }
与原始文件不同,该代码将不会溢出,即使在32位JIT(.NET和Mono)上也将运行到完成。 魔术在
尾巴中。
CIL指令。 像F#这样的编译器会自动生成包含该指令的CIL。所以我的问题是,C#编译器不执行此操作有技术上的原因吗?
我知道它在历史上可能只是不值得。 像
Counter()
这样的代码在惯用的C#和/或.NET框架中并不常见。 您可以轻松地将C#的TCO视为不必要或过早的优化。随着LINQ和其他功能的引入,似乎C#和C#开发人员都朝着更多的功能方向发展。 因此,如果使用递归不是一件不安全的事情,那将是很好的。 但是我的问题确实是技术性更高的问题。
遗漏了一些使TCO这样的东西对C#来说不是一个好主意(或冒险)。 还是有什么事情使正确做事变得特别棘手? 这确实是我希望了解的。 有任何见解吗?
编辑:感谢您提供的宝贵信息。 我只是想明确一点,我不是在批评缺乏甚至不要求此功能。 我对围绕优先级排序的合理性并不超级感兴趣。 我的好奇心是我可能无法理解或理解的障碍,这使这件事变得困难,危险或不受欢迎。
也许不同的背景将有助于使谈话的重点...
让我说我要实现 我在CLR上自己的类似C#的语言。 我为什么不(机会成本除外)包括自动透明地发出“尾巴”。 说明在适当的地方? 在非常类似于C#的语言中支持该功能时,我会遇到什么挑战或遇到什么限制。
再次(并提前)感谢您的答复。