嵌套的“for”循环的数量是不是有限制?
Posted
技术标签:
【中文标题】嵌套的“for”循环的数量是不是有限制?【英文标题】:Is there a limit to the number of nested 'for' loops?嵌套的“for”循环的数量是否有限制? 【发布时间】:2017-05-11 20:23:25 【问题描述】:既然一切都有限制,我想知道嵌套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、2、4、8、16、32,... 嵌套循环的程序,您将很快了解是否有限制。 #1。 OP 没有表明这与生产代码有任何关系,而不仅仅是一个假设的“假设”类型的问题。 #2。不管 OP 在实验时做了多少个嵌套循环,它们永远不会达到无穷大;无论 OP 将其推多远,唯一能证明是否存在限制的方法就是它是否以及何时真正突破。 “既然一切都有极限”,不:sin(x) 对 x 没有极限 -> oo。另外:如果你的前提是“一切都是有界的”,你为什么还要问“这个东西有界”?答案就在你的假设中,这使得论证完全无用和微不足道。 @EricLippert 严格来说,无论你继续这条路多久,你都永远无法确定是否没有限制。 "x -> oo" 让我哭了 :) 使用 "x→∞" 【参考方案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
,则在超出堆栈大小之前可能会出现更少的嵌套循环。此外,这可能不是严格的技术限制,因为可能存在隐藏设置以增加错误消息中提到的“分析器”或(如有必要)生成的可执行文件的最大堆栈大小。然而,这部分答案留给深入了解 C# 的人。
更新
回复question in the comments:
我很想看到这个答案扩展为通过实验“证明”如果它们 不 在 for 循环中使用,你是否可以将 575 个局部变量放在堆栈上,和/或你是否可以将 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 个本地变量,包括编译器生成的本地变量
这是一个有趣的点,但已经在其他地方观察到:Maximum number of variables in method
同样,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);
也可以编译。在这里,我也试图找到极限,并创建了更多这样的循环。特别是,我不确定这种情况下的循环变量是否也算作“本地人”,因为它们在自己的 block
中。但是,超过 65534 仍然是不可能的。最后,我添加了一个包含 40000 个循环模式的测试
for (int i39999 = 0; i39999 < 10; i39999++)
int j = 0;
Console.WriteLine(j + i39999);
在循环中包含一个附加变量,但这些似乎也算作“本地变量”,因此无法编译。
总结一下:~550 的限制确实是由循环的嵌套深度造成的。错误消息也表明了这一点
错误 CS8078:表达式太长或太复杂而无法编译
不幸的是(但可以理解)documentation of error CS1647 没有指定复杂性的“度量”,而只是给出了务实的建议
在处理您的代码的编译器中出现堆栈溢出。要解决此错误,请简化您的代码。
再次强调这一点:对于深度嵌套的for
-loops 的特殊情况,所有这些都是相当学术和假设的。但是,通过网络搜索 CS1647 的错误消息,可以发现在几个情况下,出现此错误的代码很可能不是故意复杂的,而是在现实场景中创建的。
【讨论】:
@PatrickHofman 是的,提示 "with default settings..." 很重要:这是指在 VS2015 中天真地创建默认项目。项目设置没有显示“明显”的参数来增加限制。但不管怎样,我想知道 CLI/CLR/其他是否没有任何进一步的限制。 ECMA-335 spec 有一个部分 “III.1.7.4 必须提供 maxstack”,但我是一个 C# 菜鸟,无法说出这是否真的相关,甚至无法分析其含义详细.... 这不是微软第一次限制for
循环。 Microsoft BASIC 的嵌套限制为 8。
@Davislor:错误信息是指编译器中的堆栈大小,它也是一个程序,正在递归处理嵌套循环结构。它与正在编译的代码中的局部变量数量无关(强烈)。
只有我一个人吗?我觉得这个答案非常搞笑!我也会认为42
作为答案。
可能值得注意的是,500 个嵌套循环假设每个循环只有 2 次迭代并且每个循环一次迭代将需要大约 10^33 googol 年 (10^133) 才能在 4 GHz 计算机上运行。比较宇宙的年龄约为 1.37 x 10^10 年。鉴于预计核子会在大约 10^40 年内衰变,我可以直截了当地说,当前的硬件无法持续那么长时间,您可能需要同时重新启动计算机。【参考方案2】:
C# 语言规范或 CLR 中没有硬性限制。您的代码将是迭代的,而不是递归的,这可能会很快导致堆栈溢出。
有一些东西可以算作阈值,例如您将使用的(通常)int
计数器,它将为每个循环在内存中分配一个int
(在您分配整个堆栈之前)带整数...)。请注意,int
的使用是必需的,您可以重复使用相同的变量。
作为pointed out by Marco,编译器中的当前阈值比实际语言规范或运行时中的更多。一旦重新编码,您可能会进行更多的迭代。 If you for example use Ideone,默认使用旧编译器,可以轻松搞定超过 1200 个 for
循环。
虽然如此多的 for 循环表明设计不佳。我希望这个问题纯粹是假设性的。
【讨论】:
我同意这一点,但要补充一点,您可能会在达到软件限制之前达到时间/硬件限制(例如编译时间太长,或者您的代码需要太长时间/太多资源无法执行)。 一百万行的代码文件编译速度非常快,所以这不是问题。此外,由于代码对执行引擎非常直接,因此我不会对它的性能抱太大期望。 来吧,语言本身没有限制。文件系统只有 20MB 并且源文件或可执行文件变得太大并不是真正的限制@Marco13 与您的答案相反,实际上每个循环确实会增加使用的堆栈空间。 (它正在添加一个新变量。)因此,当您有足够的变量时,堆栈将用完。这与递归的问题完全相同(除了一个方法的堆栈帧将占用比一个int
更多的堆栈空间)。如果它们是没有循环变量的循环,那就不同了。
我相信 CPython 将语言结构的嵌套限制在 50 个左右。即使支持 1200 个嵌套循环也没有意义……已经有 10 个明显表明程序有问题。【参考方案3】:
所有编译为 MSIL 的 C# 都有一个限制。 MSIL 只能支持 65535 个局部变量。如果您的 for
循环与您在示例中显示的一样,则每个循环都需要一个变量。
您的编译器可能会在堆上分配对象作为局部变量的存储空间,从而绕过此限制。但是,我不确定会产生什么样的奇怪结果。反射可能会出现问题,从而使这种方法非法。
【讨论】:
for 循环不需要任何变量。 @PatrickHofman 你说得对,我在忘记包含的部分进行了编辑 @PatrickHofman 如果我没记错的话,没有变量的 for 循环等同于while(true)
,这将形成一个无限循环。如果我们将问题编辑为“终止程序中嵌套 for 循环的数量是否有限制?”那么我们可以使用局部变量技巧来形成硬限制吗?
@emory 没有什么能阻止你重复使用变量的一些非常荒谬的循环。我不会称它们为有意义的 for 循环,但它们可以完成。
@emory 你可以用break
终止循环或者让循环条件检查外部的东西,比如( ; DateTime.now() < ... ; )
【参考方案4】:
空的for(;;)
循环在 800 和 900 之间。
镜像 Marco13 的方法,除了尝试过 for(;;)
循环:
for (;;) // 0
for (;;) // 1
for (;;) // 2
// ...
for (;;) // n_max
// empty body
它适用于 800 个嵌套 for(;;)
,但它给出了 Marco13 在尝试 900 个循环时遇到的相同错误。
当它编译时,for(;;)
似乎会阻塞线程而不会耗尽 CPU;从表面上看,它的行为就像Thread.Sleep()
。
【讨论】:
“它几乎立即完成运行” - 这很奇怪 - 我认为for(;;)
在 C# 中会是一个无限循环?! (我现在不能尝试,但如果结果是错误的,我会删除这条评论)
@Marco13:是的,你是对的!不确定我之前的测试代码中发生了什么,但 for(;;)
正在阻塞而不消耗 CPU 时间。
for(;;) 循环是一个无限循环,所以为什么要添加另一个 for(;;) 循环或其中的 800 个循环,因为它们不会运行。第一个将永远运行。
@FredSmith:for(;;)
循环是嵌套的,所以它们都会从最嵌套的循环开始,即无限循环。要阻止第一个 for(;;)
,它必须是 for(;;)
。至于为什么要这样做,只是测试一下当前的.NET/C#实现有什么限制;显然它不能转到 900 for(;;)
's,即使,正如你所指出的,它什么也不做。以上是关于嵌套的“for”循环的数量是不是有限制?的主要内容,如果未能解决你的问题,请参考以下文章
在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState。 React 限制嵌套更新的数量以防止无限循环
fatal error C1061: 编译器限制 : 块嵌套太深