嵌套“ 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。OP并未表示这与生产代码有任何关系,而与假设的“假设”类型相反。#2。无论OP在实验中产生多少个嵌套循环,它们都永远不会达到无限。不管OP推到多远,唯一能证明是否存在极限的方法是,是否以及何时真正打破极限。
Panzercrisis

10
“因为一切都有极限”,否:sin(x)没有x-> oo的极限。另外:如果您的前提是“一切都受限”,那么您为什么还要问“这件事是否受限”?答案是在您的假设下进行的,这使该论点完全无用且琐碎。
巴库里

5
@EricLippert严格来说,无论您继续这样做多久,都永远不会确定是否没有限制。
Casey

2
“ x-> oo”让我哭了一点:)使用“ x→∞”
Manu

Answers:


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,则在超出堆栈大小之前,可能会出现较少的嵌套循环。同样,在可能存在隐藏设置的情况下,这可能不是严格的技术限制,以增加错误消息中提及的“分析器”或(如果需要)生成的可执行文件的最大堆栈大小。但是,答案的这一部分留给了深入了解C#的人。


更新资料

针对评论中问题

我很想看到这个答案可以扩展为通过实验“证明”是否可以将575个局部变量(如果在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 KB。但是我很好奇,为了测试更多的限制,我增加了这个数字。最终,它并不能编译,导致错误

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

也可以编译。在这里,我还试图找到极限,并创建了更多的循环。特别是,我不确定这种情况下的循环变量是否也计入als“ locals”,因为它们是独立存在的{ 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#小白说这是否真的是相关的,或者甚至详细分析它的意义....
Marco13

10
微软不是第一次对for循环进行限制。微软BASIC过的8嵌套限制
马克

3
@Davislor:错误消息是指编译器中的堆栈大小,该编译器也是一个程序,并且正在递归处理嵌套循环构造。它与编译代码中的局部变量的数量没有(强烈)关联。
ruakh

2
只有我吗?我发现这个答案非常好笑!我也会以为42答案。
cmroanirgo

11
值得注意的是,假设500个嵌套循环每个循环仅进行2次迭代,而每个循环进行一次迭代,则在4 GHz计算机上运行大约需要10 ^ 33 googol年(10 ^ 133)。比较宇宙的年龄是大约1.37 x 10 ^ 10年。假设核子将在大约10 ^ 40年内衰减,我可以直言不讳地说当前的硬件不能持续使用那么长时间,并且您可能需要同时重启计算机。
Maciej Piechotka '16

40

C#语言规范或CLR中没有硬性限制。您的代码将是迭代的,而不是递归的,这可能会很快导致堆栈溢出。

有一些事情可以算作一个阈值,例如int您将使用的(通常)计数器,它将int为每个循环分配一个内存(在为您的整个堆栈分配ints之前)。请注意,必须使用,int并且您可以重复使用相同的变量。

正如Marco指出的,当前的阈值在编译器中要比实际语言规范或运行时中的阈值更多。重新编码后,您可能需要进行更多迭代。例如如果使用Ideone(默认情况下使用旧的编译器),则可以for轻松获得1200个以上的循环。

这么多的for循环是不良设计的指标。我希望这个问题纯粹是假设的。


我同意这一点,但是还要补充一点,您可能会在达到软件限制之前达到时间/硬件限制(例如,编译时间太长,或者代码执行时间太长/资源太多)。
马歇尔·虎格斯

一百万行的代码文件编译速度非常快,所以这不是问题。另外,由于代码很直接地传递给执行引擎,因此从性能角度来看,我不会期望太多。
Patrick Hofman

1
来吧,语言本身没有限制。文件系统只有20MB并且源文件或可执行文件变得太大并不是真正的限制@ Marco13
Patrick Hofman

3
与您的答案相反,实际上每个循环确实会增加所使用的堆栈空间。(它添加了一个新变量。)因此,当您有足够的变量时,堆栈将用完。这与递归完全相同(除了方法的堆栈框架要占用一个以上的堆栈空间之外int)。如果它们是没有循环变量的循环,那将是不同的。
Servy

3
我相信CPython将语言结构的嵌套限制为大约50种。即使支持1200个嵌套循环也没有意义……已经有10个明确的迹象表明程序有问题。
巴库里

13

所有编译为MSIL的C#都有一个限制。MSIL仅支持65535个局部变量。如果for循环类似于示例中所示,则每个循环都需要一个变量。

您的编译器可能会在堆上分配对象以充当局部变量的存储,从而规避了此限制。但是,我不确定会从中得出什么样的结果。反射可能会导致使这种方法非法的问题。


6
for循环不需要任何变量。
Patrick Hofman

1
@PatrickHofman是的,我编辑了我忘了包含的部分
Cort Ammon

@PatrickHofman,如果我没有记错没有变量的for循环,它等效于while(true)一个无限循环。如果我们将问题编辑为“终止程序中嵌套的for循环数是否有限制?” 那么我们可以使用局部变量技巧来形成硬限制吗?
emory

2
@emory没有什么可以阻止您从一些真正荒谬的循环中重用变量。对于循环,我不会说它们有意义,但是可以做到。
Cort Ammon

1
@emory您可以使用终止循环break或让循环条件检查外部事物,例如( ; DateTime.now() < ... ; )
Grisha Levit

7

for(;;)循环在800和900之间。

反映了Marco13的方法,但尝试过的for(;;)循环除外:

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

它适用于800个嵌套for(;;),但给出的错误与Marco13尝试900个循环时遇到的错误相同。

当它确实编译时,for(;;)似乎在不使CPU耗尽的情况下阻塞了线程。从表面上看,它看起来像一个Thread.Sleep()


2
“它几乎立即完成运行” -奇怪-我认为这for(;;)将是C#中的无限循环?(我现在无法尝试,但是如果发现错误,我将删除此评论)
Marco13,2013年

@ Marco13:是的,你是对的!不知道之前在我的测试代码中发生了什么,但是在for(;;)没有消耗CPU时间的情况下阻塞了代码。
2013年

for(;;)循环是一个无限循环,因此为什么要添加另一个for(;;)循环或其中的800个循环,因为它们将无法运行。第一个将永远运行。
弗雷德·史密斯

1
@FredSmith:for(;;)循环是嵌套的,因此它们都从最嵌套的循环开始,即无限循环。对于第一个for(;;)阻止的对象,必须为for(;;){}。至于为什么这样做,这只是一个测试,以了解当前.NET / C#实现具有哪些局限性。显然,它不能达到900for(;;)年代,尽管正如您所指出的,它什么也没做。
2017年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.