c++記憶體流失實戰

來源:互聯網
上載者:User

開始開發C++項目也有2個月了,從一開始寫代碼就繞不開記憶體流失這個點。找了個時間專門搞了個專項活動,從開始的幾十個記憶體流失點到現在慢慢了有了點經驗,現總結下。

偵查工具 記憶體流失點實踐 0x01 0x02 0x03

偵查工具

我一般會用以下命令啟動程式,然後把各個介面觸發一遍,讓代碼覆蓋的全一點,然後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;            }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.