c++内存泄漏实战
Posted Q博士
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++内存泄漏实战相关的知识,希望对你有一定的参考价值。
开始开发C++项目也有2个月了,从一开始写代码就绕不开内存泄漏这个点。找了个时间专门搞了个专项活动,从开始的几十个内存泄漏点到现在慢慢了有了点经验,现总结下。
检测工具
我一般会用以下命令启动程序,然后把各个接口触发一遍,让代码覆盖的全一点,然后ctrl+c
结束程序,打开leak.log文件查看内存泄漏情况。
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --log-file=leak.log <bin文件>
打开leak.log文件后,会直接跳到最后看LEAK SUMMARY
:
上图中definitely lost
一栏表示有一个泄漏点,泄漏了56bytes内存。目前我们只关注definitely lost
泄漏,如果修复后,LEAK SUMMARY
应该如下所示:
如果发现definitely lost
有泄漏点,我会用definitely lost
关键字去搜索具体泄漏点,这个地方需要提醒下,尽量把你依赖的静态库中都加上-g
参数,不然无法显示静态库泄漏点的行号,对查问题不方便。
内存泄漏点实践
从我最近修的几个内存泄漏点详细讲解下。
0x01
判断条件在某种条件失效
redisReply *reply = NULL;
int redis_ret;
for (int i = 0; i < valid_cmd_num; ++i)
redis_ret = redisGetReply(conn->_ctx, (void **) &reply);
if (REDIS_OK != redis_ret)
DS_LOG_WARNING("execute cmd by pipeline failed. errno:[%d], err_msg:[%s] cmd:[%s]", conn->_ctx->err,
conn->_ctx->errstr, cmds[cmd_index[i]].c_str());
break;
std::vector <std::string> rider_info;
if (NULL != reply && REDIS_REPLY_ARRAY == reply->type)
for (int j = 0; j < reply->elements; ++j)
if (REDIS_REPLY_STRING != reply->element[j]->type)
rider_info.push_back("");
else
rider_info.push_back(reply->element[j]->str);
if (!rider_info.empty())
value.push_back(rider_info);
freeReplyObject(reply);
reply = NULL;
if (NULL != reply)
freeReplyObject(reply);
这个方法是redis
执行pipeline
方法,我把get/set
方法糅合到一起了,关注内存泄漏点的时候只考虑了get
情况,只有get
有返回,且返回对象是array
才会调用如下方法释放reply
指针,但我忽略了set
情况下reply->type
不是array
,所以也就走不到下面的方法,for
循环过程中产生的reply指针都无法释放,最后for
循环外的释放reply
只能操作最后一个reply
指针,这就造成了内存泄漏了。
freeReplyObject(reply);
reply = NULL;
解决方法
for
循环中只要reply!=NULL
都要释放指针,调整后的代码如下:
redisReply *reply = NULL;
int redis_ret;
for (int i = 0; i < valid_cmd_num; ++i)
redis_ret = redisGetReply(conn->_ctx, (void **) &reply);
if (REDIS_OK != redis_ret)
DS_LOG_WARNING("execute cmd by pipeline failed. errno:[%d], err_msg:[%s] cmd:[%s]", conn->_ctx->err,
conn->_ctx->errstr, cmds[cmd_index[i]].c_str());
break;
std::vector <std::string> rider_info;
if (NULL != reply)
if(REDIS_REPLY_ARRAY == reply->type)
for (int j = 0; j < reply->elements; ++j)
if (REDIS_REPLY_STRING != reply->element[j]->type)
rider_info.push_back("");
else
rider_info.push_back(reply->element[j]->str);
if (!rider_info.empty())
value.push_back(rider_info);
freeReplyObject(reply);
reply = NULL;
DS_LOG_DEBUG("test[%d]",0);
if (NULL != reply)
freeReplyObject(reply);
DS_LOG_DEBUG("test[%d]",1);
reply = NULL;
0x02
指针初始化位置不当
int A::b(FileInfo *file)
if (NULL == file)
DS_LOG_WARNING("fileinfo is NULL");
return -1;
//延迟5s加载
uint64_t start = CommonUtil::get_ms_timestamp();
AvgConf *new_avg_data = new AvgConf();
//load file
std::string file_path = file->_file_path + file->_file_name;
std::ifstream in_file(file_path.c_str());
if (!in_file)
DS_LOG_WARNING("open file [%s] failed", file_path.c_str());
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
_avg_info_map.erase(file->_file_path);
file->_last_modify_time = file->file_time();
return -1;
if (!file->is_channged())
return 0;
//给new_avg_data设置值得过程
....
file->_last_modify_time = file->file_time();
in_file.clear();
in_file.close();
if (0 == data_count)
DS_LOG_WARNING("new avg_data is empty");
delete new_avg_data;
new_avg_data = NULL;
return 0;
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
//指针切换,需要释放之前的指针
AvgConf *tmp = iter->second;
iter->second = new_avg_data;
if (NULL != tmp)
//延迟20s删除
sleep(20);
delete tmp;
tmp = NULL;
else
DS_LOG_DEBUG("avg_key: [%s]", file->_file_path.c_str());
_avg_info_map.insert(std::make_pair(file->_file_path, new_avg_data));
return 0;
这是一个在线程中每隔一段时间都会调用的方法,用到判断文件是否有更新,如果有更新就要更新内存中的数据,没有更新就直接返回了。第一次调用该方法时,是没有问题的,但是发现如下指针是在判断前就初始化了:
AvgConf *new_avg_data = new AvgConf();
如果第一次过后再调用该方法,如果文件没有更新,走到判断文件是否变化的方法,就会直接return -1
,这个时候这个指针就在哪里懵逼了,没人管他了。
解决方法
把指针初始化的过程放到file->is_channged()
判断后面,修改后如下:
int A::b(FileInfo *file)
if (NULL == file)
DS_LOG_WARNING("fileinfo is NULL");
return -1;
//延迟5s加载
uint64_t start = CommonUtil::get_ms_timestamp();
//load file
std::string file_path = file->_file_path + file->_file_name;
std::ifstream in_file(file_path.c_str());
if (!in_file)
DS_LOG_WARNING("open file [%s] failed", file_path.c_str());
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
_avg_info_map.erase(file->_file_path);
file->_last_modify_time = file->file_time();
return -1;
if (!file->is_channged())
return 0;
AvgConf *new_avg_data = new AvgConf();
//给new_avg_data设置值得过程
....
file->_last_modify_time = file->file_time();
in_file.clear();
in_file.close();
if (0 == data_count)
DS_LOG_WARNING("new avg_data is empty");
delete new_avg_data;
new_avg_data = NULL;
return 0;
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
//指针切换,需要释放之前的指针
AvgConf *tmp = iter->second;
iter->second = new_avg_data;
if (NULL != tmp)
//延迟20s删除
sleep(20);
delete tmp;
tmp = NULL;
else
DS_LOG_DEBUG("avg_key: [%s]", file->_file_path.c_str());
_avg_info_map.insert(std::make_pair(file->_file_path, new_avg_data));
return 0;
0x03
指针交换,原指针没有处理
int A::b(FileInfo *file)
if (NULL == file)
DS_LOG_WARNING("fileinfo is NULL");
return -1;
//延迟5s加载
uint64_t start = CommonUtil::get_ms_timestamp();
//load file
std::string file_path = file->_file_path + file->_file_name;
std::ifstream in_file(file_path.c_str());
if (!in_file)
DS_LOG_WARNING("open file [%s] failed", file_path.c_str());
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
_avg_info_map.erase(file->_file_path);
file->_last_modify_time = file->file_time();
return -1;
if (!file->is_channged())
return 0;
AvgConf *new_avg_data = new AvgConf();
//给new_avg_data设置值得过程
....
file->_last_modify_time = file->file_time();
in_file.clear();
in_file.close();
if (0 == data_count)
DS_LOG_WARNING("new avg_data is empty");
delete new_avg_data;
new_avg_data = NULL;
return 0;
std::map<std::string, AvgConf *>::iterator iter = _avg_info_map.find(file->_file_path);
if (iter != _avg_info_map.end())
iter->second = new_avg_data;
else
DS_LOG_DEBUG("avg_key: [%s]", file->_file_path.c_str());
_avg_info_map.insert(std::make_pair(file->_file_path, new_avg_data));
return 0;
上面的问题是在替换内存中数据时iter->second = new_avg_data;
,原先的指针没有做释放指针的操作,然后valgrind
会检查到AvgConf *new_avg_data = new AvgConf();
语句没有释放内存。
解决方法
交换指针,用临时指针存放旧指针,然后释放。
AvgConf *tmp = iter->second;
iter->second = new_avg_data;
if (NULL != tmp)
//延迟20s删除
sleep(20);
delete tmp;
tmp = NULL;
以上是关于c++内存泄漏实战的主要内容,如果未能解决你的问题,请参考以下文章