1. 程式人生 > 其它 >一個解決方案多個專案使用vld檢測記憶體洩漏_記憶體管理-如何排查記憶體洩漏

一個解決方案多個專案使用vld檢測記憶體洩漏_記憶體管理-如何排查記憶體洩漏

技術標籤:一個解決方案多個專案使用vld檢測記憶體洩漏

1434f9b8b7b6d47ad35b4fad58b86f11.png

全球圖形學領域教育的領先者、自研引擎的倡導者、底層技術研究領域的技術公開者,東漢書院在致力於使得更多人群具備核心級競爭力的道路上,將帶給小夥伴們更多的公開技術教學和視訊,感謝一路以來有你的支援。我們正在用實際行動來幫助小夥伴們構建一套成體系的圖形學知識架構,你在我們這裡獲得的不止於那些毫無意義的程式碼,我們這裡更多的是程式碼背後的故事,以及精準、透徹的理解。我們不會扔給人們一本書或者給個思路讓人們去自學,我們是親自來設計出好的資源,讓人們明白到底背後還有哪些細節。

這裡插播一個引擎大賽的訊息,感興趣的同學可以看一眼,這也是東漢書院的立項使命:

瘋狂的程式設計師:自研引擎大賽​zhuanlan.zhihu.com 24d0099f286aa758f73ae21a4d52121d.png

大賽官方主頁

東漢書院-自己動手寫遊戲引擎​edu.battlefire.cn

參賽選手作品

一個小小的渲染引擎,opengl4.0搭配imgui,可控制雲 水 山 天空的變換​github.com 68e124b4f284c4604e25c556d09970b1.png

簡介

在我們寫一個龐大專案的時候,比如我們為公司寫一個面向具體業務的引擎核心的時候,我們並沒有什麼卵詳盡的開發計劃和設計,往往我們都是一邊設計,一邊想實現,需求還經常變化,這是無法避免的,就像是你去打副本,你只知道要把人家Boss收拾了才能拿到裝備,至於你每次去的時候組到了什麼隊友,怎麼收拾的Boss,那可能就千變萬化了。

從設計者的角度來看

這種開發流程是非常棒的

6de1fcd285485e48506ed6cfc9bba06c.png

因為非常的自由與靈活

碰到問題隨機應變、靈活處置

但是從施工隊的角度來說

這必然會導致專案變得一團糟

胸中除了羊駝在奔騰以外

殺人的心都有了

abdc796b1947fc46a506187c3d025bd3.png

當專案的dead line臨近的時候,我們忽然開始意識到,我們需要把程式碼整理一下,把沒用的程式碼刪掉,讓專案變得穩定起來,要開始準備Alpha測試了。

如果你是在一個小公司或者大公司裡的一個小團隊的話,至少在一般的遊戲公司中,依然是大公司下圈養著一群互相獨立的小作坊的模式。如此一來,你就不可能指望能拿到一大堆專業的測試工具來幫助你把專案測試一遍。於是我們就需要一個解決方案,這個解決方案能夠很輕鬆的植入到現有的專案程式碼中,同樣,我們希望這樣的一個解決方案是通用的,而不是說我們需要為每一個專案都搞一套記憶體洩漏檢查方案。我們希望這樣的一套記憶體檢測機制能夠非常容易的在下一次專案中也能用上。在程式執行結束的時候,我能夠通過這個解決方案拿到程式未釋放的記憶體的一個清單,這樣就可以幫助我去排查記憶體的洩漏了。

已經報名了我們引擎課程的同學應該是想到了,我們講過的記憶體池的實現,其實那個不僅僅是實現了記憶體池來加速記憶體的分配和回收速度,更大的作用在於幫助你去非常方便的去Debug你專案的記憶體的使用狀況。

現在讓我來更詳盡的闡明,我們的記憶體追蹤器到底需要什麼。首先,我們需要這個記憶體追蹤器能夠新增到現有的專案中,且不要對現有的專案進行大的改動。程式碼重用是非常重要的,尤其是你在一個公司裡面幹活的時候。它可能節省很多時間與金錢。其次,我們的解決方案必須足夠簡單。我們不能夠為了實現對專案中所有記憶體使用的追蹤而對現有的程式碼大改特改。最後這個解決方案必須是免費的

16b75495f468387b53830d44004f3e8e.png

雖然大家都說免費的東西是最貴的

所以我們現在來看一眼我們專案裡的程式碼,我們發現的第一個東西就是,所有的記憶體分配的操作都是通過new操作符以及它的變種來完成的。同樣的,所有的記憶體回收函式都是通過delete操作符以及它的變種來完成的。

那麼我們是不是隻需要使用什麼專門的函式來替換掉現在的這些new和delete操作符就可以完成追蹤記憶體的目的了呢?不是的!這麼幹,你要改動的程式碼太多了。C++允許你去為你的類過載new和delete操作符。如果這不是意味著你需要為你的所有的類去過載new和delete運算子的話,這將是一個非常棒的解決方案。等等...我們也可以過載全域性的new和delete操作的啊!!!過載了全域性的new和delete操作符了之後,你就可以在每一次分配記憶體和釋放記憶體的時候幹任何你想幹的事情了。這真的是太棒了!其實我們的引擎同學應該已經學完這塊了,畢竟這是整個引擎的根基,我們教會同學們的豈止是一個過載new和delete的操作符,還有我們的C++Tricks中講述的,我們是在教會同學們整套底層的核心級玩法。

言歸正傳,現在我們只需要在記憶體塊被邏輯層需要的時候標記一下,如果程式執行結束的時候,這塊記憶體沒被還回來,那麼它就是洩漏了!

由於我們這裡不是在實現記憶體池管理解決方案,所以我們這裡加了_DEBUG巨集,引擎課程學習的同學可以不必管這裡講的這些,跟著引擎課的內容走就可以了,那邊是整套成體系的內容。

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size, const char *file, int line){
};
inline void __cdecl operator delete(void *p){
};
#endif

上面就是現階段的全部程式碼了,我們就過載了這倆函式,並且我們用#ifdef和#endif把它們框起來了,因為我們說了,我們本文介紹的是記憶體追蹤,不是記憶體池演算法。如果你實現了記憶體池演算法,那麼你的這個過載應該是一直開著的。記憶體池的好處咱們就不詳細的說了,總而言之,就是讓你的系統更加的穩定和可Debug。

由於我們還希望知道我們記憶體分配的時候,是哪個檔案的哪行程式碼分配的記憶體洩漏了,所以我們的使用了下面的巨集來進一步的封裝了new運算子,這樣一來,你就無需要改變你現有的程式碼,它們會自動的給自己新增上自己是在哪個地方初始化的記憶體塊。

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW

現在我們來實現我們的new和delete函式,我們在初始化記憶體塊後,對它進行追蹤,在釋放記憶體塊之後,取消對它的追蹤,如此一來,程式執行結束之後,還處於追蹤狀態的記憶體塊就是沒被釋放的記憶體塊,也就是記憶體洩漏了。

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size,const char *file, int line){
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);
};
inline void __cdecl operator delete(void *p){
    RemoveTrack((DWORD)p);
    free(p);
};
#endif

下面的程式碼展示的就是追蹤記憶體塊和取消對記憶體塊追蹤的程式碼,我們會把所有的記憶體塊都串成一個連結串列。

typedef struct {
  DWORD  address;
  DWORD  size;
  char  file[64];
  DWORD  line;
} ALLOC_INFO;
typedef list<ALLOC_INFO*> AllocList;
AllocList *allocList;
void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum){
  ALLOC_INFO *info;
  if(!allocList) {
    allocList = new(AllocList);
  }
  info = new(ALLOC_INFO);
  info->address = addr;
  strncpy(info->file, fname, 63);
  info->line = lnum;
  info->size = asize;
  allocList->insert(allocList->begin(), info);
};

void RemoveTrack(DWORD addr){
  AllocList::iterator i;
  if(!allocList)
    return;
  for(i = allocList->begin(); i != allocList->end(); i++){
    if((*i)->address == addr){
      allocList->remove((*i));
      break;
    }
  }
};

最後這段程式碼就是在程式執行結束的時候去打印出來還沒有被釋放的記憶體塊的程式碼,這些記憶體塊就是記憶體洩漏的地方。由於我們之前記錄了每一塊記憶體是從哪裡分配出去的,所以我們很容易就知道,我們的程式碼是哪裡出現了記憶體洩漏。

void DumpUnfreed(){
  AllocList::iterator i;
  DWORD totalSize = 0;
  char buf[1024];
  if(!allocList)
    return;
  for(i = allocList->begin(); i != allocList->end(); i++) {
    sprintf(buf, "%-50s:ttLINE %d,ttADDRESS %dt%d unfreedn",
      (*i)->file, (*i)->line, (*i)->address, (*i)->size);
    OutputDebugString(buf);
    totalSize += (*i)->size;
  }
  sprintf(buf, "-----------------------------------------------------------n");
  OutputDebugString(buf);
  sprintf(buf, "Total Unfreed: %d bytesn", totalSize);
  OutputDebugString(buf);
};

故事的最後


我們為同學們介紹一個程式分析工具

這個工具的名字叫:valgrind

它的功能是自動檢測記憶體管理以及執行緒BUG

Valgrind​www.valgrind.org

我們核心關注和討論的領域是引擎的底層技術以及商業化方面的資訊,可能並不適合初級入門的同學。另外官方維護兩個公眾號,第一個公眾號是關於我們企業自身產品的資訊與動態的公眾號,如果對我們自身資訊與動態感興趣的同學,可以關注圖形之心

除此之外,我們為了更頻繁的釋出一些諮詢與文章,我們維護的第二個公眾號是“核心觀察”,核心觀察提供的主要是一些與我們無關的諮詢與文章。

隻言片語,無法描繪出整套圖形學領域的方方面面,只有成體系的知識結構,才能夠充分理解和掌握一門科學,這是藝術。我們已經為你準備好各式各樣的內容了,東漢書院,等你來玩。