• 记一次因为没有关闭 管道导致的内存泄露


    原本想探究一下 string(char*) 是移动构造还是复制构造,在测试时发现了意料之外的内存泄露,
    找了挺久没有想到哪里,最后发现是管道没有正确关闭,UNIX 系统中一切皆文件,但是 管道这个文件是待在内存中的,不关闭就会发生内存泄露。
    样例代码如下

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define SEPRATOR '\n'
    
    // 原本想探究一下 string(char*) 是移动构造还是复制构造
    // 这个函数会导致内存泄露
    template <typename... Args> std::string format_string_leak(std::string &&format, Args... elems)
    {
        char *p = new char[1024];
        sprintf(p, format.c_str(), elems...);
        return std::string{ p }; // 这里会发生转移吗?实际是不会,这里会发生复制
    }
    
    // 正确的用法
    template <typename... Args> std::string format_string(std::string &&format, Args... elems)
    {
        char *p = new char[1024];
        sprintf(p, format.c_str(), elems...);
        std::string ret{ p }; // 这里会发生转移吗?实际是不会,这里会发生复制
        delete[] p;
        return ret;
    }
    
    // 打印资源使用情况,这里只打印内存
    void printResource()
    {
        std::string cmd = format_string("ps -p %d -o rss", getpid());
        FILE *pipe = popen(cmd.c_str(), "r"); // 这里开启了一个管道命令
        if (!pipe) {
            std::cerr << "pipline create failed" << std::endl;
            exit(EXIT_FAILURE);
        } else {
            // 通过重复读取的方式将管道文件中的内容读取出来,有没有更简单优雅的方法?
            std::stringstream ss;
            char buf[128];
            while (fgets(buf, sizeof(buf), pipe) != nullptr) {
                ss << buf;
            }
            // 管道输出为该进程的常驻内存占用(kb),要按行解析找到第二行
            //  RSS
            // 73893
            std::vector<std::string> result;
            std::string token;
            while (std::getline(ss, token, SEPRATOR)) {
                result.emplace_back(token);
            }
    
            int mem_usage_kb = std::stoi(result[1]);
            std::cout << format_string("mem usage : %dkb\n", mem_usage_kb);
            // 如果这里没有关闭管道,就会发生内存泄露
            // pclose(pipe); 
        }
    }
    // 编译 g++ --std=c++11 ./main.cpp -o main
    // 模拟泄露 ./main 1000 --leak true
    // 正常  ./main 10000
    int main(int argc, char *argv[])
    {
        int bench_count = 1;
        bool leak = false;
    
        if (argc >= 2) {
            bench_count = std::stoi(argv[1]);
        }
        if (argc == 4 && std::string(argv[2]) == "--leak") {
            leak = std::string(argv[3]) == "true" ? true : false;
        }
        std::cout << "test leak " << (leak ? "true" : "false") << std::endl;
        // 重复跑,不断打印内存使用,看是否有上涨
        for (int i = 0; i < bench_count; i++) {
            if (leak) {
                std::string v = format_string_leak("hello world %s, %d, %ld", "yang", 12, 23.L);
            } else {
                std::string v = format_string("hello world %s, %d, %ld", "yang", 12, 23.L);
            }
            if (i % 20 == 0) {
                printResource();
            }
        }
        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

    自己没找出来,是通过 valgrind 工具跑出来的

     valgrind --leak-check=full ./main 1000 --leak false
    
    • 1
  • 相关阅读:
    D. Anti-Sudoku
    【WINDOWS / DOS 批处理】字符串替换
    Linux 开源数据库Mysql-10-mysql集群一主一从GTID
    eBPF学习笔记(二)—— eBPF开发工具
    20220814NOI模拟赛--考后总结
    springboot+ssm+jsp疫苗接种管理系统
    浅谈C++函数
    kafka本地安装报错
    Ajax学习与使用(HTML、XML、JSON格式数据的处理方法、区别)
    三个本地组策略的设置实例
  • 原文地址:https://blog.csdn.net/richard_m_yang/article/details/133891539