Чи існує обмеження на кількість вкладених циклів 'for'?


79

Оскільки все має обмеження, мені було цікаво, чи існує обмеження кількості вкладених 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....
      }
    }
  }
}

78
Ви програміст: дайте відповідь на власне запитання за допомогою комп’ютерного програмування. Напишіть програму, яка генерує, компілює та запускає програми з 1, 2, 4, 8, 16, 32, ... вкладеними циклами, і ви дуже швидко дізнаєтесь, чи існує обмеження.
Ерік Ліпперт,

38
№1. ВП не вказав, що це якимось чином пов'язано з виробничим кодом, на відміну від просто гіпотетичного типу "що-як". №2. Скільки б вкладених петель не робив OP під час експериментів, вони ніколи не дістануться до нескінченності; незалежно від того, наскільки далеко ОП штовхає його, єдиний спосіб, який коли-небудь довести, чи існує межа, - це якщо і коли він насправді порушується.
Panzercrisis

10
"Оскільки все має межу", no: sin (x) не має межі для x -> oo. Також: якщо ваша передумова "все обмежено", чому ви взагалі запитуєте "чи ця річ обмежена"? Відповідь у ваших припущеннях, що робить аргумент абсолютно марним і тривіальним.
Бакуріу

5
@EricLippert Власне кажучи, ви ніколи не будете впевнені, що немає обмежень, незалежно від того, як довго ви продовжували цей шлях.
Кейсі

2
"x -> oo" змусив мене трохи поплакати :) Використовуйте "x → ∞"
Ману

Відповіді:


120

Я виходжу на ноги, публікуючи це, але я думаю, що відповідь така:

Між 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: Вираз занадто довгий або складний для компіляції

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


Оновлення

У відповідь на запитання в коментарях :

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

В обох випадках відповідь така: так, це можливо. При заповненні методу 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 циклів, як у

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);
}

також можна скомпілювати. Тут я також спробував знайти межу і створив більше цих циклів. Зокрема, я не був впевнений, чи змінні циклу в цьому випадку також враховують усіх "місцевих жителів", оскільки вони є своїми { block }. Але все-таки більше 65534 неможливо. Нарешті, я додав тест, що складається з 40000 петель візерунка

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

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


Отже, підсумовуючи: Межа ~ 550 справді обумовлена глибиною вкладеності петель. На це також вказувало повідомлення про помилку

помилка CS8078: Вираз занадто довгий або складний для компіляції

Документації CS1647 помилок , до жаль (але по зрозумілих причин ) не визначає «вимірювання» складності, але дає тільки прагматичний рада

У компіляторі, який обробляє ваш код, було переповнення стека. Щоб вирішити цю помилку, спростіть свій код.

Щоб ще раз наголосити на цьому: для forприватного випадку глибоко вкладених циклів все це досить академічно та гіпотетично . Але веб-пошук повідомлення про помилку CS1647 виявляє кілька випадків, коли ця помилка з'являлася для коду, який, швидше за все, не був навмисно складним, але створювався в реалістичних сценаріях.


2
@PatrickHofman Так, підказка "із налаштуваннями за замовчуванням ..." є важливою: це стосується наївного створення проекту за замовчуванням у VS2015. Налаштування проекту не відображали "очевидного" параметра для збільшення ліміту. Але, незважаючи на це, мені цікаво, чи немає подальших обмежень, встановлених CLI / CLR / чим завгодно. Специфікація ECMA-335 має розділ "III.1.7.4 Повинен надати maxstack" , але я занадто великий C # noob, щоб сказати, чи це насправді пов'язано, або навіть детально проаналізувати його значення ....
Marco13

10
Це не був перший раз, коли Microsoft обмежувала forцикли. Microsoft BASIC обмежував
Познач

3
@Davislor: Повідомлення про помилку стосується розміру стека в компіляторі , який також є програмою і рекурсивно обробляє вкладену циклічну конструкцію. Він (сильно) не прив'язаний до кількості локальних змінних у коді, що компілюється.
ruahh

2
Це лише я? Я вважаю цю відповідь надзвичайно веселою! Я також міг би подумати 42як відповідь.
cmroanirgo

11
Ймовірно, варто зазначити, що 500 вкладених циклів, припускаючи лише 2 ітерації на цикл і одну ітерацію на цикл, займе близько 10 ^ 33 гугольських років (10 ^ 133) для роботи на комп'ютері з частотою 4 ГГц. Для порівняння вік Всесвіту становить приблизно 1,37 х 10 ^ 10 років. Враховуючи те, що передбачається розпад нуклонів приблизно через 10 ^ 40 років, я можу прямо сказати, що поточне обладнання не будується так довго, і вам, можливо, доведеться перезавантажити комп'ютер тим часом.
Maciej Piechotka

40

Жорстких обмежень у специфікації мови C # або CLR немає. Ваш код буде ітераційним, а не рекурсивним, що може призвести до переповнення стека досить швидко.

Є декілька речей, які можна вважати пороговими значеннями, наприклад (звичайно) intлічильник, який ви використовуєте, який виділяє intпам’ять для кожного циклу (і до того, як ви виділите весь стек з ints ...). Зверніть увагу, що використання цього intє обов’язковим, і ви можете повторно використовувати ту саму змінну.

Як вказував Марко , поточний поріг більше у компіляторі, ніж у фактичній специфікації мови чи середовищі виконання. Після того, як це перекодується, ви можете отримати ще кілька ітерацій. Наприклад, якщо ви використовуєте Ideone , який за замовчуванням використовує старий компілятор, ви можете легко отримати понад 1200 forциклів.

Це стільки, що петлі - це показник поганого дизайну. Сподіваюся, це питання суто гіпотетичне.


Я погоджуюся з цим, але додаю, що ви, швидше за все, досягли обмеження за часом / апаратним забезпеченням, перш ніж досягти обмежень програмного забезпечення (наприклад, компіляція триває занадто довго, або для виконання вашого коду потрібно занадто багато часу / занадто багато ресурсів).
Marshall Tigerus

Файли коду з мільйона рядків складаються досить швидко, тому це не складе проблем. Крім того, оскільки код цілком прямий до механізму виконання, я б не чекав від нього занадто багато продуктивності.
Патрік Хофман

1
Давай, у самій мові немає обмежень. Те, що файлова система займає всього 20 МБ, а вихідний файл або виконуваний файл стає занадто великим, не є реальним обмеженням @ Marco13
Patrick Hofman

3
На відміну від вашої відповіді, кожен цикл насправді додає до використовуваного простору стека. (Це додавання нової змінної.) Тому стек закінчиться, коли у вас буде достатньо змінних. Це точно така ж проблема, як і з рекурсією (за винятком того, що кадр стека для методу займе більше місця в стеку, ніж просто один int). Якби це були цикли без змінної циклу, то це було б інакше.
Серві

3
Я вважаю, що CPython обмежує вкладеність мовних конструкцій приблизно в 50. Немає сенсу підтримувати навіть 1200 вкладених циклів ... вже 10 є явним знаком того, що щось не так з програмою.
Бакуріу

13

Існує обмеження для всіх C #, скомпільованих до MSIL. MSIL може підтримувати лише 65535 локальних змінних. Якщо ваші forцикли схожі на ті, що ви показали у прикладі, кожна з них потребує змінної.

Можливо, ваш компілятор міг виділити об'єкти з купи, щоб виступати в ролі сховища для локальних змінних, обходячи це обмеження. Однак я не впевнений, які дивні результати могли б вийти з цього. Можуть виникати проблеми, що виникають із роздумами, які роблять такий підхід незаконним.


6
Вам не потрібні ніякі змінні для циклу for.
Патрік Хофман,

1
@PatrickHofman Ви маєте рацію, я відредагував частину, яку забув включити
Cort Ammon

@PatrickHofman, якщо я не помиляюся, цикл for без змінних еквівалентний тому, while(true)який утворює нескінченний цикл. Якщо ми відредагували запитання до "Чи існує обмеження на кількість вкладених циклів у програмі, що завершує роботу?" тоді ми могли б використовувати фокус локальної змінної, щоб сформувати жорстку межу?
emory

2
@emory Ніщо не зупиняє вас від деяких дійсно абсурдних циклів, які повторно використовують змінні. Я б не називав їх значущими для циклів, але їх можна зробити.
Корт Аммон

1
@emory ти можеш завершити цикл за допомогою a breakабо мати умову циклу перевірити щось зовнішнє, наприклад( ; DateTime.now() < ... ; )
Grisha Levit

7

Від 800 до 900 для порожніх for(;;)петель.

Віддзеркалений підхід Marco13, крім випробуваних for(;;)петель:

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

Він працював для 800 вкладених for(;;), але дав ту саму помилку, з якою зіткнувся Marco13 при спробі 900 циклів.

Коли він компілюється, for(;;)здається, блокує потік, не збільшуючи центральний процесор; зовні, здається, діє як Thread.Sleep().


2
"він закінчується майже миттєво" - це дивно - я думав, що for(;;)це буде нескінченний цикл в C # ?! (Я не можу спробувати зараз, але якщо це виявиться помилковим, я
видалю

@ Marco13: Так, ти маєш рацію! Не впевнений, що відбувалося в моєму тестовому коді раніше, але for(;;)це блокує, не витрачаючи час процесора.
Nat

Цикл for (;;) - це нескінченний цикл, то навіщо додавати ще один цикл for (;;) або 800 з них, оскільки вони не будуть запущені. перший буде працювати вічно.
Фред Сміт

1
@FredSmith: Цикли for(;;)вкладені, тому всі вони починаються до самого вкладеного циклу, який нескінченний. Щоб перший for(;;)заблокував, це мав би бути for(;;){}. Щодо того, навіщо це робити, це був лише тест, щоб побачити, які обмеження має поточна реалізація .NET / C #; мабуть, він не може піти на 900 for(;;), хоча, як ви зауважуєте, він нічого не робить.
Nat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.