基於C++記憶體配置、函數調用與傳回值的深入分析

來源:互聯網
上載者:User

在談述函數調用和傳回值問題之前,先來看看C++中記憶體配置的問題。
C++編譯器將電腦記憶體分為代碼區和資料區,很顯然,代碼區就是存放程式碼,而資料區則是存放程式編譯和執行過程出現的變數和常量。資料區又分為待用資料區、動態資料區,動態資料區包括堆區和棧區。
以下是各個區的作用:
(1)代碼區:存放程式碼;
(2)資料區
a.待用資料區: 在編譯器進行編譯的時候就為該變數分配的記憶體,存放在這個區的資料在程式全部執行結束後系統自動釋放,生命週期貫穿於整個程式執行過程。
b.動態資料區:包括堆區和棧區
堆區:這部分儲存空間完全由程式員自己負責管理,它的分配和釋放都由程式員自己負責。這個區是唯一一個可以由程式員自己決定變數生存期的區間。可以用malloc,new申請對記憶體,並通過free和delete釋放空間。如果程式員自己在堆區申請了空間,又忘記將這片記憶體釋放掉,就會造成記憶體泄露的問題,導致後面一直無法訪問這片儲存地區。
棧區:存放函數的形式參數和局部變數,由編譯器分配和自動釋放,函數執行完後,局部變數和形參佔用的空間會自動被釋放。效率比較高,但是分配的容量很有限。
注意:
1)全域變數以及靜態變數存放在待用資料區;
2)注意常量的存放地區,通常情況下,常量存放在程式區(程式區是唯讀,因此任何修改常量的行為都是非法的),而不是資料區。有的系統,也將部分常量分配到待用資料區,比如字串常量(有的系統也將其分配在程式區)。但是要記住一點,常量所在的記憶體空間都是受系統保護的,不能修改。對常量空間的修改將造成訪問記憶體出錯,一般系統都會提示。常量的生命週期一直到程式執行結束為止。
在弄懂記憶體配置的問題過後,來看看函數調用的過程:
執行某個函數時,如果有參數,則在棧上為形式參數分配空間(如果是參考型別的參數則類外),繼續進入到函數體內部,如果遇到變數,則按情況為變數在不同的儲存地區分配空間(如果是static類型的變數,則是在進行編譯的過程中已經就分配了空間),函數內的語句執行完後,如果函數沒有傳回值,則直接返回調用該函數的地方(即執行遠點),如果存在傳回值,則先將傳回值進行拷貝傳回,再返回執行遠點,函數全部執行完畢後,進行退棧操作,將剛才函數內部在棧上申請的記憶體空間釋放掉。
下面通過幾個例子來談談記憶體配置和函數傳回值的問題:
記憶體配置的問題:
複製代碼 代碼如下:int a=1; a在棧區
char s[]="123"; s在棧區,“123”在棧區,其值可以被修改
char *s="123"; s在棧區,“123”在常量區,其值不能被修改
int *p=new int; p在棧區,申請的空間在堆區(p指向的地區)
int *p=(int *)malloc(sizeof(int)); p在棧區,p指向的空間在堆區
static int b=0; b在靜態區

1.test1 複製代碼 代碼如下:#include<iostream>
using namespace std;
void test(int *p)
{
int b=2;
p=&b;
cout<<p<<endl;
}
int main(void)
{
int a=10;
int *p=&a;
cout<<p<<endl;
test(p);
cout<<p<<endl;
return 0;
}

第一行輸出和第三行輸出的結果相同,而第一行、第三行與第二行輸出的結果不同。從這裡可以看出,當指標作為參數進行傳遞時傳遞的也只是一個值,只不過該值只一個地址,因此對於形參的改變並不影響實參。
2.test2
複製代碼 代碼如下:#include<iostream>
using namespace std;
char* test(void)
{
char str[]="hello world!";
return str;
}
int main(void)
{
char *p;
p=test();
cout<<p<<endl;
return 0;
}

輸出結果可能是hello world!,也可能是亂麻。
出現這種情況的原因在於:在test函數內部聲明的str數組以及它的值"hello world”是在棧上儲存的,當用return將str的值返回時,將str的值拷貝一份傳回,當test函數執行結束後,會自動釋放棧上的空間,即存放hello world的單元可能被重新寫入資料,因此雖然main函數中的指標p是指向存放hello world的單元,但是無法保證test函數執行完後該儲存單元裡面存放的還是hello world,所以列印出的結果有時候是hello world,有時候是亂麻。
3.test3
複製代碼 代碼如下:#include<iostream>
using namespace std;
int test(void)
{
int a=1;
return a;
}
int main(void)
{
int b;
b=test();
cout<<b<<endl;
return 0;
}

輸出結果為 1
有人會問為什麼這裡傳回來的值可以正確列印出來,不是棧會被重新整理內容嗎?是的,確實,在test函數執行完後,存放a值的單元是可能會被重寫,但是在函數執行return時,會建立一個int型的零時變數,將a的值複製拷貝給該零時變數,因此返回後能夠得到正確的值,即使存放a值的單元被重寫資料,但是不會受到影響。
4.test4
複製代碼 代碼如下:#include<iostream>
using namespace std;
char* test(void)
{
char *p="hello world!";
return p;
}
int main(void)
{
char *str;
str=test();
cout<<str<<endl;
return 0;
}

執行結果是 hello world!
同樣返回的是指標,為什麼這裡會正確地列印出hello world1?這是因為char *p="hello world!",指標p是存放在棧上的,但是"hello world!”是一個常量字串,因此存放在常量區,而常量區的變數的生存期與整個程式執行的生命期是一樣的,因此在test函數執行完後,str指向存放“hello world!”的單元,並且該單元裡的內容在程式沒有執行完是不會被修改的,因此可以正確輸出結果。
5.test5
複製代碼 代碼如下:#include<iostream>
using namespace std;
char* test(void)
{
char *p=(char *)malloc(sizeof(char)*100);
strcpy(p,"hello world");
return p;
}
int main(void)
{
char *str;
str=test();
cout<<str<<endl;
return 0;
}

運行結果 hello world
這種情況下同樣可以輸出正確的結果,是因為是用malloc在堆上申請的空間,這部分空間是由程式員自己管理的,如果程式員沒有手動釋放堆區的空間,那麼儲存單元裡的內容是不會被重寫的,因此可以正確輸出結果。
6.test6
複製代碼 代碼如下:#include<iostream>
using namespace std;
void test(void)
{
char *p=(char *)malloc(sizeof(char)*100);
strcpy(p,"hello world");
free(p);
if(p==NULL)
{
cout<<"NULL"<<endl;
}
}
int main(void)
{
test();
return 0;
}

沒有輸出
在這裡注意了,free()釋放的是指標指向的記憶體!注意!釋放的是記憶體,不是指標!這點非常非常重 要!指標是一個變數,只有程式結束時才被銷毀。釋放了記憶體空間後,原來指向這塊空間的指標還是存在!只不過現在指標指向的內容的垃圾,是未定義的,所以說是垃圾。因此,釋放記憶體後應把把指標指向NULL,防止指標在後面不小心又被使用,造成無法估計的後果。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.