• 一个C++工程内存泄漏问题的排查及重现工程


    最近遇到一个C++工程内存泄漏的问题,经过排查,发现原来是 map 的使用有问题,本文记录了排查的过程,并给出一个类似的工程代码。

    起因

    某日,运维反馈生产环境某台设备出现问题,经组长排查,有两个工程服务占用内存较多,出现 OOM 被 Linux 系统干掉了。其中一个是我接手的工程,竟达到了 6GB,随即安排我排查。

    排查

    首先在本地虚拟机用 cppcheck、valgrind 测试,但没有发现容易看得懂的问题点,像 cppcheck 提示了很多不怎么要紧的问题——其实有大半问题已经在前两个月修正了。而 valgrind提示多的都是第三方库,比如 curl、xml、ssl 等。

    因为没有头绪,也不敢随便动生产环境,所以写了个简单的 shell 脚本,用于监控程序的内存使用情况,并放在生产环境上,观察半天,发现隔1分钟就有少量内存泄漏,大概几十 KB 左右。因此得到存在内存泄漏的结论,但这只是验证猜测而已,因为在问题发现之初就已经把问题引致这方面了。

    由于代码年代久远,错综复杂,几天过去也没头绪,还好发现概率比较小,还有时间排查。

    后经同事指点,将监控程序频率提高,输出内存的同时打印日期时间,将其与工程日志的日期对比,缩小可疑范围,最后定位到传输模块的一个函数。

    该函数使用 malloc 根据某个数据表名称为一个结构体变量指针申请内存,再放到 map 全局变量中,由于外部函数使用到,故不能释放,跟踪发现在类的析构函数中会释放内存,但在程序运行过程并没有进行析构,所以一直没有释放内存。存放到 map 的目的是防止多次申请内存,因为数据表的数量有限——不到十个,因此使用 map,在申请之前会查找 map,如不存在再申请,并存起来。

    业务逻辑上并无问题,后在某个不起眼的地方看到了对该 map 变量的清除操作,即调用 clear 函数。怀疑此函数使用有误,于是写了一个简单的测试程序重现问题。最终得到结论:调用 map 的 clear 函数会清除 key,但如果 key 为指针,则不会释放其指向的内存。这正是问题根本原因所在。

    重现问题

    用于重现问题的测试程序如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #include <string>
    #include <map>
    
    typedef struct {
        char Name[1024];
        char Name1[1024];
        char Name3[1024];
    } TTableStruct;
    
    class CMapLeak {
    public:
        CMapLeak();
        ~CMapLeak();
        
        TTableStruct *GetTable(const char *TableName);
        void TableTest();
    
    private:
        std::map < std::string, TTableStruct * >m_mTable;
    };
    
    CMapLeak::CMapLeak()
    {
        
    }
    
    CMapLeak::~CMapLeak()
    {
        std::map < std::string, TTableStruct * >::iterator iter;
    
        for (iter = m_mTable.begin(); iter != m_mTable.end(); iter++)
        {
            TTableStruct *pStruct = iter->second;
            if (pStruct != NULL)
            {
                delete pStruct;
            }
        }
    }
    
    TTableStruct* CMapLeak::GetTable(const char *TableName)
    {
        TTableStruct *pStruct = NULL;
        std::map < std::string, TTableStruct * >::iterator iter;
        iter = m_mTable.find(TableName);
        if (iter == m_mTable.end())
        {
            pStruct = new TTableStruct[100];
            m_mTable[TableName] = pStruct;
            printf("NEW!!! struct ptr: %p\n", pStruct);
        }
        else
        {
            pStruct = iter->second;
            printf("struct ptr: %p\n", pStruct);
        }
        return pStruct;
    }
    
    void CMapLeak::TableTest()
    {
        TTableStruct *pStruct = NULL;
        int i = 0;
        char tablename[32] = {0};
        while (1)
        {
            //m_mTable.clear(); // !!! 如执行此行,则会清空 map 的 key
            sprintf(tablename, "table_%d", (i++)&0x03);
            pStruct = GetTable(tablename);
            printf("%s: struct ptr: %p\n", tablename, pStruct);
            printf("----------------\n");
            sleep(1);
        }
    }
    
    int main(void)
    {
        CMapLeak* pLeak = new CMapLeak();
        pLeak->TableTest();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    代码逻辑比较简单,为模拟生产环境的运行,直接使用死循环执行。先查找 m_mTable,如果 key 不存在则申请内存,否则直接返回已申请的内存。为了方便观察内存使用情况,在结构体中多加了几个数组。

    当对 map 进行 clear 操作时,出现内存泄漏,监控脚本输出如下:

    有内存泄漏的:
    23:14:31
    dataserver ps mem: 13596 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413468 kB Cached: 637216 kB
    -------------
    23:14:36
    dataserver ps mem: 15116 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2412884 kB Cached: 637216 kB
    -------------
    23:14:41
    dataserver ps mem: 16636 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413492 kB Cached: 637216 kB
    -------------
    23:14:46
    dataserver ps mem: 18460 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413144 kB Cached: 637216 kB
    -------------
    23:14:52
    dataserver ps mem: 19980 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2412740 kB Cached: 637216 kB
    -------------
    23:14:57
    dataserver ps mem: 21500 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413104 kB Cached: 637216 kB
    -------------
    23:15:02
    dataserver ps mem: 23020 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413364 kB Cached: 637216 kB
    -------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    如果不调用 clear 函数,则内存占用较稳定:

    23:10:12
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413616 kB Cached: 637212 kB
    -------------
    23:10:17
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2412888 kB Cached: 637212 kB
    -------------
    23:10:22
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413504 kB Cached: 637212 kB
    -------------
    23:10:27
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413092 kB Cached: 637212 kB
    -------------
    23:10:32
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413536 kB Cached: 637212 kB
    -------------
    23:10:37
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2412988 kB Cached: 637212 kB
    -------------
    23:10:43
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413348 kB Cached: 637212 kB
    -------------
    23:10:48
    dataserver ps mem: 13900 VmRSS: 1068 kB
    System memory info:  MemTotal: 3861496 kB MemFree: 2413796 kB Cached: 637212 kB
    -------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    小结

    就目前排查结果看,只需要将原工程清除 map 的 clear 函数去掉即可。但排查过程,还是花了一定的时间。

  • 相关阅读:
    食品商城网站设计—食品商城购物网站(8页) HTML+CSS+JavaScript 静态网页的制作
    第二章操作系统测试
    Java程序处理不同数据库时间类型
    Docker安装RabbitMQ
    给你 2 万条数据,怎么快速导入到 MySQL?写得太好了...
    Java多线程编程
    Python读取文件
    静态HTML CSS网站制作成品 简单的学生网页作业代码【带视频演示】
    SpringBoot整合Shiro和加密
    最舒适的蓝牙耳机有哪些推荐?骨传导蓝牙耳机
  • 原文地址:https://blog.csdn.net/subfate/article/details/120928123