作者: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等等函數都只會拷貝指定個數的字符,而不理會是否在末尾有結束符,如果忘記了自己補上結束符,可能導致訪問這個字符串時訪問到別人的內存喔。
http://rendao.org/blog/224/