自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我擼了個內存泄漏檢測工具,只用了兩招

開發(fā) 開發(fā)工具
不管使用什么語言,一定要處理好內存問題,要有檢測內存問題的方法論,于是擼了個檢測是否有泄漏的小工具,這里分享一波。

 [[389657]]

本文轉載自微信公眾號「程序喵大人」,作者程序喵大人。轉載本文請聯(lián)系程序喵大人公眾號。

大家看我寫了這么長時間C++文章,殊不知我在工作中已經(jīng)一年多沒有用過C++了,最近做一個新項目,終于又回到C++的懷抱了,有點激動,也有點不適應。

不管使用什么語言,一定要處理好內存問題,要有檢測內存問題的方法論,于是擼了個檢測是否有泄漏的小工具,這里分享一波。

先貼個效果圖:

實現(xiàn)方法

眾所周知C++中申請和釋放內存使用的是new和delete關鍵字:

  1. void func() { 
  2.     A* a = new A(); 
  3.     delete a; 
  4.     A* b = new int[4]; 
  5.     delete[] b; 

再明確下需求:如果程序中存在內存泄漏,我們的目的是找到這些內存是在哪里分配的,如果能夠具體對應到代碼中哪一個文件的那一行代碼最好。好了需求明確了,開始實現(xiàn)。

內存在哪里釋放的我們沒必要監(jiān)測,只需要檢測出內存是在哪里申請的即可,如何檢測呢?

整體思路很簡單:在申請內存時記錄下該內存的地址和在代碼中申請內存的位置,在內存銷毀時刪除該地址對應的記錄,程序最后統(tǒng)計下還有哪條記錄沒有被刪除,如果還有沒被刪除的記錄就代表有內存泄漏。

很多人應該都知道new關鍵字更底層是通過operator new來申請內存的:

  1. void* operator new(std::size_t sz) 

也就是正常情況下C++都是通過operator new(std::size_t sz)來申請內存,而這個操作符我們可以重載:

  1. void* operator new(std::size_t size, const char* file, int line); 
  2. void* operator new[](std::size_t size, const char* file, int line); 

tip:new和new[]的區(qū)別我就不具體介紹了,太基礎。

如果能讓程序申請內存時調用重載的這個函數(shù),就可以記錄下內存申請的具體位置啦。

怎么能夠讓底層程序申請內存時調用重載的這個函數(shù)呢?這里可以對new使用宏定義:

  1. #define new new (__FILE__, __LINE__) 

有了這個宏定義后,在new A的時候底層就會自動調用operator new(std::size_t size, const char* file, int line)函數(shù),至此達到了我們記錄內存申請位置的目的。

這里有兩個問題:

  1. 在哪里記錄內存申請的位置等信息呢?如果在operator new內部又申請了一塊內存,用于記錄位置,那新申請的這塊內存需要記錄不?這豈不是遞歸調用了?
  2. 只有在new宏定義包裹范圍內申請了內存才會被記錄,然而某些第三方庫或者某些地方?jīng)]有被new宏定義包裹,可能就無法被監(jiān)測是否申請了內存吧?

下面逐個擊破:

哪里存儲具體信息?

我們肯定不能讓它遞歸調用啊,那這些信息存儲在哪里呢?這里可以在每次申請內存時,一次性申請一塊稍微大點的內存,具體信息存儲在多余的那塊內存里,像這樣:

  1. static void* alloc_mem(std::size_t size, const char* file, int line, bool is_array) { 
  2.     assert(line >= 0); 
  3.  
  4.     std::size_t s = size + ALIGNED_LIST_ITEM_SIZE; 
  5.     new_ptr_list_t* ptr = (new_ptr_list_t*)malloc(s); 
  6.     if (ptr == nullptr) { 
  7.         std::unique_lock<std::mutex> lock(new_output_lock); 
  8.         printf("Out of memory when allocating %lu bytes\n", (unsigned long)size); 
  9.         abort(); 
  10.     } 
  11.     void* usr_ptr = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; 
  12.  
  13.     if (line) { 
  14.         strncpy(ptr->file, file, _DEBUG_NEW_FILENAME_LEN - 1)[_DEBUG_NEW_FILENAME_LEN - 1] = '\0'
  15.     } else { 
  16.         ptr->addr = (void*)file; 
  17.     } 
  18.  
  19.     ptr->line = line; 
  20.     ptr->is_array = is_array; 
  21.     ptr->size = size
  22.     ptr->magic = DEBUG_NEW_MAGIC; 
  23.     { 
  24.         std::unique_lock<std::mutex> lock(new_ptr_lock); 
  25.         ptr->prev = new_ptr_list.prev; 
  26.         ptr->next = &new_ptr_list; 
  27.         new_ptr_list.prev->next = ptr; 
  28.         new_ptr_list.prev = ptr; 
  29.     } 
  30.     total_mem_alloc += size
  31.     return usr_ptr; 

new_ptr_list_t結構體定義如下:

  1. struct new_ptr_list_t { 
  2.     new_ptr_list_t* next
  3.     new_ptr_list_t* prev; 
  4.     std::size_t size
  5.     union { 
  6.         char file[200]; 
  7.  
  8.         void* addr; 
  9.     }; 
  10.     unsigned line; 
  11. }; 

沒有被new宏包裹的地方可以檢測的到嗎?

沒有被new宏包裹的地方是會調用operator new(std::size_t sz)函數(shù)來申請內存的。這里operator new函數(shù)不只可以重載,還可以重新定義它的實現(xiàn),而且不會報multi definition的錯誤哦。因為它是一個weak symbol,有關strong symbol和weak symbol的知識點可以看我之前的一篇文章:《談談程序鏈接及分段那些事》

既然可以重定義,那就可以這樣:

  1. void* operator new(std::size_t size) {  
  2.     return operator new(size, nullptr, 0);  

這樣有個缺點,就是不能記錄內存申請的具體代碼位置,只能記錄下來是否申請過內存,不過這也挺好,怎么也比沒有任何感知強的多。

其實這里不是沒有辦法,盡管沒有了new宏,獲取不到具體申請內存的代碼位置,但是可以獲取到調用棧信息,把調用棧信息存儲起來,還是可以定位大體位置。關于如何獲取調用棧信息,大家可以研究下libunwind庫看看。

釋放內存時怎么辦?

這里需要重定義operator delete(void* ptr)函數(shù):

  1. void operator delete(void* ptr) noexcept {  
  2.     free_pointer(ptr, nullptr, false);  

free_pointer函數(shù)的大體思路就是在鏈表中找到要對應節(jié)點,刪除掉,具體定義如下:

  1. static void free_pointer(void* usr_ptr, void* addr, bool is_array) { 
  2.     if (usr_ptr == nullptr) { 
  3.         return
  4.     } 
  5.     new_ptr_list_t* ptr = (new_ptr_list_t*)((char*)usr_ptr - ALIGNED_LIST_ITEM_SIZE); 
  6.     { 
  7.         std::unique_lock<std::mutex> lock(new_ptr_lock); 
  8.         total_mem_alloc -= ptr->size
  9.         ptr->magic = 0; 
  10.         ptr->prev->next = ptr->next
  11.         ptr->next->prev = ptr->prev; 
  12.     } 
  13.     free(ptr); 

如何檢測是否有內存泄漏?

遍歷鏈表即可,每次new時候會把這段內存插入鏈表,delete時候會把這段內存從鏈表中移出,如果程序最后鏈表長度不為0,即為有內存泄漏,代碼如下:

  1. int checkLeaks() { 
  2.     int leak_cnt = 0; 
  3.     int whitelisted_leak_cnt = 0; 
  4.     new_ptr_list_t* ptr = new_ptr_list.next
  5.  
  6.     while (ptr != &new_ptr_list) { 
  7.         const char* const usr_ptr = (char*)ptr + ALIGNED_LIST_ITEM_SIZE; 
  8.         printf("Leaked object at %p (size %lu, ", usr_ptr, (unsigned long)ptr->size); 
  9.         if (ptr->line != 0) { 
  10.             print_position(ptr->file, ptr->line); 
  11.         } else { 
  12.             print_position(ptr->addr, ptr->line); 
  13.         } 
  14.         printf(")\n"); 
  15.         ptr = ptr->next
  16.         ++leak_cnt; 
  17.     } 
  18.     return leak_cnt; 

ps:關于可以重定義operator new這個操作,我也是最近看到別人代碼后才發(fā)現(xiàn),于是參考別人代碼小擼了個代碼檢測工具,希望大家有所收獲!

 

責任編輯:武曉燕 來源: 程序喵大人
相關推薦

2021-11-05 08:29:13

數(shù)據(jù)校驗Spring

2017-05-24 17:25:44

2025-04-08 00:33:00

數(shù)據(jù)校驗Bean

2024-07-03 11:28:15

2013-08-02 09:52:14

AndroidApp內存泄漏

2019-09-09 11:35:21

GitHub工具瀏覽

2021-12-23 11:10:38

稅收大數(shù)據(jù)大數(shù)據(jù)稅收

2017-09-07 16:52:23

2020-11-04 07:56:19

工具Linux 翻譯

2015-04-17 10:35:51

c++c++程序內存泄漏檢測代碼

2016-09-12 16:01:28

Android內存泄露內存管理

2020-03-06 08:15:54

新人技術主管陳琦

2011-08-15 10:16:55

內存泄露

2009-02-01 09:42:00

2024-04-19 15:55:01

系統(tǒng)設計繪圖工具

2024-04-19 08:00:00

2023-07-17 07:06:51

電腦管家C盤文件

2022-03-25 14:11:11

Java死鎖線程

2009-11-20 10:55:13

Oracle數(shù)據(jù)比較

2018-05-23 16:56:40

戴爾
點贊
收藏

51CTO技術棧公眾號