本来,本节应该是继续典型的DUMP分析的,但由于后面我准备说到堆损坏(Heap Corruption)所导致的程序崩溃,而堆相对于栈来说要复杂的多,所以,有些基础知识必须得清楚,才能定位堆损坏的原因。
本节,我们从表面开始,即new/delete和malloc/free,这两套API(简单的当作API吧)一个用于C++,一个用于C,面试题中常常拿来进行比较。事实上,它们有本质上的区别,new/delete是操作符,而malloc/free是函数,操作符意味着有依赖的对象,因而new/delete可以操作对象的构造和析构函数,而malloc和free不能。
事实上,我们本来应该从new/delete开始,因为它们包装了malloc/free。但这篇文章对new/delete已经说得比较清楚,文章地址为:
http://www.jianshu.com/p/8239fba221ab/comments/2578795
你可以找到Visual Studio中new/delete的源代码,它们位于 Microsoft Visual Studio 9.0\VC\crt\src 目录下的new.h 和 new.cpp中。(我的是VS2008)
因此,我们从malloc和free开始。new通过编译器进行类型检查,并计算最终需要分配的内存大小之后,就调用malloc进行实际的内存分配,因此,对于一般的类型来说,new基本上没有额外的开销,因为类型检查和待分配的内存大小的计算都是由编译器完成的。下面我们来看看malloc的代码(它们位于Microsoft Visual Studio 9.0\VC\crt\src 目录下的malloc.h和malloc.c):
void * __cdecl _malloc_base (size_t size)
{
void *res = NULL;
// validate size
if (size <= _HEAP_MAXREQ) {
for (;;) {
// allocate memory block
res = _heap_alloc(size);
// if successful allocation, return pointer to memory
// if new handling turned off altogether, return NULL
if (res != NULL)
{
break;
}
if (_newmode == 0)
{
errno = ENOMEM;
break;
}
// call installed new handler
if (!_callnewh(size))
break;
// new handler was successful -- try to allocate again
}
} else {
_callnewh(size);
errno = ENOMEM;
return NULL;
}
RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));
if (res == NULL)
{
errno = ENOMEM;
}
return res;
}
这个_malloc_base就是实际的malloc,不信你可以看看汇编代码。一次分配的内存上限为_HEAP_MAXREQ,它的定义如下:
#ifdef _WIN64
#define _HEAP_MAXREQ 0xFFFFFFFFFFFFFFE0
#else /* _WIN64 */
#define _HEAP_MAXREQ 0xFFFFFFE0
#endif /* _WIN64 */
从malloc的源代码中我们知道它实际上调用_heap_alloc来实现,这个函数如下:
__forceinline void * __cdecl _heap_alloc (size_t size)
{
#ifndef _WIN64
void *pvReturn;
#endif /* _WIN64 */
if (_crtheap == 0) {
_FF_MSGBANNER(); /* write run-time error banner */
_NMSG_WRITE(_RT_CRT_NOTINIT); /* write message */
__crtExitProcess(255); /* normally _exit(255) */
}
#ifdef _WIN64
return HeapAlloc(_crtheap, 0, size ? size : 1);
#else /* _WIN64 */
if (__active_heap == __SYSTEM_HEAP) {
return HeapAlloc(_crtheap, 0, size ? size : 1);
} else
if ( __active_heap == __V6_HEAP ) {
if (pvReturn = V6_HeapAlloc(size)) {
return pvReturn;
}
}
#ifdef CRTDLL
else if ( __active_heap == __V5_HEAP )
{
if (pvReturn = V5_HeapAlloc(size)) {
return pvReturn;
}
}
#endif /* CRTDLL */
if (size == 0)
size = 1;
size = (size + BYTES_PER_PARA - 1) & ~(BYTES_PER_PARA - 1);
return HeapAlloc(_crtheap, 0, size);
#endif /* _WIN64 */
}
中间有很多宏,无非是选择不同的平台及编译模式,最终都是由HeapAlloc来实现。所以,记住这一点,Windows下的malloc通过Win32 API HeapAlloc来实现。
同样,读者可以去看free的实现,源代码位于free.c中,它通过Win32 API HeapFree来实现。这里还注意HeapAlloc是从_crtheap这个堆上分配内存的,这个堆从何而来?请看heapinit.c中的 _heap_init函数,这个里面有这样一句代码(完整的函数我就不贴出来了):
if ( (_crtheap = HeapCreate( mtflag ? 0 : HEAP_NO_SERIALIZE,
BYTES_PER_PAGE, 0 )) == NULL )
这就是 _crtheap 被创建的时刻。那这个 _heap_init是何时被调用的呢?自然是在msvcr90.dll 的初始化函数中,打开dllcrt0.c中的_CRT_INIT函数,里面就有调用 _heap_init的代码。现在,你知道一点,malloc和free有自己专用的堆_crtheap,分配和释放内存都是从这个堆上进行的。VS运行时使用这个堆,那么一旦某个地方出现错误,后续的堆操作都可能会导致问题,包括库函数,这一点非常重要,知道这一点,你就能理解各种奇葩的堆破坏错误为什么会定位到库函数内部了。
本节就到这里了,下一节我们开始查看示例了。你应该对堆管理的底层有比较多的了解,这一点在张银奎编著的《软件调试》的堆和堆检查一章中有详细的说明,堆管理随着系统版本的变化略有差异,但基本上都是相同的。