DUMP文件分析5:Windows中malloc和free的实现

本来,本节应该是继续典型的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运行时使用这个堆,那么一旦某个地方出现错误,后续的堆操作都可能会导致问题,包括库函数,这一点非常重要,知道这一点,你就能理解各种奇葩的堆破坏错误为什么会定位到库函数内部了。
本节就到这里了,下一节我们开始查看示例了。你应该对堆管理的底层有比较多的了解,这一点在张银奎编著的《软件调试》的堆和堆检查一章中有详细的说明,堆管理随着系统版本的变化略有差异,但基本上都是相同的。

rte_malloc_heap_dump() 函数可以用于打印当前 DPDK 内存堆的使用情况。其函数原型如下: ```c void rte_malloc_heap_dump(FILE *f); ``` 其,参数 f 是指向 FILE 结构的指针,用于指定输出的文件句柄。如果 f 为 NULL,则输出到标准输出。 以下是一个简单的示例代码,展示如何使用 rte_malloc_heap_dump() 函数: ```c #include <stdio.h> #include <rte_malloc.h> int main(int argc, char **argv) { // 初始化 DPDK 环境 rte_eal_init(argc, argv); // 分配一块内存 void *mem = rte_malloc(NULL, 1024, 0); if (mem == NULL) { printf("Failed to allocate memory!\n"); return -1; } // 输出内存使用情况 rte_malloc_heap_dump(stdout); // 释放内存 rte_free(mem); return 0; } ``` 在上述代码,我们首先使用 rte_eal_init() 函数初始化 DPDK 环境,然后使用 rte_malloc() 函数分配了一块 1024 字节的内存。接着,我们使用 rte_malloc_heap_dump() 函数打印内存使用情况,将结果输出到标准输出。最后,我们使用 rte_free() 函数释放了分配的内存。 当运行该程序时,屏幕上将输出类似以下的信息: ``` DPDK memory heap Heap name: rte_malloc_heap Total size: 268435456 bytes Free size: 267438584 bytes Free blocks: 2 Allocated blocks: 3 Minimum alloc size: 64 bytes Maximum alloc size: 268435392 bytes Total allocations: 3072 bytes Total frees: 3072 bytes ``` 可以看到,我们在程序分配了一块 1024 字节的内存,而该内存块的信息也被打印出来了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值