Gibt es eine Begrenzung für die Anzahl der verschachtelten 'for'-Schleifen?

79

Da alles begrenzt ist, habe ich mich gefragt, ob die Anzahl der verschachtelten forSchleifen begrenzt ist oder ob ich sie hinzufügen kann, solange ich Speicher habe. Kann der Visual Studio-Compiler ein solches Programm erstellen?

Natürlich forwären 64 oder mehr verschachtelte Schleifen nicht praktisch zum Debuggen, aber ist es machbar?

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....
      }
    }
  }
}
Fred Smith
quelle
78
Sie sind ein Computerprogrammierer: Beantworten Sie Ihre eigene Frage mit Computerprogrammierung. Wenn Sie ein Programm schreiben, das Programme mit verschachtelten Schleifen 1, 2, 4, 8, 16, 32, ... generiert, kompiliert und ausführt, werden Sie sehr schnell feststellen, ob es ein Limit gibt.
Eric Lippert
38
# 1. Das OP hat nicht angegeben, dass dies in irgendeiner Weise mit dem Produktionscode zusammenhängt, im Gegensatz zu nur einer hypothetischen "Was-wäre-wenn" -Frage. # 2. Unabhängig davon, wie viele verschachtelte Schleifen das OP beim Experimentieren erstellt, werden sie niemals unendlich werden. Egal wie weit das OP es schiebt, der einzige Weg, der jemals beweisen wird, ob es ein Limit gibt, ist, ob und wann es tatsächlich bricht.
Panzercrisis
10
"Da alles eine Grenze hat", nein: sin (x) hat keine Grenze für x -> oo. Außerdem: Wenn Ihre Prämisse "alles ist begrenzt" lautet, warum fragen Sie dann überhaupt "ist dieses Ding begrenzt"? Die Antwort liegt in Ihren Annahmen, was das Argument völlig nutzlos und trivial macht.
Bakuriu
5
@EricLippert Genau genommen wären Sie sich nie sicher, ob es keine Begrenzung gibt, egal wie lange Sie diesen Weg fortgesetzt haben.
Casey
2
"x -> oo" brachte mich ein wenig zum Weinen :) Benutze "x → ∞"
Manu

Antworten:

120

Ich gehe auf die Nerven, indem ich dies poste, aber ich denke, dass die Antwort lautet:

Zwischen 550 und 575

mit Standardeinstellungen in Visual Studio 2015

Ich habe ein kleines Programm erstellt, das verschachtelte forSchleifen generiert ...

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

Für 500 verschachtelte Schleifen kann das Programm weiterhin kompiliert werden. Mit 575 Schleifen rettet der Compiler:

Warnung AD0001 Analyzer 'Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer' hat eine Ausnahme vom Typ 'System.InsufficientExecutionStackException' mit der Meldung 'Unzureichender Stack ausgelöst, um die sichere Ausführung des Programms fortzusetzen. Dies kann passieren, wenn zu viele Funktionen auf dem Aufrufstapel oder auf dem Stapel zu viel Stapelspeicherplatz vorhanden sind. '

mit der zugrunde liegenden Compiler-Nachricht

Fehler CS8078: Ein Ausdruck ist zu lang oder zu komplex zum Kompilieren

Dies ist natürlich ein rein hypothetisches Ergebnis. Wenn die innerste Schleife mehr als a Console.WriteLineausführt, sind möglicherweise weniger verschachtelte Schleifen möglich, bevor die Stapelgröße überschritten wird. Dies ist möglicherweise auch keine rein technische Grenze, da möglicherweise versteckte Einstellungen vorhanden sind, um die maximale Stapelgröße für den in der Fehlermeldung genannten "Analyzer" oder (falls erforderlich) für die resultierende ausführbare Datei zu erhöhen. Dieser Teil der Antwort bleibt jedoch den Leuten überlassen, die C # genau kennen.


Aktualisieren

Als Antwort auf die Frage in den Kommentaren :

Es würde mich interessieren, wenn diese Antwort erweitert würde, um experimentell zu "beweisen", ob Sie 575 lokale Variablen auf den Stapel legen können, wenn sie nicht in for-Schleifen verwendet werden, und / oder ob Sie 575 nicht verschachtelte for-Schleifen einfügen können eine einzige Funktion

In beiden Fällen lautet die Antwort: Ja, das ist möglich. Beim Füllen der Methode mit 575 automatisch generierten Anweisungen

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

es kann noch kompiliert werden. Alles andere hätte mich überrascht. Die für die intVariablen erforderliche Stapelgröße beträgt nur 2,3 KB. Aber ich war neugierig und um weitere Grenzen zu testen, habe ich diese Zahl erhöht. Schließlich wurde es nicht kompiliert, was den Fehler verursachte

Fehler CS0204: Nur 65534 Einheimische, einschließlich der vom Compiler generierten, sind zulässig

Das ist ein interessanter Punkt, wurde aber bereits an anderer Stelle beobachtet: Maximale Anzahl von Variablen in der Methode

In ähnlicher Weise 575 nicht verschachtelte for Schleifen, wie in

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

kann auch kompiliert werden. Hier habe ich auch versucht, das Limit zu finden, und mehr dieser Schleifen erstellt. Insbesondere war ich mir nicht sicher, ob die Schleifenvariablen in diesem Fall auch als "Einheimische" gelten, weil sie in ihren eigenen sind { block }. Mehr als 65534 ist jedoch nicht möglich. Schließlich habe ich einen Test hinzugefügt, der aus 40000 Schleifen des Musters besteht

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

das enthielt eine zusätzliche Variable in der Schleife, aber diese scheinen auch als "Einheimische" zu zählen, und es war nicht möglich, diese zu kompilieren.


Zusammenfassend: Die Grenze von ~ 550 wird tatsächlich durch die Verschachtelungstiefe der Schleifen verursacht. Dies wurde auch durch die Fehlermeldung angezeigt

Fehler CS8078: Ein Ausdruck ist zu lang oder zu komplex zum Kompilieren

Die Dokumentation des Fehlers CS1647 spezifiziert leider (aber verständlicherweise) keine "Messung" der Komplexität, sondern gibt nur den pragmatischen Rat

Es gab einen Stapelüberlauf im Compiler, der Ihren Code verarbeitete. Vereinfachen Sie Ihren Code, um diesen Fehler zu beheben.

Um dies noch einmal zu betonen: Für den speziellen Fall tief verschachtelter forSchleifen ist dies alles eher akademisch und hypothetisch . Die Websuche nach der Fehlermeldung von CS1647 zeigt jedoch mehrere Fälle, in denen dieser Fehler für Code auftrat, der höchstwahrscheinlich nicht absichtlich komplex war, sondern in realistischen Szenarien erstellt wurde.

Marco13
quelle
2
@PatrickHofman Ja, der Hinweis "mit Standardeinstellungen ..." ist wichtig: Dies bezieht sich auf das naive Erstellen eines Standardprojekts in VS2015. Die Projekteinstellungen zeigten keinen "offensichtlichen" Parameter, um das Limit zu erhöhen. Unabhängig davon frage ich mich, ob die CLI / CLR / was auch immer keine weiteren Beschränkungen auferlegt. Die ECMA-335-Spezifikation enthält einen Abschnitt "III.1.7.4 Muss Maxstack bereitstellen" , aber ich bin viel zu sehr ein C # -Nobel, um zu sagen, ob dies wirklich verwandt ist, oder um seine Bedeutung im Detail zu analysieren.
Marco13
10
Es wäre nicht das erste Mal, dass Microsoft ein Limit für forSchleifen hat. Microsoft BASIC hatte ein Verschachtelungslimit von 8.
Mark
3
@Davislor: Die Fehlermeldung bezieht sich auf die Stapelgröße im Compiler , der ebenfalls ein Programm ist und das Konstrukt mit verschachtelten Schleifen rekursiv verarbeitet. Es ist nicht (stark) an die Anzahl der lokalen Variablen im zu kompilierenden Code gebunden.
Ruakh
2
Ist es nur ich Ich finde diese Antwort äußerst witzig! Ich hätte auch 42als Antwort gedacht .
cmroanirgo
11
Es ist wahrscheinlich erwähnenswert, dass 500 verschachtelte Schleifen, die nur 2 Iterationen pro Schleife und eine Iteration pro Zyklus annehmen, ungefähr 10 ^ 33 Googol-Jahre (10 ^ 133) benötigen würden, um auf einem 4-GHz-Computer ausgeführt zu werden. Der Vergleich des Alters des Universums beträgt ungefähr 1,37 x 10 ^ 10 Jahre. Angesichts der Tatsache, dass vorausgesagt wird, dass Nukleonen in etwa 10 bis 40 Jahren zerfallen, kann ich genau sagen, dass die aktuelle Hardware nicht so lange hält und Sie den Computer möglicherweise in der Zwischenzeit neu starten müssen.
Maciej Piechotka
40

In der C # -Sprachenspezifikation oder der CLR gibt es keine feste Grenze. Ihr Code wäre eher iterativ als rekursiv, was sehr schnell zu einem Stapelüberlauf führen könnte.

Es gibt einige Dinge, die als Schwellenwert gelten könnten, z. B. den (allgemein) verwendeten intZähler, der intjeder Schleife einen In-Speicher zuweist (und bevor Sie Ihren gesamten Stapel mit Ints zugewiesen haben ...). Beachten Sie, dass die Verwendung interforderlich ist und Sie dieselbe Variable möglicherweise wiederverwenden.

Wie von Marco hervorgehoben , liegt der aktuelle Schwellenwert mehr im Compiler als in der tatsächlichen Sprachspezifikation oder Laufzeit. Sobald dies neu codiert ist, können Sie weitere Iterationen durchführen. Wenn Sie beispielsweise Ideone verwenden , das standardmäßig den alten Compiler verwendet, können Sie forproblemlos über 1200 Schleifen erhalten.

So viel zu Loops ist jedoch ein Indikator für schlechtes Design. Ich hoffe, diese Frage ist rein hypothetisch.

Patrick Hofman
quelle
Ich bin damit einverstanden, würde aber hinzufügen, dass Sie wahrscheinlich ein Zeit- / Hardware-Limit erreichen würden, bevor Sie eine Software-Einschränkung erreichen würden (z. B. dauert das Kompilieren zu lange oder die Ausführung Ihres Codes dauert zu lange / zu viele Ressourcen).
Marshall Tigerus
Codedateien mit einer Million Zeilen werden recht schnell kompiliert, sodass dies kein Problem darstellt. Da der Code für die Ausführungs-Engine recht einfach ist, würde ich in Bezug auf die Leistung nicht zu viel davon erwarten.
Patrick Hofman
1
Komm schon, der Sprache selbst sind keine Grenzen gesetzt. Dass das Dateisystem nur 20 MB groß ist und die Quelldatei oder ausführbare Datei zu groß wird, ist keine wirkliche Einschränkung @ Marco13
Patrick Hofman
3
Im Gegensatz zu Ihrer Antwort erhöht jede Schleife tatsächlich den verwendeten Stapelspeicher. (Es wird eine neue Variable hinzugefügt.) Daher wird der Stapel leer, wenn Sie über genügend Variablen verfügen. Es ist genau das gleiche Problem wie bei der Rekursion (außer dass der Stapelrahmen für eine Methode mehr Stapelspeicherplatz als nur einen benötigt int). Wenn sie Schleifen ohne Schleifenvariable wären, wäre das anders.
Servy
3
Ich glaube, CPython begrenzt die Verschachtelung von Sprachkonstrukten auf etwa 50. Es macht keinen Sinn, auch 1200 verschachtelte Schleifen zu unterstützen ... bereits 10 sind ein klares Zeichen dafür, dass etwas mit dem Programm nicht stimmt.
Bakuriu
13

Es gibt ein Limit für alle C #, die bis zu MSIL kompiliert wurden. MSIL kann nur 65535 lokale Variablen unterstützen. Wenn Ihre forSchleifen denen entsprechen, die Sie im Beispiel gezeigt haben, benötigt jede eine Variable.

Es ist möglich, dass Ihr Compiler Objekte auf dem Heap als Speicher für lokale Variablen zuweist und diese Grenze umgeht. Ich bin mir jedoch nicht sicher, welche seltsamen Ergebnisse daraus resultieren würden. Bei der Reflexion können Probleme auftreten, die einen solchen Ansatz illegal machen.

Cort Ammon
quelle
6
Sie benötigen keine Variablen für eine for-Schleife.
Patrick Hofman
1
@PatrickHofman Sie haben Recht, ich habe in dem Teil bearbeitet, den ich vergessen habe einzuschließen
Cort Ammon
@PatrickHofman, wenn ich mich nicht irre, entspricht eine for-Schleife ohne Variablen while(true)einer Endlosschleife. Wenn wir die Frage auf "Gibt es eine Begrenzung für die Anzahl der verschachtelten for-Schleifen in einem Abschlussprogramm?" Könnten wir dann den lokalen Variablentrick verwenden, um ein hartes Limit zu bilden?
Emory
2
@emory Nichts hält Sie von einigen wirklich absurden Schleifen ab, die Variablen wiederverwenden. Ich würde sie nicht als sinnvoll für Schleifen bezeichnen, aber sie könnten gemacht werden.
Cort Ammon
1
@emory Sie können eine Schleife mit einem beenden breakoder die Schleifenbedingung etwas Externes überprüfen lassen, wie( ; DateTime.now() < ... ; )
Grisha Levit
7

Zwischen 800 und 900 für leere for(;;)Schleifen.

Der Ansatz von Marco13 wurde gespiegelt, mit Ausnahme der versuchten for(;;)Schleifen:

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

Es funktionierte für 800 verschachtelte for(;;), aber es gab den gleichen Fehler, den Marco13 beim Versuch von 900 Schleifen hatte.

Wenn es kompiliert wird, for(;;)scheint der Thread zu blockieren, ohne die CPU zu maximieren. oberflächlich scheint es sich wie ein zu verhalten Thread.Sleep().

Nat
quelle
2
"es endet ziemlich sofort" - das ist seltsam - ich dachte, das for(;;)wäre eine Endlosschleife in C #?! (Ich kann es
momentan
@ Marco13: Ja, du hast recht! Ich bin mir nicht sicher, was früher in meinem Testcode passiert ist, aber das for(;;)blockiert, ohne CPU-Zeit zu verbrauchen.
Nat
Eine for (;;) - Schleife ist eine Endlosschleife. Warum also eine weitere for (;;) - Schleife oder 800 davon hinzufügen, da sie nicht ausgeführt werden? Der erste wird für immer laufen.
Fred Smith
1
@FredSmith: Die for(;;)Schleifen sind verschachtelt, daher beginnen sie alle mit der am meisten verschachtelten Schleife, die unendlich ist. Für den ersten for(;;)Block musste es sein for(;;){}. Warum dies getan wurde, war nur ein Test, um festzustellen, welche Einschränkungen die aktuelle .NET / C # -Implementierung aufweist. anscheinend kann es nicht bis 900 gehen for(;;), obwohl es, wie Sie bemerken, nichts tut.
Nat