c++ 內存分析

    在談述函數調用和返回值問題之前,先來看看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,防止指針在後面不小心又被使用,造成無法估計的後果。

作者:海子
         
本博客中未標明轉載的文章歸作者海子和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章