Существует ли ограничение на количество вложенных циклов for?

Поскольку все имеет предел, мне было интересно, существует ли ограничение на количество вложенных циклов for или пока у меня есть память, я могу их добавить, может ли компилятор Visual Studio создать такую ​​программу?

Конечно, 64-х или более вложенных циклов for было бы непригодно для отладки, но возможно ли это?

private void TestForLoop()
{
  for (int a = 0; a < 4; a++)
  {
    for (int b = 0; b < 56; b++)
    {
      for (int c = 0; c < 196; c++)
      {
        //etc....
      }
    }
  }
}

Ответ 1

Я выхожу на конечность, публикуя это, но я думаю, что ответ:

Между 550 и 575

с настройками по умолчанию в Visual Studio 2015

Я создал небольшую программу, которая генерирует вложенные циклы for...

for (int i0=0; i0<10; i0++)
{
    for (int i1=0; i1<10; i1++)
    {
        ...
        ...
        for (int i573=0; i573<10; i573++)
        {
            for (int i574=0; i574<10; i574++)
            {
                Console.WriteLine(i574);
            }
        }
        ...
        ...
    }
}

Для 500 вложенных циклов программа все еще может быть скомпилирована. С 575 циклами компилятор выдает:

Предупреждение Анализатор AD0001 "Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer" выбрал исключение типа "System.InsufficientExecutionStackException" с сообщением "Недостаточно стека, чтобы продолжить выполнение программы безопасно. Это может произойти из-за слишком большого количества функций в стеке вызовов или функции в стеке, используя слишком много пространства стека. '.

с основным сообщением компилятора

ошибка CS8078: выражение слишком длинное или сложное для компиляции

Конечно, это чисто гипотетический результат. Если самый внутренний цикл выполняет больше, чем Console.WriteLine, тогда может быть меньше вложенных циклов до превышения размера стека. Кроме того, это может быть не строго технический предел в том смысле, что могут быть скрытые настройки для увеличения максимального размера стека для "Анализатора", упомянутого в сообщении об ошибке, или (если необходимо) для результирующего исполняемого файла. Однако эта часть ответа остается для людей, которые знают С# по глубине.


Update

В ответ на вопрос в комментариях:

Мне было бы интересно увидеть, что этот ответ расширен, чтобы "доказать" экспериментально, можете ли вы поместить 575 локальных переменных в стек, если они не используются в for-loops, и/или вы можете поместить 575 не вложенных для -loops в одной функции

В обоих случаях ответ: Да, это возможно. При заполнении метода 575 автоматически сгенерированных операторов

int i0=0;
Console.WriteLine(i0);
int i1=0;
Console.WriteLine(i1);
...
int i574=0;
Console.WriteLine(i574);

он все еще может быть скомпилирован. Все остальное меня удивило бы. Размер стека, необходимый для переменных int, составляет всего 2,3 КБ. Но мне было любопытно, и для того, чтобы проверить дальнейшие ограничения, я увеличил это число. В конце концов, он скомпилировал не, вызывая ошибку

ошибка CS0204: допускается только 65534 локальных жителей, включая те, которые сгенерированы компилятором.

что является интересным моментом, но уже наблюдалось в другом месте: Максимальное количество переменных в методе

Аналогично, 575 не вложенных for -loops, как в

for (int i0=0; i0<10; i0++)
{
    Console.WriteLine(i0);
}
for (int i1=0; i1<10; i1++)
{
    Console.WriteLine(i1);
}
...
for (int i574=0; i574<10; i574++)
{
    Console.WriteLine(i574);
}

также может быть скомпилирован. Здесь я также попытался найти предел и создал больше этих циклов. В частности, я не был уверен, что переменные цикла в этом случае также считают als "locals", потому что они находятся в их собственном { block }. Но все же более 65534 невозможно. Наконец, я добавил тест, состоящий из 40000 циклов шаблона

for (int i39999 = 0; i39999 < 10; i39999++)
{
    int j = 0;
    Console.WriteLine(j + i39999);
}

который содержит дополнительную переменную в цикле, но они, похоже, также считаются "локальными", и компилировать это невозможно.


Итак, чтобы суммировать: предел ~ 550 действительно вызван глубиной вложенности. Это также было сообщено сообщением об ошибке

ошибка CS8078: выражение слишком длинное или сложное для компиляции

документация об ошибке CS1647, к сожалению (но понятно) не указывает "измерение" сложности, а дает только прагматический совет

В компиляторе обрабатывался ваш код. Чтобы устранить эту ошибку, упростите свой код.

Чтобы подчеркнуть это снова: для частного случая глубоко вложенных for -loops все это довольно академично и гипотетично. Но поиск в Интернете сообщения об ошибке CS1647 показывает несколько случаев, когда эта ошибка появилась для кода, который, скорее всего, не был намеренно сложным, но создан в реалистичных сценариях.

Ответ 2

В спецификации языка С# или в CLR нет жесткого ограничения. Ваш код будет итеративным, а не рекурсивным, что может привести к переполнению стека довольно быстро.

Есть несколько вещей, которые могли бы считаться порогом, например счетчик (обычно) int, который вы использовали бы, который выделял бы int в памяти для каждого цикла (и до того, как вы выделили весь свой стек с ints...). Обратите внимание, что использование этого int требуется, и вы можете повторно использовать одну и ту же переменную.

Как отмеченный Марко, текущий порог больше в компиляторе, чем в фактической спецификации языка или времени исполнения. Как только это будет перекодировано, у вас может возникнуть еще несколько итераций. Если вы, например, используете Ideone, который по умолчанию использует старый компилятор, вы можете легко получить более 1200 for.

Однако для циклов индикатор плохой дизайн. Надеюсь, этот вопрос является чисто гипотетическим.

Ответ 3

Существует ограничение для всех С#, скомпилированных до MSIL. MSIL может поддерживать только 65535 локальных переменных. Если ваши циклы for похожи на те, которые были показаны в примере, для каждого из них требуется переменная.

Возможно, ваш компилятор мог бы распределять объекты в куче, чтобы действовать как хранилище для локальных переменных, обходя этот предел. Однако я не уверен, какие из этих странных результатов будут получены. Могут возникнуть проблемы, которые возникают при отражении, которые делают такой подход незаконным.

Ответ 4

От 800 до 900 для пустых петель for(;;).

Зеркальный подход Marco13, за исключением попыток for(;;):

for (;;)  // 0
for (;;)  // 1
for (;;)  // 2
// ...
for (;;)  // n_max
{
  // empty body
}

Он работал на 800 вложенных for(;;), но он дал ту же ошибку, что и Marco13 при попытке 900 циклов.

Когда он компилируется, появляется for(;;), чтобы блокировать поток без максимизации процессора; поверхностно, он действует как a Thread.Sleep().