混搭下的C與C++內存操做

源自最近遇到一個的問題,先介紹一下背景。項目中混用了C與C++編程範式,鑑於項目成員背景不一,每一個模塊的負責人能夠自行2選1。同時爲了提升效率,C範式的模塊被容許使用STL庫的部分容器(其實也就僅僅大量使用了vector而已)。開發環境是visual studio 2005 wiht sp1。編程

那麼問題來了,在部分模塊中,純C結構體和包含C++類的結構體共存,但它們的內存佈局是不一樣的,所須要的初始化方式、內存操做函數均不一樣(malloc、new、memset....)。數組

巧合的是,在vs2005下包含vector類的結構體可使用C的內存操做函數,而不會出錯。好比用malloc申請結構體內存,用memset清空整個結構體都沒有問題,程序能夠正確運行。因此使用C規範的模塊很開心的無腦使用malloc,且都是全局變量也不涉及free的問題。數據結構

悲劇的是由於外部緣由,開發環境要升級到vs2010,上面的巧合不復存在,程序會崩潰,而且由於各模塊大量使用了memset的方式來初始化包含vector的結構體,還須要解決這類結構體的初始化問題,好比非vector的結構體成員要求清零。函數

從程序的角度來講,這是一個POD問題,如下是維基百科的POD介紹:Plain old data structure, 縮寫爲POD, 是C++語言的標準中定義的一類數據結構,POD適用於須要明確的數據底層操做的系統中。POD一般被用在系統的邊界處,即指不一樣系統之間只能以底層數據的形式進行交互,系統的高層邏輯不能互相兼容。好比當對象的字段值是從外部數據中構建時,系統尚未辦法對對象進行語義檢查和解釋,這時就適用POD來存儲數據。佈局

簡單來講就是POD類型在源代碼兼容於ANSI C時很是重要。POD對象與C語言的對應對象具備共同的一些特性,包括初始化、複製、內存佈局、尋址。測試

咱們的目標不只僅是清除現有隱患,並且要創建一個機制避免後續相似問題,由於一旦未來有人誤用內存操做,因爲錯誤地點和崩潰地點徹底不一樣,定位會很是麻煩。總的來講有如下幾點需求:spa

  1. 清理現有的POD內存申請和釋放操做,併爲POD內存申請提供檢查機制,在誤用POD內存申請的時候報錯,好比用malloc申請了非POD內存。
  2. 清理現有的memset操做,併爲memset提供檢查機制,不容許對非POD內存執行memset操做。
  3. 支持對非POD內存塊的內存清零操做
具體操做的思路是清理全部使用malloc的地方,替換爲新封裝的內存申請函數,而後迴歸測試,失敗的地方確定就是有非法POD操做,視具體狀況逐個解決便可。
  • C內存佈局(POD類型)的操做

1)動態申請和釋放
code

封裝的MemAlloc函數會檢查申請的類型是否符合POD要求。釋放操做比較簡單,封裝的MemFree函數直接調用C語言的free。對象

在vs2005和2008的標準庫中沒有is_pod函數,此時可使用boost庫的is_pod函數替代。

 

template<typename T>  
T* MemAlloc(size_t a =  1)  
{     
    assert(std::is_pod<T>::value ==  true &&  " MemAlloc POD error ");  
    T* mem = (T*) malloc(a* sizeof(T));  
     return mem;  


例:
TEST_STRU* pPdu = MemAlloc<TEST_STRU>(); //申請單個MAC_PDU內存塊
TEST_STRU* pPduS = MemAlloc<TEST_STRU>(10); //申請10個MAC_PDU內存塊
MemFree(pPdu);
blog

MemFree(pPduS);

 

2)內存置位操做

用封裝的置位函數替換標準memset函數,新函數會檢查操做的類型是否符合POD要求。採用宏替換,簡單粗暴,注意控制宏的生效範圍。

 

template<typename T>  
void pod_memset(T* p, int val, size_t size)  
{  
    assert(std::is_pod<T>::value==  true &&  " pod_memset POD error ");
    ::memset(p, val, size);  
}  
#define memset pod_memset 


  • C++內存佈局(非POD類型)的操做

1)動態申請與釋放

對於須要內存清零的申請操做,分別被封裝爲MemNew函數和MemNewMulti函數,函數中使用了不太經常使用的operator new和placement new,相關知識點這裏就不介紹了。相應的釋放函數爲MemDel和MemDelMulti。

對於不須要內存清零的動態內存操做,請使用語言自帶的new和delete。

 

template<typename T>  
T* MemNew()  
{  
    T *p = (T*) operator  new( sizeof(T));  
    ::memset(p, 0, sizeof(T));  
     new (p) T;    
     return p;  
}  
  
template<typename T>  
void MemDel(T* p)  
{  
    p->~T();  
     operator  delete(p);  
}  
  
template<typename T>  
T* MemNewMulti(size_t cnt)  
{  
    T *p = (T*) operator  new( sizeof(T)*cnt);  
    ::memset(p, 0, sizeof(T)*cnt);  
     for(size_t i= 0; i < cnt; ++i)  
    {  
         new (&p[i]) T;   
    }  
     return p;  
}  
  
template<typename T>  
void MemDelMulti(T* p, size_t cnt)  
{  
     for(size_t i= 0; i < cnt; ++i)  
    {  
        p[i].~T();  
    }  
  
     operator  delete( p);  

 

例:
TEST_STRUCT* pUser = MemNew<TEST_STRUCT>(); //申請單個TEST_STRUCT內存塊,並清零
TEST_STRUCT* pUsers = MemNewMulti<TEST_STRUCT>(3); //申請3個TEST_STRUCT內存塊,並清零
MemDel(pUser);
MemDelMulti(pUsers, 3); //釋放3個USER內存塊,注意須要額外輸入釋放的個數


2)內存置位操做

由於上面已經禁用了非POD的置位操做,誤用替換後的memset會觸發斷言保護。

對於動態申請的非POD類型的內存清零用上面的申請函數便可,對於棧上定義的非POD結構體能夠用如下方式來完成結構體成員的清零操做。


// 假設parent下的ueInfo是包含C++類成員的結構體(TEST_STRUCT),須要將其中的其餘POD成員初始化爲0 
TEST_STRUCT temp={ 0};  // 臨時變量,要求保證TEST_STRUCT的第一個成員能夠用0進行初始化,若是第一個成員也是結構體或者是數組,能夠寫成{{0}}  parent.ueInfo = temp;
相關文章
相關標籤/搜索