閱讀如下代碼,為何出錯。
1 int main() { 2 char a[] = { "I am a bad boy" }; 3 char * pA = new char[ sizeof( a ) ]; 4 pA = a; 5 6 for ( size_t i = 0; i < sizeof( a ); ++i ) { 7 std::cout << *pA << std::endl; 8 ++pA; 9 } 10 delete [] pA; 11 pA = NULL; 12 return 0; 13 }
我們把那段魔術字符串命名為x。
第一步我們將字符串x存入了棧空間;第二步我們試圖在堆空間申請一塊可以正好存放x的的內存。以上兩步都做得很對,但第三步大有問題。
本意是企圖將x復制入堆內存中,但實際操作的結果是將指針指向了原x所在的棧空間。這將導致一個內存洩漏。(要注意的是string類的等於號能夠賦值是因為操作符的重載)。接下來是遍歷整個字符串,運行結果仿佛復制成功了一樣,但實際上你是在遍歷存在於棧空間的x。遍歷的過程中還不斷修改指針指向的值,因為分配字符串時最後會自動添加一個空字符串(等價於NULL或0),指針最終指向了棧空間的x末尾的空字符串處。企圖釋放棧空間導致的爆炸就更不用說了。
代碼如何修改才正確呢?
1 int main() { 2 char a[] = { "I am a bad boy" }; 3 char * pA = new char[ sizeof( a ) ]; 4 strcpy( pA, a ); 5 for ( size_t i = 0; i < sizeof( a ); ++i ) { 6 std::cout << *( pA + i ); 7 } 8 delete [] pA; 9 pA = NULL; 10 return 0; 11 }
將字符串拷貝到堆內存分配的空間中,需要用到c函數strcpy,將值拷貝過去。你也可以將x的每一個字符賦值過去。注意,千萬盡量不要改變指向堆內存首個位置的指針,如果你有把握還原回去的話。釋放一定要從堆內存首部分開始,不然也會導致爆炸。
期間你有可能會忽略這些問題。
(1)
1 ++pA;
上面已經提到過了,沒有把握釋放的是首位的話盡量不要改變指針的指向。你可以使用指針的偏移或數組下標來進行操作
(2)
1 char * pA = new char[ strlen( a ) ];
要注意strlen不計空字符的,因此堆內存分配時中會少一個存放空字符的空間,導致很多函數不能在這段字符結束時停止讀取。
1 char * pA = new char[ strlen( a ) + 1 ];
多分配一個即可,或你可以使用sizeof(注意 sizeof 指向堆內存指針 為指針的大小)。
(3)
你可能會這樣釋放數組堆內存。
1 delete pA;
編譯運行,沒有任何問題。但這是一個未定義行為(UB),在不同的平台或編譯器下運行結果可能不同,總之這是一個潛在的危險操作,你應該讓自己嚴格使用delete[]去釋放數組堆內存。
後記:
提到了string重載的等號。對於熟悉C/C++的人在這方面不會有任何問題,但對於初學者這可能確實不太好理解和弄懂。我一直不使用重載操作符或許和這也有關系吧。