2011-09-28 73 views
3

我们有一个用Visual Basic 6.0编写的前端,它调用混合C/C++编写的几个后端DLL。问题是每个DLL似乎都有自己的堆,其中一个不够大。当我们分配了足够的内存时,堆会与程序堆栈发生冲突。 除了用C++编写的基本DLL封装外,每个DLL都完全用C语言编写。每个DLL都有一些入口点。每个入口点立即调用一个C例程。我们希望增加DLL中堆的大小,但一直未能弄清楚如何做到这一点。我搜索了指导,并发现这些MSDN文章:如何在混合语言应用程序中创建堆?

http://msdn.microsoft.com/en-us/library/hh405351(v=VS.85).aspx

这些文章很有意思,但提供了矛盾的信息。在我们的问题中,似乎每个DLL都有自己的堆。这与“堆:乐趣和痛苦”文章相匹配,文章说C启动时(C RT)库在启动时创建了自己的堆。 “管理堆内存”文章指出,C RT库分配在默认进程堆外。 “Win32中的内存管理选项”文章说这种行为取决于正在使用的C RT库的版本。

我们通过从私有堆中分配内存来暂时解决了这个问题。但是,为了改进这个非常大型的复杂程序的结构,我们希望从一个C++包装器切换到真正的具有类的C++。我们非常肯定,新的自由操作符不会从我们的私有堆中分配内存,我们想知道如何控制C++在每个DLL中分配对象时使用的堆的大小。该应用程序需要从2000年到7

问题在桌面Windows-NT的所有版本中运行,

任何人都可以带我们去明确和正确的文件是 讲解了如何控制规模堆C++用来分配 对象?

有几个人声称由堆分配覆盖堆栈造成的堆栈损坏是不可能的。这是我们观察到的。 VB前端使用它动态加载的四个DLL。每个DLL独立于其他DLL并提供前端调用的一些方法。所有的DLL通过写入磁盘文件的数据结构进行交流。这些数据结构都是静态构造的。它们不包含指针,只是值类型和固定大小的值类型数组。问题DLL由文件名传递的单个调用调用。它旨在分配完成处理所需的大约20MB数据结构。它做了很多计算,将结果写入磁盘,释放20MB的数据结构,并返回和错误代码。然后前端卸载DLL。在调试讨论中的问题时,我们在数据结构分配代码的开头设置了一个断点,并观察calloc调用返回的内存值,并将它们与当前的堆栈指针进行比较。我们看着分配的块接近堆栈。分配完成后,堆栈开始增长,直到堆栈重叠。最终计算结果写入了堆并损坏了堆栈。随着堆栈解开,它试图返回到一个无效地址并且因分段错误而崩溃。

我们的每个DLL都静态链接到CRT,这样每个DLL都有自己的CRT堆和堆管理器。微软在http://msdn.microsoft.com/en-us/library/ms235460(v=vs.80).aspx中说:

CRT库的每个副本都有一个单独的不同的状态。 因此,诸如文件句柄,环境变量和 语言环境的CRT对象仅对分配或设置了这些对象的CRT副本有效。当DLL及其用户使用CRT库的不同副本时,无法将这些CRT对象跨越DLL边界 传递,并期望它们在另一端正确拾取。
另外,因为CRT库的每个副本都有自己的堆管理器,所以在一个CRT库中分配内存并将指针穿过 DLL边界,并由另一个CRT库的副本释放,这是 的潜在原因堆腐败。

我们不通过DLL之间的指针。我们没有遇到堆腐败,我们遇到堆栈损坏。

+0

希望你得到一个正确的答案,但如果你不这样做,你总是可以覆盖'new'和'delete'运算符来处理一个私有堆。 –

+0

谢谢。我知道我们可以做到这一点,但这是一个相当复杂的解决方案。如果我们能够计算出如何去做,那么增加运行时提供的堆的大小就简单多了。我们只使用私人堆,因为我们还没有弄清楚这一点。 –

+1

碰撞是不可能的。请澄清。 –

回答

2

OK,问题是:

任何人都可以带我们去明确和正确的文件是 讲解了如何控制++使用分配 对象堆C的大小?

我要回答我自己的问题。我从Raymond Chen的博客The Old New Thing,特别是There's also a large object heap for unmanaged code, but it's inside the regular heap得到了答案。在那篇文章中,雷蒙德建议Mario Hewardt和Daniel Pravat的Advanced Windows Debugging。这本书有非常具体的信息堆栈和堆腐败,这是我想知道的。作为一个优点,它提供了关于如何调试这些问题的各种信息。

1

能否请您阐述一下您的这个说法:

堆与程序堆栈碰撞时,我们已经分配足够的存储空间。

如果我们正在谈论Windows(或任何其他成熟的平台),这应该不会发生:操作系统确保堆栈,堆,映射文件和其他对象不会相交。

另外:

任何人都可以带我们去明确和正确的文件,说明如何控制堆C的大小++用来分配对象?

在Windows上堆大小不固定:它随着应用程序使用越来越多的内存而增长。它会增长,直到使用该进程的所有可用虚拟内存空间。确认这一点很容易:只需编写一个简单的测试应用程序,它可以持续分配内存并计算已分配的内存量。在默认的32位Windows上,你将达到将近2Gb。当然,最初堆不占用所有可用空间,因此它必须在此过程中增长。

没有很多关于“碰撞”的细节,很难说出你的情况发生了什么。但是,查看这个问题的标签会提示我一种可能性。这是可能的(不幸的是,经常发生)分配内存区域的所有权正在模块之间传递(在你的情况下是DLL)。这里的情景:

  • 有两个DLL:A和B两人都创造了他们自己的堆
  • DLL中的在其堆中分配一个对象,并将指针和所有权到B
  • 的DLL B接收该指针,采用存储器和解除分配对象

如果堆是不同的,如果实际上被解除分配的存储器区域属于它(主要是出于性能原因)最堆管理器将不会检查。所以他们会放弃一些不属于他们的东西。通过这样做,他们腐败了其他模块的堆。这可能(并且经常)导致崩溃。但不总是。根据您的运气(以及特定的堆管理器实现),此操作可能会改变其中一个堆,方式是下一个分配将发生在堆所在的区域之外。

这经常发生在一个模块是托管代码时,而另一个模块是本地模块。既然你在问题中有VB6标签,我会检查是否是这种情况。

+0

您可以检查一下,因为我已经添加了更多的细节。 –

0

如果堆栈增长得足够大以至于堆中堆栈溢出可能会成为问题:传入的无效数据不满足某些递归的退出条件(循环检测不工作或不存在)问题DLL使无限递归消耗可笑的大堆栈空间。人们会期望这样一个DLL以堆栈溢出异常终止,但是对于编译器/链接器优化或大型外部堆大小而言,它会在其他地方崩溃。

+0

我们没有看到堆栈溢出。令人惊讶的是,这个应用程序的总分配空间总共很少超过40MB。我们一直假设VB6正在做一些优化来处理Win95内存管理问题,而现在不必要的优化正在给我们带来麻烦。我准备对此有所错误。 –

+0

...在这种情况下,您不会链接到MSVCPxx,并且新运营商也会拥有其私有堆。缺点是STL/WTL/ATL中的每个模板类都将被编译到C++ DLL中,从而使其体积更大。此外,如果您希望在您的C++ DLL中使用boost,则可能需要编译该批以进行静态链接。 – aquaherd

0

堆是由CRT创建的。也就是说,malloc堆是由CRT创建的,并且与HeapCreate()无关。但它并未用于大量分配,而是直接交给操作系统。

随着多个DLL,你可能有多个堆(更新VC版本在共享更好,但即使VC6没有问题,如果你使用MSVCRT.DLL - 这是共享)

堆栈,而另一方面,由OS管理。在这里你可以看到为什么多堆不重要:不同堆的操作系统分配永远不会与堆栈的操作系统分配冲突。

请注意,操作系统可能会在堆栈附近分配堆空间。该规则只是没有重叠,毕竟没有保证“未使用的分离区”。如果你有缓冲区溢出,它可能会溢出到堆栈空间。

那么,任何解决方案?是的:转到VC2010。它有缓冲区安全检查,以相当有效的方式实施。即使在发布模式下,它们也是默认设置。

相关问题