80

Since everything has a limit, I was wondering if there is a limit to the number of nested for loops or as long as I have memory, I can add them, can the Visual Studio compiler create such a program?

Of course a 64 or more nested for loops would be not handy to debug, but is it doable?

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....
      }
    }
  }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Fred Smith
  • 2,047
  • 3
  • 25
  • 36
  • I agree with others, but when you already have this situation, then you should put inner loops in outer methods. At least it would be readable – Sasha Dec 27 '16 at 14:48
  • 79
    You're a computer programmer: answer your own question with computer programming. Write a program that generates, compiles and runs programs with 1, 2, 4, 8, 16, 32, ... nested loops, and you will very quickly learn if there is a limit. – Eric Lippert Dec 27 '16 at 15:34
  • 38
    #1. The OP has not indicated that this is in any way related to production code, as opposed to just a hypothetical, "what-if" type of question. #2. No matter how many nested loops the OP makes while experimenting, they will never get to infinity; no matter how far the OP pushes it, the only way that will ever prove whether there's a limit is if and when it actually breaks. – Panzercrisis Dec 27 '16 at 16:31
  • 10
    "Since everything has a limit", no: sin(x) does not have a limit for x -> oo. Also: if your premise is "everything is bounded", why are you even asking "is this thing bounded"? The answer is in your assumptions which makes the argument completely useless and trivial. – Bakuriu Dec 27 '16 at 21:03
  • 5
    @EricLippert Strictly speaking you'd never be sure if there were no limit no matter how long you continued on this way. – Casey Dec 28 '16 at 18:25
  • 3
    "x -> oo" made me cry a little :) Use "x→∞" – Manu Dec 29 '16 at 13:10
  • @EricLippert usually these kind of test is not a good idea. Just because something compiles or run as expected, it doesn't mean it's universally true. For instance, this could be limited by the architecture limits, meaning that the limit you found could not work for me. You'd need to check the language standard to see if there's any lower/upper bound -- from my experience with other languages, there probably is a lower bound, which it probably much more than you'd ever need, and no upper bound; but this is an "educated guess". – giusti Jan 02 '17 at 01:44
  • I had to try this with clang. It croaks on more than 256 nested levels. With -fbracket-depth you can set a higher limit, but clang crashed hard on me when I tried 65536 levels. sublime_text loads the .c fine, but the syntax highlighting fails on line 65597 after closing 256 levels. – Emanuel Landeholm Jan 11 '17 at 08:53

4 Answers4

121

I'm going out on a limb by posting this, but I think that the answer is:

Between 550 and 575

with default settings in Visual Studio 2015

I created a small program that generates nested for loops...

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

For 500 nested loops, the program can still be compiled. With 575 loops, the compiler bails out:

Warning AD0001 Analyzer 'Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer' threw an exception of type 'System.InsufficientExecutionStackException' with message 'Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.'.

with the underlying compiler message

error CS8078: An expression is too long or complex to compile

Of course, this is a purely hypothetical result. If the innermost loop does more than a Console.WriteLine, then fewer nested loops may be possible before the stack size is exceeded. Also, this may not be a strictly technical limit, in the sense that there may be hidden settings to increase the maximum stack size for the "Analyzer" that is mentioned in the error message, or (if necessary) for the resulting executable. This part of the answer, however, is left to the people who know C# in depth.


Update

In response to the question in the comments :

I would be interested to see this answer expanded to "prove" experimentally whether you can put 575 local variables on the stack if they're not used in for-loops, and/or whether you can put 575 non-nested for-loops in a single function

For both cases, the answer is: Yes, it is possible. When filling the method with 575 auto-generated statements

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

it can still be compiled. Everything else would have surprised me. The stack size that is required for the int variables is just 2.3 KB. But I was curious, and in order to test for further limits, I increased this number. Eventually, it did not compile, causing the error

error CS0204: Only 65534 locals, including those generated by the compiler, are allowed

which is an interesting point, but has already been observed elsewhere: Maximum number of variables in method

Similarly, 575 non-nested for-loops, as 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);
}

can be compiled as well. Here, I also tried to find the limit, and created more of these loops. Particularly, I wasn't sure whether the loop variables in this case also count als "locals", because they are in their own { block }. But still, more than 65534 is not possible. Finally, I added a test consisting of 40000 loops of the pattern

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

that contained an additional variable in the loop, but these seem to count as "locals" as well, and it was not possible to compile this.


So to summarize: The limit of ~550 is indeed caused by the nesting depth of the loops. This also was indicated by the error message

error CS8078: An expression is too long or complex to compile

The documentation of error CS1647 unfortunately (but understandably) does not specify a "measurement" of complexity, but only gives the pragmatic advice

There was a stack overflow in the compiler processing your code. To resolve this error, simplify your code.

To emphasize this again: For the particular case of deeply nested for-loops, all this is rather academic and hypothetical. But websearching for the error message of CS1647 reveals several cases where this error appeared for code that was most likely not intentionally complex, but created in realistic scenarios.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • So the fact is that the current Roslyn compiler stops there, okay. Nice to know. An adjusted compiler might yield totally different results. +1 nonetheless ;) – Patrick Hofman Dec 27 '16 at 16:31
  • See my updated answer. The number is a lot higher when using the old C# compiler. – Patrick Hofman Dec 27 '16 at 16:35
  • 2
    @PatrickHofman Yes, the hint *"with default settings..."* is important: This refers naïvely creating a default project in VS2015. The project settings did not show an "obvious" parameter to increase the limit. But regardless of that, I wonder whether there aren't any further limits imposed by the CLI/CLR/whatever. The [ECMA-335 spec](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf) has a section *"III.1.7.4 Must provide maxstack"*, but I'm far too much of a C# noob to say whether this is really related, or to even analyze its meaning in detail.... – Marco13 Dec 27 '16 at 16:45
  • Of course, and I didn't expect you to go in so much detail. – Patrick Hofman Dec 27 '16 at 16:47
  • @PatrickHofman As you already said: For the `for` loops, all this is hypothetical. But websearching for the error messages that I mentioned brings many results. So this may at least have *some* practical relevance - at the very least, regarding the question of how to increase the limit *if* the error appears for non-hypothetical code! This particular question could be tagged `language-lawayer` (or `CLI-lawyer`, if this tag existed), but hopefully nobody will seriously nest more than a "few" loops anyhow... – Marco13 Dec 27 '16 at 16:51
  • That limit appears to be a function of stack size, but what if you didn’t declare a new counter inside each loop, so that the loops needed no additional stack size? The original question didn’t specify as a requirement that you use counters. – Davislor Dec 27 '16 at 17:31
  • @Davislor: Unless something weird is going on with debug info, 575 "int" variables should fit in a stack frame just fine. I would be interested to see this answer expanded to "prove" experimentally whether you can put 575 local variables on the stack if they're *not* used in for-loops, and/or whether you can put 575 *non-nested* for-loops in a single function. – Quuxplusone Dec 27 '16 at 17:46
  • @Quuxplusone I added an **Update** for this. – Marco13 Dec 27 '16 at 18:34
  • 10
    Wouldn't be the first time Microsoft had a limit on `for` loops. Microsoft BASIC had a nesting limit of 8. – Mark Dec 27 '16 at 22:23
  • @Mark Interesting fun fact. But in this case, it's not a "real" limit. First of all, its's not part of the language/spec. Secondly, 550 is basically impossible to appear in practice (whereas I think I've seen ~8 nested loops somewhere). And finally: The fact that the compiler bails out is not "inherently" connected to the nesting depth, but more generally, to the complexity of the expression. I could imagine that similar, artificial complex calls (like 600 nested `foo(bar(...foo(bar(1.0)) ...))` calls, or overly complex `if`-statements) may cause the same error. – Marco13 Dec 27 '16 at 23:16
  • 3
    @Davislor: The error message refers to the stack size in the *compiler*, which is also a program, and which is recursively processing the nested-looped construct. It's not (strongly) tied to the number of local variables in the code under compilation. – ruakh Dec 28 '16 at 00:49
  • "An expression is too long or complex to compile" ...I find myself wondering what happens if you minimize the whitespace (remove newlines and spaces). ;) That would tell us something about whether or not it's the *length* or the *depth* of the statement. – jpmc26 Dec 28 '16 at 02:11
  • 2
    Is it just me? I find this answer utterly hilarious! I would also have thought `42` as the answer. – cmroanirgo Dec 28 '16 at 08:58
  • 11
    It's probably worth noting that 500 nested loops assuming only 2 iterations per loop and one iteration per cycle would take about 10^33 googol years (10^133) to run on 4 GHz computer. To compare age of universe is is about 1.37 x 10^10 years. Given that nucleons are predicted to decay in about 10^40 years I can squarely say that current hardware is not build to last that long and you may need to restart computer in meantime. – Maciej Piechotka Dec 28 '16 at 10:05
  • Thanks for going back and answering my question! – Davislor Dec 28 '16 at 10:06
  • @Marco13, regarding nested function calls, I see no issue for a compiler to optimize this nicely to `val = 1.0; for (int i=0; i – sleblanc Dec 28 '16 at 21:48
  • @sleblanc There are several possible configurations that one could explore here. I covered 3 in the update, but of course, one could go arbitrarily far with such "black box tests" to figure out the scenarios that cause the CS8078 error. I won't be able to do many further tests in the next few days, although particularly the nested function call case could be interesting. But I wonder whether this should not be analyzed more systematically, maybe as an answer to a broader question, like "What *exactly* causes the CS8078 error?" - What do you think? – Marco13 Dec 29 '16 at 01:37
  • @sleblanc: The nested function call is not about the generated code, you'll have problems even if the compiler optimise it to iterative form because the nested function calls is the compiler's parser trying to parse the for loop. You need the compiler's compiler to optimise the parser code in the compiler to iterative form. Either that or you need someone to modify the compiler's source to implement a recursive descent parser using a loop instead of recursion. – slebetman Dec 29 '16 at 08:42
40

There is no hard limit in the C# language specification or the CLR. Your code would be iterative, rather than recursive, which could lead to a stack overflow quite fast.

There are a few things that could count as a threshold, for example the (generally) int counter you would use, which would allocate an int in memory for each loop (and before you have allocated your entire stack with ints...). Note that the use of that int is required and you may reuse the same variable.

As pointed out by Marco, the current threshold is more in the compiler than in the actual language specification or runtime. Once that is recoded, you might up with quite some more iterations. If you for example use Ideone, which by default uses the old compiler, you can get over 1200 for loops easily.

That much for loops are an indicator of bad design though. I hope this question is purely hypothetical.

Community
  • 1
  • 1
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • I agree with this, but would add that you'd likely reach a time/hardware limit before you'd reach a software restriction (e.g. it takes too long to compile, or your code takes too long/too many resources to execute). – Marshall Tigerus Dec 27 '16 at 14:34
  • Code files of a million lines compile quite fast, so that wouldn't be a problem. Also, since the code is quite straight forward to the execution engine, I wouldn't expect too much from it performance-wise. – Patrick Hofman Dec 27 '16 at 14:39
  • a lot of code in a single method can hurt performance of the method. but if you divide logics it can perform faster (better). – M.kazem Akhgary Dec 27 '16 at 14:42
  • I'm not familiar with C#, but can *definitely* say that this answer is plainly wrong. The size of the file will eventually exceed the representable file size for 64bit systems. Yes, this is 16 exabytes. Most likely, the limit is lower. The answer is wrong nevertheless. – Marco13 Dec 27 '16 at 15:29
  • 1
    Come on, there is no limit in the language itself. That the file system is just 20MB and the source file or executable is getting too big is not a real limitation @Marco13 – Patrick Hofman Dec 27 '16 at 15:30
  • 3
    Contrary to your answer, each loop does in fact add to the stack space used. (It's adding a new variable.) Therefore the stack will run out when you have enough variables. It's exactly the same issue as with recursion (except that the stack frame for a method is going to take more stack space than just one `int`). If they were loops with no loop variable, then that would be different. – Servy Dec 27 '16 at 16:19
  • Sure, but I think that there are further relevant limits here. I looked at the spec, but am not sure where to start. I'm usually at home in the Java world, and, for example, there are clear specifications for the JVM about the maximum byte code size of a method, which will implicitly govern the maximum nesting depth of such a loop cascade. I think that similar limits will exist for the CLI. (But I'm **not** the downvoter, by the way) – Marco13 Dec 27 '16 at 16:19
  • @Servy That can be true indeed, but still you don't need the `int` counter, which may lead to more available space on the stack. – Patrick Hofman Dec 27 '16 at 16:21
  • @Servy reworded a few sentences to make it more correct hopefully. – Patrick Hofman Dec 27 '16 at 16:24
  • 3
    I believe CPython limits nesting of language constructs at about 50. There is no point in supporting even 1200 nested loops... already 10 are a clear sign that something wrong with the program. – Bakuriu Dec 27 '16 at 21:05
13

There is a limit for all C# compiled down to MSIL. MSIL can only support 65535 local variables. If your for loops are like the ones you showed in the example, each one requires a variable.

It's possible that your compiler could allocate objects on the heap to act as storage for local variables, circumventing this limit. However, I'm not sure what sorts of odd results would come from that. There may be issues that arise with reflection which make such an approach illegal.

Cort Ammon
  • 10,221
  • 31
  • 45
  • 6
    You don't need any variables for a for loop. – Patrick Hofman Dec 27 '16 at 21:54
  • 1
    @PatrickHofman You're right, I edited in the part that I forgot to include – Cort Ammon Dec 28 '16 at 00:59
  • @PatrickHofman if I am not mistaken a for loop without variables is equivalent to `while(true)` which would form an infinite loop. If we edited the question to "Is there a limit to the number of nested for loops in a terminating program?" then could we use the local variable trick to form a hard limit? – emory Dec 29 '16 at 02:29
  • 2
    @emory Nothing stops you from some really absurd loops which reuse variables. I wouldn't call them meaningful for loops, but they could be done. – Cort Ammon Dec 29 '16 at 03:08
  • 1
    @emory you can terminate a loop with a `break` or have the loop condition check something external, like `( ; DateTime.now() < ... ; )` – Grisha Levit Jan 21 '17 at 18:19
7

Between 800 and 900 for empty for(;;) loops.

Mirrored Marco13's approach, except tried for(;;) loops:

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

It worked for 800 nested for(;;), but it gave the same error that Marco13 encountered when trying 900 loops.

When it does compile, the for(;;) appears to block the thread without maxing out the CPU; superficially, it seems to act like a Thread.Sleep().

Nat
  • 1,085
  • 2
  • 18
  • 35
  • 2
    *"it finishes running pretty much instantly"* - that's strange - I thought that `for(;;)` would be an infinite loop in C#?! (I can't try it out right now, but if it turns out to be wrong, I'll delete this comment) – Marco13 Dec 29 '16 at 22:04
  • @Marco13: Yup, you're right! Not sure what was happening in my test code earlier, but the `for(;;)` is blocking without consuming CPU time. – Nat Dec 30 '16 at 05:44
  • A for(;;) loop is an infinite loop so why add another for(;;) loop or 800 of them because they won't be run. the first one will run forever. – Fred Smith Jan 09 '17 at 12:19
  • 1
    @FredSmith: The `for(;;)` loops are nested, so they all do start down to the most nested loop, which is infinite. For the first `for(;;)` to block, it'd have to be `for(;;){}`. As for why do this, it was just a test to see what limitations the current .NET/C# implementation has; apparently it can't go to 900 `for(;;)`'s, even though, as you note, it does nothing. – Nat Jan 09 '17 at 16:05