如何在混合语言应用程序中创建堆

How are heaps created in mixed language applications?

本文关键字:创建 应用程序 语言 混合      更新时间:2023-10-16

我们有一个用Visual Basic 6.0编写的前端,它调用了几个用C/c++混合编写的后端dll。问题是,每个DLL似乎都有自己的堆,其中一个不够大。当我们分配了足够的内存时,堆与程序堆栈发生碰撞。每个DLL都是完全用C编写的,除了基本的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++语言。我们非常确定new和free操作符不会从私有堆中分配内存,并且我们想知道如何控制c++用于在每个DLL中分配对象的堆的大小。该应用程序需要在桌面Windows-NT的所有版本中运行,从2000到7。

谁能给我们指出明确和正确的文件解释如何控制c++用来分配的堆的大小对象?

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

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

CRT库的每个副本都有一个单独的和不同的状态。因此,CRT对象,如文件句柄、环境变量和区域设置仅对这些对象所在的CRT副本有效分配的或设置的当一个DLL和它的用户使用不同的副本时CRT库中,不能跨DLL边界传递这些CRT对象并期望它们在另一边被正确地捡起。
另外,由于CRT库的每个副本都有自己的堆管理器,在一个CRT库中分配内存,并将指针传递给由CRT库的不同副本释放的DLL边界是堆损坏的潜在原因。

在dll之间不传递指针。我们遇到的不是堆损坏,而是堆栈损坏。

好的,问题是:

谁能给我们指出明确和正确的文件解释如何控制c++用来分配的堆的大小对象?

我要回答我自己的问题。我从Raymond Chen的博客the Old New Thing中得到了答案,特别是对于非托管代码,也有一个很大的对象堆,但它在常规堆中。在那篇文章中,Raymond推荐了Mario Hewardt和Daniel Pravat的高级Windows调试。这本书有关于堆栈和堆损坏的非常具体的信息,这正是我想知道的。另外,它还提供了关于如何调试这些问题的各种信息。

您能详细说明一下您的说法吗?

当我们分配了足够的内存时,堆与程序堆栈发生碰撞。

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

:

谁能给我们指出明确的和正确的文档来解释如何控制c++用来分配对象的堆的大小?

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

如果没有很多关于"碰撞"的细节,很难判断你的情况下发生了什么。然而,看看这个问题的标签,我想到了一种可能性。分配的内存区域的所有权有可能(不幸的是,这种情况经常发生)在模块(在您的示例中是dll)之间传递。场景如下:

  • 有两个dll: A和b,它们都创建了自己的堆
  • DLL A在它的堆中分配一个对象,并将指针和所有权传递给B
  • DLL B接收指针,使用内存并释放对象

如果堆是不同的,大多数堆管理器不会检查被释放的内存区域是否真正属于它(主要是出于性能原因)。所以他们会把不属于他们的东西拆散。通过这样做,它们破坏了其他模块的堆。这可能(而且经常)导致崩溃。但并非总是如此。根据您的运气(以及特定的堆管理器实现),此操作可能会以某种方式更改其中一个堆,使得下一次分配将发生在堆所在区域之外。

当一个模块是托管代码,而另一个模块是本机代码时,通常会发生这种情况。由于您在问题中有VB6标签,因此我会检查情况是否如此。

如果堆栈增长到足以撞击堆,那么过早终止的堆栈溢出可能是问题所在:传递的无效数据不满足问题DLL中某些递归的退出条件(循环检测不起作用或不存在),因此无限递归消耗了大得离谱的堆栈空间。人们可能会期望这样的DLL以堆栈溢出异常终止,但可能由于编译器/链接器优化或大型外部堆大小,它会在其他地方崩溃。

堆是由CRT创建的。也就是说,malloc堆是由CRT创建的,与HeapCreate()无关。它不用于大的分配,但是,这是直接传递给操作系统。

使用多个dll,您可能会有多个堆(较新的VC版本更好地共享,但即使VC6也没有问题,如果您使用MSVCRT.DLL -那是共享的)

另一方面,堆栈是由操作系统管理的。在这里,您可以看到为什么多个堆无关紧要:操作系统对不同堆的分配永远不会与操作系统对堆栈的分配发生冲突。

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

有解吗?是:迁移到VC2010。它具有缓冲区安全检查,以一种相当有效的方式实现。即使在发布模式下也是默认的