1. 一個錯誤釋放記憶體的例子 下麵的場景會有什麼錯? 一切看上去都是有序的。new匹配了一個delete。但有一些地方確實是錯了。程式的行為是未定義的。至少來說,stringArray指向的100個string對象中的99個看上去都不能被正確釋放,因為他們的析構函數可能永遠不會被調用。 2. 使用n ...
1. 一個錯誤釋放記憶體的例子
下麵的場景會有什麼錯?
1 std::string *stringArray = new std::string[100]; 2 3 ... 4 5 delete stringArray
一切看上去都是有序的。new匹配了一個delete。但有一些地方確實是錯了。程式的行為是未定義的。至少來說,stringArray指向的100個string對象中的99個看上去都不能被正確釋放,因為他們的析構函數可能永遠不會被調用。
2. 使用new 和delete時究竟做了啥?
當你使用一個new表達式(通過使用new動態的創建一個對象)時,會發生兩件事情。第一,記憶體被分配(通過一個叫做operator new的函數,看Item 49和Item 51)。第二,在分配的記憶體上調用了一個或多個構造函數。當你使用一個delete表達式時,另外兩件事情會發生:在記憶體上調用了一個或者多個析構函數,然後記憶體被解除分配(通過調用叫做operator delete的函數,見 Item 51)。關於delete的一個重要的問題是:在即將被刪除的記憶體中究竟有多少對象?這個問題的答案決定了有多少個析構函數必須被調用。
3. new和delete不配對使用為啥會出錯?
實際上,下麵這個問題更加簡單:被刪除的指針是指向一個單獨的對象還是指向數組的所有對象?這是個關鍵的問題,因為單個對象的記憶體分配通常情況下同數組的記憶體分配是不一樣的。特別的,一個數組的記憶體通常包含了數組的大小,因此delete很容易就會知道需要調用多少個析構函數。單個對象的記憶體卻沒有這樣的信息。你可以將記憶體不同分配想象成下麵這個樣子,n是數組的大小:
當然這隻是一個例子。編譯器不需要這麼實現,雖然很多編譯器確實是這麼實現的。
當你在一個指針上使用delete時,delete能夠知道數組容量信息是否存在的唯一方法就是通過你來告訴它。如果當你使用delete時用了“[]”,delete認為指針指向一個數組。否則,它會認為它在指向一個單一的對象:
1 std::string *stringPtr1 = new std::string; 2 3 std::string *stringPtr2 = new std::string[100]; 4 5 ... 6 7 delete stringPtr1; // delete an object 8 9 delete [] stringPtr2; // delete an array of objects
4. new和delete不配對使用會有什麼後果?
如果你在stringPtr1上使用“[]”將會發生什麼?結果是未定義的,但是結果不會太好。假設記憶體分佈如上圖所示,delete會讀取一些記憶體並把它所讀到的解釋為一個數組容量,接下來就開始多次調用析構函數,卻忽略的以下事實:它處理的記憶體不但不是一個數組,也可能並沒有包含它正忙著釋放的那種類型的對象。
如果你不在stringPtr2上使用“[]”會發生什麼?結果也是未定義的,但是你可以看到這會導致過少的構造函數被調用。此外,對於像int的內建類型來說結果也是未定義的(有時甚至是有害的),雖然內建類型沒有析構函數。
規則很簡單:如果你在一個new表達式中使用”[]”,你必須在對應的delete表達式中使用”[]”,反之亦然。
當你實現一個包含指向動態分配記憶體的指針的類,並且同時提供多個構造函數的時候,你需要將上面的重要規則記在心中,因為你必須當心在對構造函數中對指針成員進行初始化時,new必須使用相同的形式。如果你不這麼做,你又怎麼能知道在析構函數中將使用什麼形式的delete呢?
5. 使用typedef時需要註意new和delete的配對使用
對於傾向於使用typedef的人來說這條規則同樣值得註意,因為這意味著typedef的作者必須指出使用new來創建typedef類型的對象時,使用什麼形式的delete對其進行銷毀。看下麵的例子:
1 typedef std::string AddressLines[4]; // a person’s address has 4 lines, 2 3 // each of which is a string
因為AddressLines是一個數組,new應該這麼使用:
1 std::string *pal = new AddressLines; // note that “new AddressLines” 2 3 // returns a string*, just like 4 5 // “new string[4]” would
使用delete的形式必須和new相匹配:
1 delete pal; // undefined! 2 3 delete [] pal; // fine
為了避免這種混淆,不如放棄在數組類型上使用typedef。這很容易,因為標準c++庫(見Item 54)中包含string,vector和模板,使得對動態分配數組的需求幾乎將為0。這裡我們舉個例子,AddressLines可以被定義成由strings組成的vector,也就是類型 vector<string>。