您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
c++内存泄漏实战
 
作者:Q博士
   次浏览      
 2020-7-7 
 
编辑推荐:

本文主要讲解了检测工具以及内存泄漏点实践,希望本文对大家有帮助。

文章来自于csdn,由火龙果Anna编辑推荐。

检测工具

我一般会用以下命令启动程序,然后把各个接口触发一遍,让代码覆盖的全一点,然后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;
}
1

   
次浏览       
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
.NET Core 3.0 正式公布:新特性详细解读
.NET Core部署中你不了解的框架依赖与独立部署
C# event线程安全
简析 .NET Core 构成体系
C#技术漫谈之垃圾回收机制(GC)
最新课程
.Net应用开发
C#高级开发技术
.NET 架构设计与调试优化
ASP.NET Core Web 开发
ASP.Net MVC框架原理与应用开发
更多...   
成功案例
航天科工集团子公司 DotNet企业级应用设计与开发
日照港集 .NET Framewor
神华信 .NET单元测试
台达电子 .NET程序设计与开发
神华信息 .NET单元测试
更多...