原本想探究一下 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;
}
自己没找出来,是通过 valgrind 工具跑出来的
valgrind --leak-check=full ./main 1000 --leak false