昨天幫導師作的一個程序出了內存泄露的bug(在VS上程序運行一切正常,等return返回後纔出錯)html
並且是程序運行結束後纔出現的錯誤,在退出前一切代碼都順利執行完了,只是return以後出錯。java
以後我在Linux下從新編譯運行程序,提示的信息更詳細:ios
free(): invalid next size (normal)函數
而後下面顯示Backtrace和Memory map等一大串錯誤信息。post
最終調試發現問題在於,讀取數據格式不對,致使字符串轉換後的int小於0,下標越界。我只檢查了上限N,沒檢查下限0。測試
那麼問題來了,爲何動態分配的內存能訪問下標爲負的地方呢?來寫幾個程序測試下。ui
#include <iostream> using namespace std; int main() { const int N = 5; int* p = new int[N]; p[-1] = 1; p[-2] = 3; for (int i = -4; i < 0; i++) { cout << p[i] << " "; } cout << endl; delete[] p; return 0; }
0 0 3 1 *** Error in `./a.out': munmap_chunk(): invalid pointer: 0x0000000000ed6c20 ***
能夠發如今n<0時,p[n]仍然能夠訪問,可是最終結束時會出錯。url
再看看下面這份代碼spa
#include <iostream> using namespace std; int main() { const int N = 5; int* p = new int[N]; p[N] = 100; cout << p[N] << endl; delete[] p; return 0; }
運行結果是100,而且沒任何問題。指針
也就是說,C/C++能夠訪問顯式申請的內存以外的內存空間,它們多是庫函數隱式申請的,好比之因此上面一份代碼正常運行,可是異常退出,下面一份代碼正常運行、正常退出。緣由是,new(會調用內置的allocator)動態申請一片內存時,會在返回的指針p以前記錄下申請的內存大小,這樣以後用delete釋放new申請的內存時會隱式查找記錄的內存大小,從而知道該釋放多少內存。因此才能夠用delete[]而不是delete[N]。
同理,使用malloc()時也會在返回的指針以前的某個地址記錄申請內存大小,這樣free()就會在釋放內存時找到這個記錄分配大小的地址,而後知道釋放多少。
C/C++不會像java同樣在編譯層面檢查下標是否越界,因此若是不在代碼裏手動檢查,下標越界可能會致使庫函數須要用到的內存地址被咱們誤修改,從而使庫函數出錯。
明白了這一點後,new和delete配對,new[]和delete[]配對,malloc()和free()配對的緣由也理解了。每一個內存分配器都有本身的申請和釋放的策略,好比說記錄申請的空間,我能夠在一個字節的前幾位記錄,也能夠在一個字節的後幾位記錄,若是申請和釋放的規則不一致的話就會形成錯誤的後果。
回顧以前個人兩篇相似的博客
【free() invalid next size】謹慎地在C++的類中存儲指針來方便訪問其餘節點
第一篇,用cvLoadImage申請內存,卻用delete釋放內存,二者記錄申請內存大小的策略不一樣,所以釋放出錯。
第二篇,記錄了vector以前的內部指針p,可是vector從新分配內存後內部指針變了,再訪問p指向的位置就物是人非了。和我此次很像的是,以前那篇我自信滿滿地認爲vector不會從新分配內存,即認爲push_back的次數小於reserve預留的大小,這篇則是自信滿滿地認爲下標確定爲非負數,由於以前的下標是用字符串轉換而成的,好比"0a"對應的就是10,我認爲確定會不小於0,可是這些下標是從1開始的,因此我將字符串轉換後的下標都減了1,這樣的話錯誤的輸入好比"00"在轉換後就是-1,下標越界。
總結下來,C/C++下標越界確實是個麻煩,有時候像這種「自信滿滿」的預測會致使運行錯誤,因此最佳的實踐方式是寫出便於調試的代碼。
一、儘量使用STL容器,STL容器在下標越界時會在訪問時就出錯,不會讓程序繼續運行;
二、使用RAII來讓申請和釋放配對;
三、調試時若想得到更詳細的信息,在全部須要用下標的位置都加上檢查語句。