Возможный дубликат:
Почему нет .net/С# исключить хвостовую рекурсию?
Возьмите следующий код С#:
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);
}
}
}
Компилятор С# (мой в любом случае) скомпилирует метод 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
}
Проблема с приведенным выше кодом заключается в том, что это вызовет переполнение стека (примерно на я = 262000 на моем оборудовании). Чтобы обойти эту проблему, некоторые языки выполняют так называемое исключение хвостового вызова или оптимизацию хвостовых вызовов (TCO). По сути, они превращают рекурсивный вызов в цикл.
Я понимаю, что 64-битная реализация .NET 4 JIT теперь выполняет TCO и позволяет избежать переполнения кода, как указано выше в CIL. Однако 32-разрядная JIT не работает. Моно тоже не кажется. Интересно, что JIT (который находится под временем и ресурсным давлением) делает TCO, в то время как компилятор С# этого не делает. Мой вопрос в том, почему сам компилятор С# больше не знает TCO?
Существует инструкция CIL, которая сообщает JIT выполнять TCO. Например, компилятор С# мог бы сгенерировать следующий 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). Магия находится в инструкции tail.
CIL. Компиляторы, такие как F #, будут генерировать CIL, который включает эту инструкцию автоматически.
Итак, мой вопрос в том, есть ли техническая причина, что компилятор С# не делает этого?
Я понимаю, что это исторически возможно просто не стоило того. Код, подобный Counter()
, не был распространен в идиоматических С# и/или .NET framework. Вы можете легко просмотреть TCO для С# как ненужную или преждевременную оптимизацию.
С введением LINQ и других вещей, похоже, как разработчики С# и С# движутся в более функциональных направлениях. Таким образом, было бы неплохо, если бы использование рекурсии не было такой опасной задачей. Но мой вопрос действительно более технический.
Отсутствует то, что делает TCO такой плохой идеей (или рискованной) для С#. Или есть что-то, что делает его особенно сложным, чтобы получить право? Именно это я и надеюсь понять. Любое понимание?
EDIT: Спасибо за отличную информацию. Я просто хотел быть ясным, что я не критикую отсутствие или даже требование этой функции. Я не очень заинтересован в рациональном распределении приоритетов. Мое любопытство - это то, что препятствия я не могу понять или понять, что делает это трудным, опасным или нежелательным делом.
Возможно, другой контекст поможет сосредоточиться на разговоре...
Скажем, я собирался реализовать свой собственный С# -подобный язык в среде CLR. Почему бы мне (кроме альтернативных) не включать автоматическую и прозрачную эмиссию "хвоста". где это необходимо? С какими проблемами я столкнулся или какие ограничения я бы представил в поддержке этой функции на языке, очень похожем на С#.
Еще раз спасибо (и заранее) за ответы.