我有我曾经在 XP 机器上运行的 ac# 应用程序。我最近切换到 Windows 7.0 机器。
在调试器中时出现以下错误消息:“System.StackOverflowException”。还是有XP的机器,这台没问题。
它在递归算法的中间溢出。有谁熟悉这个问题?是与此有关的操作系统还是机器本身?
非常感谢您的帮助,
迈克尔
我有我曾经在 XP 机器上运行的 ac# 应用程序。我最近切换到 Windows 7.0 机器。
在调试器中时出现以下错误消息:“System.StackOverflowException”。还是有XP的机器,这台没问题。
它在递归算法的中间溢出。有谁熟悉这个问题?是与此有关的操作系统还是机器本身?
非常感谢您的帮助,
迈克尔
在达到基本情况之前了解递归在 XP 中的深度以及在 Win7 中出错的位置会很有帮助。
从理论上讲,Windows 7 进程应该比 WinXP 进程拥有更多的可用堆栈空间;至少,它们应该是相同的。但是,这里还有其他因素在起作用。查看这篇博文:http: //blogs.technet.com/b/markrussinovich/archive/2009/07/08/3261309.aspx
简而言之,限制因素通常是“常驻可用内存”;这是物理 RAM(不是页面文件空间),可用于必须保存在那里且不能交换到页面文件的数据。很多东西必须“常驻”在普通计算机上,不能换出到页面文件中;最重要的是,任何必须在“内核模式”(需要直接访问核心系统)下运行的东西都必须保存在 RAM 中以避免页面错误,即使当时该进程没有活动线程也是如此。
Windows 7 有更多这样的“内核模式”进程。例如,Windows Aero(不是 WinXP 的一部分)使用您的显卡来加速桌面的渲染,因此它必须在内核模式下运行。Windows 7 内核本身更大,因为它包括额外的安全性和额外的内置硬件支持。Windows 7 还具有在内核模式下运行的其他后台进程等,这些进程不在 WinXP 中。
因此,在所有其他条件相同(包括 RAM)的情况下,Windows 7 机器实际上将有更少的常驻内存可用于提交您的递归算法,这意味着该算法将无法在调用之前进行足够深的递归以达到基本情况由于 Windows 没有足够的常驻内存来满足新调用所需的“提交”,触发 StackOverflowException。
此外,Windows 7 在内存中的排列方式有所不同。较早的 Windows 版本(XP 和更早版本)以大致顺序的方式为每个新进程保留内存空间;第 N+1 个进程(或线程)在为第 N 个进程/线程保留的最后一个块之后的一个块被赋予一个内存地址。从 Windows Vista 开始,内存以更“随机”的方式分配;Windows 将在内存中选择一个位置,该位置可能与任何其他保留块相邻,也可能不相邻(仅保证不属于任何其他保留块)。这是一项安全功能,旨在混淆恶意软件并防止其成功窥探其他进程的内存。但是,空间效率较低的分配方案意味着操作系统将更快地用完 1MB 的连续 RAM 块来分配给每个新线程。此时,它开始分配间隙。因此,根据您的 Windows 7 机器的特定内存使用量,递归函数的线程可能会请求通常的 1MB 堆栈空间,并由操作系统提供一个实际上只有 128K 连续空间的指针。您的程序将无法区分,直到它实际上无法提交它认为已保留的所有空间。这可能会产生 Heisenbugs,它会在一次工作但下一次失败,因为 Windows 每次为线程保留的确切内存空间存在不确定性差异。并由操作系统给出一个实际上只有 128K 连续空间的指针。您的程序将无法区分,直到它实际上无法提交它认为已保留的所有空间。这可能会产生 Heisenbugs,它会在一次工作但下一次失败,因为 Windows 每次为线程保留的确切内存空间存在不确定性差异。并由操作系统给出一个实际上只有 128K 连续空间的指针。您的程序将无法区分,直到它实际上无法提交它认为已保留的所有空间。这可能会产生 Heisenbugs,它会在一次工作但下一次失败,因为 Windows 每次为线程保留的确切内存空间存在不确定性差异。
所有这一切的答案是“更多的 RAM”。核心内核模式进程所需的数量是相对静态的,因此您可以添加的每 GB 额外 RAM 是仅可用于用户程序进程和线程的 GB。
我不相信这与您 PC 上的物理 RAM 有任何关系。我怀疑你没有碰巧在 XP 上看到它的原因仅仅是 Windows 7 可能有一个(稍微?)不同版本的 .Net。
显然,您需要以某种方式限制递归的深度(或替换非递归循环)。
但是您可以潜在地配置您的 .Net 堆栈。请查看以下链接:
递归有多递归?
如果您正在用尽堆栈并且确定这不是错误,则可以管理自己的堆栈...
例如:
void Process(SomeType foo)
{
DoWork(foo); //work on foo
foreach(var child in foo.Children)
{
Process(child);
}
}
可能成为
void Process(SomeType foo)
{
Stack<SomeType> bar=new Stack<SomeType>();
bar.Push(foo);
while(bar.Any())
{
var item=bar.Pop();
DoWork(item);//work on item
foreach(var child in item.Children)
{
bar.Push(child);
}
}
}
从而消除任何 CLR 调用堆栈问题。
当然,这不会修复无限递归。