C++内存管理——指针数组
C++/C程序中,指針和數組在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。但二者有著本質的區別:
數組:要么在靜態存儲區被創建(如全局數組),要么在棧上被創建。數組名對應著(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容可以改變。
指針:可以隨時指向任意類型的內存塊,它的特征是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。
下面以字符串為例比較指針與數組的特性:
1.修改內容
實例1代碼中,字符數組a的容量是6個字符,其內容為hello。a的內容可以改變,如a[0]= ‘X’。指針p指向常量字符串“world”(位于靜態存儲區,內容為world),常量字符串的內容是不可以被修改的。從語法上看,編譯器并不覺得語句p[0]= ‘X’有什么不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。
View Code1 實例1 修改數組和指針內容char a[] = “hello”;
a[0] = ‘X’;cout << a << endl;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 編譯器不能發現該錯誤
cout << p << endl;
2.內容復制與比較
不能對數組名進行直接復制與比較。示例2中,若想把數組a的內容復制給數組b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函數strcpy進行復制。同理,比較b和a的內容是否相同,不能用if(b==a) 來判斷,應該用標準庫函數strcmp進行比較。
指針應用中,語句p = a 并不能把a的內容復制指針p,而是把a的地址賦給了p。要想復制a的內容,可以先用庫函數malloc為p申請一塊容量為strlen(a)+1個字符的內存,再用strcpy進行字符串復制。同理,語句if(p==a) 比較的不是內容而是地址,應該用庫函數strcmp來比較。
// 數組…
char a[] = "hello";char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)
…
// 指針…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用
p = a;if(strcmp(p, a) == 0) // 不要用 if (p == a)
…
3.計算內存容量
用運算符sizeof可以計算出數組的容量(字節數)。示例3-1中,sizeof(a)的值是12(注意別忘了'\0')。指針p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指針變量的字節數,相當于sizeof(char*),而不是p所指的內存容量。C++/C語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。示例3-2中,不論數組a的容量是多少,sizeof(a)始終等于sizeof(char *)。
View Code 3 實例3-1 計算數組和指針的內存容量char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12字節
cout<< sizeof(p) << endl; // 4字節 View Code 4 實例3-2 數組退化為指針
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4字節而不是100字節
}
4.指針參數是如何傳遞內存的?
如果函數的參數是一個指針,不要指望用該指針去申請動態內存。示例4-1中,Test函數的語句GetMemory(str, 200)并沒有使str獲得期望的內存,str依舊是NULL,為什么?
View Code 5 實例4-1 試圖用指針參數申請動態內存 void GetMemory(char *p, int num){
p = (char *)malloc(sizeof(char) * num);
}
void Test1(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然為 NULL
strcpy(str, "hello"); // 運行錯誤
}
?
毛病出在函數GetMemory中。編譯器總是要為函數的每個參數制作臨時副本,指針參數p的副本是 _p,編譯器使 _p = p。如果函數體內的程序修改了_p的內容,就導致參數p的內容作相應的修改。這就是指針可以用作輸出參數的原因。在本例中,_p申請了新的內存,只是把_p所指的內存地址改變了,但是p絲毫未變。所以函數GetMemory并不能輸出任何東西。事實上,每執行一次GetMemory就會泄露一塊內存,因為沒有用free釋放內存。
(每次執行GetMemory,_p就會申請新的內存,_p的地址就會改變,堆內存需要手動釋放,而函數中沒有free釋放內存,所以每執行一次GetMemory就會泄露一塊內存。)
如果非得要用指針參數去申請內存,那么應該改用“指向指針的指針”,見示例4-2
View Code6 實例4-2 用指向指針的指針申請動態內存 void GetMemory2(char **p, int num){
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意參數是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
?**p的內容是指針p的地址,&str是指針str的地址。
由于“指向指針的指針”這個概念不容易理解,我們可以用函數返回值來傳遞動態內存。這種方法更加簡單,見示例4-3
View Code 7 實例4-3? 用函數返回值來傳遞動態內存 char *GetMemory3(int num){
char *p = (char *)malloc(sizeof(char) * num); //返回堆中的地址
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
?GetMemory3的返回值類型是char類型的指針,所以return p 返回的是指針p的地址。(char類型的指針是說指針所指向的內容是char類型)
?用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把return語句用錯了。這里強調不要用return語句返回指向“棧內存”的指針,因為該內存在函數結束時自動消亡,見示例4-4
View Code 8 實例4-4? return語句返回指向“棧內存”的指針 char *GetString(void){
char p[] = "hello world"; //返回棧中的地址
return p; // 編譯器將提出警告
}
void Test4(void)
{
char *str = NULL;
str = GetString(); // str 的內容是垃圾
cout<< str << endl;
}
?char p[] 是在棧中分配內存。
用調試器逐步跟蹤Test4,發現執行str = GetString語句后str不再是NULL指針,但是str的內容不是“hello world”而是垃圾。因為char p[]在棧上分配空間,一旦函數結束,所分配的空間就會被釋放了。
如果把示例4-4改寫成示例4-5,會怎么樣?
View Code 9 實例4-5 return語句返回常量字符串 char *GetString2(void){
char *p = "hello world";
return p;
}
void Test5(void)
{
char *str = NULL;
str = GetString2();
cout<< str << endl;
}
函數Test5運行雖然不會出錯,但是函數GetString2的設計概念卻是錯誤的。因為GetString2內的“hello world”是常量字符串,位于靜態存儲區,它在程序生命期內恒定不變。無論什么時候調用GetString2,它返回的始終是同一個“只讀”的內存塊。
?5.char []與 char *的區別
以下面兩個變量為例:
char a[]="hello";
char *b="hello";
?(1)數組對應著一塊內存區域,而指針是指向一塊內存區域。數組的地址和容量在生命期里不會改變,只有數組的內容可以改變;而指針所指向的內存區域的大小可以隨時改變,而且當指針指向常量字符串時,它的內容是不可以被修改的,否則在運行時會報錯。
數組a需要在內存中占用6個字節的空間,這段內存區通過名字a來標識。
指針b則需要4個字節的空間來存放地址,這4個字節用名字b來標識,其中存放的地址可以指向幾乎任何地方,也可以哪里都不指向,即空指針。在這里,指針b指向某個連續的6字節的空間,即字符串"hello",該字符串存放在常量區,常量區的內容不能被修改。不能修改*b的值,比如b[2]='d'(將"hello"中第3個字符改為'd'),會導致程序崩潰。
(2)以a[2]和b[2]為例,二者都返回字符‘l’,但是編譯器產生的執行代碼卻不一樣。對于a[2],執行代碼是從a的位置開始,向后移動2兩個字節,然后取出其中的字符。對于b[2],執行代碼是從p的位置取出一個地址,在其上加2,然后取出對應內存中的字符。
(3)char *b = "hello";實際上先是在文字常量區分配了一塊內存放"hello",然后在棧上分配一地址給b并指向存放"hello"這塊地址,然后改變常量"hello"自然會崩潰,然而char a[] = "hello",實際上"hello"分配內存的地方在棧區。在以后的存取中,在棧上的數組比指針所指向的字符串快。
(4)下面代碼詳細注明了各個變量所在的內存區:
//main.cpp int a=0; //全局初始化區 char *p1; //全局未初始化區 main() {int b;棧char s[]="abc"; //棧char *p2; //棧char *p3="123456"; //123456\0在常量區,p3在棧上。static int c=0; //全局(靜態)初始化區p1 = (char*)malloc(10);p2 = (char*)malloc(20); //分配得來得10和20字節的區域就在堆區。strcpy(p1,"123456"); //123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。 }?
?
轉載于:https://www.cnblogs.com/fly1988happy/archive/2011/11/17/2251914.html
總結
以上是生活随笔為你收集整理的C++内存管理——指针数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 15 个最新的 CSS3 教程
- 下一篇: MVC2中Area的路由注册实现