標籤:date data 本質 成員 svc invalid library ++ 並且
作者:rendao.org,著作權聲明,轉載必須徵得同意。
記憶體越界,變數被篡改
memset時間長度度參數超出了數組長度,但memset當時並不會報錯,而是操作了不應該操作的記憶體,導致變數被無端篡改
還可能導致記憶體越界的函數有memset、memcpy、memmove、strcpy、strncpy、strcat、sprintf等等
臨時指標問題,std::string、wstring的c_str()是個臨時指標
c_str()傳回值是個char*/wchar_t*指標,這個數組的資料是臨時的,當有一個改變這些資料的成員函數被調用後,其中的資料就會失效。此時,用strlen、wcslen、strcpy、wcscpy等操作都是不可預知的,程式會崩潰。因此要麼現用先轉換,要麼把它的資料複製到使用者自己可以管理的記憶體中。
string s=”1234″;
const char* c = s.c_str();
這樣的寫法是錯誤的。因為重複使用的c指向內容實際上很容易失效。
delete char*時程式崩潰
1、記憶體越界
char* newstr = new char[strlen(s1) + strlen(s2) + 1];
sprintf(newpath, “%s\\%s”, s1, s2);
然後這個newstr在被delete時崩潰了,原因是開闢的記憶體空間不夠,記憶體越界,無法容納結束符0,如果把strlen(s1) + strlen(s2) + 1改成strlen(s1) + strlen(s2) + 2就OK了。
2、刪除的指標不屬於自己的堆棧
如果對於堆來說,每個DLL有自己的堆,那麼從DLL中動態分分配的記憶體最好是從DLL中刪除。如果從DLL中分配記憶體,然後再EXE中或者另外一個DLL中刪除,很可能導致程式崩潰。並且,輸出報錯為Invalid Address specified to RtlValidateHeap。
3、重複delete
重複delete同一個指標會崩潰,有時不易發現,可能的情況之一就是某類存在char*成員(需要new開闢記憶體),而此類是淺拷貝,若不同對象A、B間發生拷貝,A銷毀時釋放了char*指向記憶體,B仍認為自己的char*指標是有效。有時現象更加隱秘,你曉得有淺拷貝這回事還不夠,比如拷貝的發生不一定是你自己的代碼中直接導致的,比如vector的push_back操作就會不停的導致類對象的拷貝,而你不瞭解push_back的本質的話就不曉得發生了拷貝。
4、不同工程使用了不同的執行階段程式庫設定
具體現象是delete指標時程式崩潰,並輸出提示Invalid Address specified to RtlValidateHeap,這是因為在不同模組(工程)之間傳遞 C++ 類,而這兩個模組用了不同的執行階段程式庫(C/C++ -> Code Generation -> Use runtime library)設定。例如:EXE 模組調用 DLL 模組裡傳遞 C++ 類的函數,DLL模組使用靜態連結(Release 是Multi-threaded (/MT) 、Debug 是 Multi-threaded Debug (/MTd) )方式編譯,而 EXE 模組使用動態連結(Release 是 Multi-threaded DLL (/MD) 、Debug 是 Multi-threaded Debug DLL (/MDd) )方式編譯。可以對比這兩個模組的Use Runtime Library ,看看設定是否一樣,如果不一樣要改成一樣的。
以上是網路上的解釋,而且這個原因可能和第3條有關聯,可能是由於執行階段程式庫的設定不同,導致調用另外模組的類的方法時,導致這時new出來的記憶體所屬堆歸屬有問題,所以delete時就出錯了。這一段是簡單的猜測,很可能猜錯了,但這個問題已經消耗了非常多時間了,抓緊,實踐證明,我在EXE(Peer)和DLL(Reg)中使用了相同的類(Xstr),並都是源碼加入工程編譯的,EXE中建立類對象,把對象指標傳入DLL,DLL中調用類的某方法(會new一段記憶體)來容納返回的資料,而這個類對象在EXE中別銷毀時就出現了delete wchar_t*崩潰的問題,提示Invalid Address specified to RtlValidateHeap。此時檢查執行階段程式庫設定,發現的確不同,而且正如網上摘錄中舉得例子一樣,如果把EXE和DLL中的該設定都改為Multi-threaded DLL/ Multi-threaded Debug DLL,則問題解決。 Multi-threaded與Multi-threaded DLL,前者是指使用多線程版本的執行階段程式庫但靜態編譯,後者的差別就是動態連結。
關於Use runtime library的含義,微軟和C有兩種C運行期函數庫,一種是普通的函數庫:LIBC.LIB,不支援多線程。另外一種是支援多線程的: msvcrt.lib。如果一個工程裡,這兩種函數庫混合使用,可能會引起這個錯誤,一般情況下它需要MFC的庫先於C運行期函數庫被連結,因此建議使用支援多線程的msvcrt.lib。所以在使用第三方的庫之前首先要知道它連結的是什麼庫,否則就可能造成LNK2005錯誤。如果不得不使用第三方的庫,可以嘗試按下面所說的方法修改,但不能保證一定能解決問題,前兩種方法是微軟提供的: A、選擇VC菜單Project->Settings->Link->Catagory選擇Input,再在Ignore libraries 的Edit 欄中填入你需要忽略的庫,如:Nafxcwd.lib;Libcmtd.lib。然後在Object/library Modules的Edit欄中填入正確的庫的順序,這裡需要你能確定什麼是正確的順序.(最好不要這麼做)
B、選擇VC菜單Project->Settings->Link頁,然後在Project Options的Edit欄中輸入/verbose:lib,這樣就可以在編譯連結程式過程中在輸出視窗看到連結的順序了。 C、選擇VC菜單Project->Settings->C/C++頁,Catagory選擇Code Generation後再在User Runtime libraray中選擇MultiThread DLL等其他庫,逐一嘗試。
5、指標所指記憶體位址不再是new出來的首地址
char* p = new char[10];
strcpy(p, “hello”);
p++;
delete p; //運行到這回崩潰
記憶體泄露的解決
1、 尋找new和delete,malloc/realloc/calloc/strdup和free,VirtualAlloc和VirtualFree是否成對
2、 檢查new char之後是否誤用了小括弧,因為使用小括弧的話,是賦初值,方括弧才是指定長度。Debug時可能越界執行而不立即報錯,但程式在退出時則會提示記憶體泄露了。當然,如果是release,估計程式執行到這裡將立即崩潰。
3、 有時候,VC的記憶體泄露提示行代碼中new出來的類對象出現記憶體泄露,而實際上檢查發現該類對象的的確確是delete了的,實際上是提示不夠精確
可能1:實際發現還可能不是該類對象new了未釋放,而是該類對象中的代碼、包括調用的函數中出現了new但未釋放的情況,比如new CmySiteRoot中調用了xml_INIeasyget,而xml_INIeasyget中調用了只new不delete的charenc_strutf8toansi,進而導致記憶體泄露的,但報錯位置卻是new CmySiteRoot
可能2:事實證明,甚至可能和報錯的new位置new出來的那個東西毫不相干的,此時還是乖乖按照步驟解決方案1去尋找,反而快些,基本肯定的是,按照步驟1去找,總可以找到不匹配的地方,不一定過於相信VC的輸出提示位置
減少數組越界讀寫的方法
boost::array(debug時檢查數組下標,release時效率和普通數組一樣)、智能指標、vector
記憶體泄露、越界檢查工具
BoundsCheck、pageheap、gflags(Windebug中帶的)、WinDebug
字串拷貝注意要點
一定要注意結束符,經過實際驗證,strncpy、wcsncpy、memcpy、wcstombs等等函數都只會拷貝指定個數的字元,而不理會是否在末尾有結束符,如果忘記了自己補上結束符,可能導致訪問這個字串時訪問到別人的記憶體喔。
Related Posts:
- boost配置(VC/CodeBlocks)與編譯(bjam+msvc/mingW)
Tags: C++, 記憶體
http://rendao.org/blog/224/
C++記憶體問題大集合(指標問題,以及字串拷貝問題,確實挺危險的)