數組與指針閱讀如下代碼,為何出錯。 1 int main() { 2 char a[] = { "I am a bad boy" }; 3 char * pA = new char[ sizeof( a ) ]; 4 pA = a; 5 6 ...
數組與指針
閱讀如下代碼,為何出錯。
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++的人在這方面不會有任何問題,但對於初學者這可能確實不太好理解和弄懂。我一直不使用重載操作符或許和這也有關係吧。