1. 程式人生 > >C++成員函式在記憶體中的儲存方式

C++成員函式在記憶體中的儲存方式

        用類去定義物件時,系統會為每一個物件分配儲存空間。如果一個類包括了資料和函式,要分別為資料和函式的程式碼分配儲存空間。按理說,如果用同一個類定義了10個物件,那麼就需要分別為10個物件的資料和函式程式碼分配儲存單元,如下圖所示。


        能否只用一段空間來存放這個共同的函式程式碼段,在呼叫各物件的函式時,都去呼叫這個公用的函式程式碼。如下圖所示。


        顯然,這樣做會大大節約儲存空間。C++編譯系統正是這樣做的,因此每個物件所佔用的儲存空間只是該物件的資料部分(虛擬函式指標和虛基類指標也屬於資料部分)所佔用的儲存空間,而不包括函式程式碼所佔用的儲存空間

        看如下測試程式碼:
class D  
{  
public:  
    void printA()  
    {  
        cout<<"printA"<<endl;  
    }  
    virtual void printB()  
    {  
        cout<<"printB"<<endl;  
    }  
};  
int main(void)
{
	D *d=NULL;
	d->printA();
	d->printB();
}

        問題:以上程式碼的輸出結果是什麼?

        C++程式的記憶體格局通常分為四個區:全域性資料區(data area),程式碼區(code area),棧區(stack area),堆區(heap area)(即自由儲存區)

。全域性資料區存放全域性變數,靜態資料和常量;所有類成員函式和非成員函式程式碼存放在程式碼區;為執行函式而分配的區域性變數、函式引數、返回資料、返回地址等存放在棧區;餘下的空間都被稱為堆區。根據這個解釋,我們可以得知在類的定義時,類成員函式是被放在程式碼區,而類的靜態成員變數在類定義時就已經在全域性資料區分配了記憶體,因而它是屬於類的。對於非靜態成員變數,我們是在類的例項化過程中(構造物件)才在棧區或者堆區為其分配記憶體,是為每個物件生成一個拷貝,所以它是屬於物件的。

        應當說明,常說的“某某物件的成員函式”,是從邏輯的角度而言的,而成員函式的儲存方式,是從物理的角度而言的,二者是不矛盾的。

        下面我們再來討論下類的靜態成員函式和非靜態成員函式的區別:靜態成員函式和非靜態成員函式都是在類的定義時放在記憶體的程式碼區的,因而可以說它們都是屬於類的,但是類為什麼只能直接呼叫靜態類成員函式,而非靜態類成員函式(即使函式沒有引數)只有類物件才能呼叫呢?原因是類的非靜態類成員函式其實都內含了一個指向類物件的指標型引數(即this指標),因而只有類物件才能呼叫(此時this指標有實值)

        回答開頭的問題,答案是輸出“printA”後,程式崩潰。類中包括成員變數和成員函式。new出來的只是成員變數,成員函式始終存在,所以如果成員函式未使用任何成員變數的話,不管是不是static的,都能正常工作。需要注意的是,雖然呼叫不同物件的成員函式時都是執行同一段函式程式碼,但是執行結果一般是不相同的。不同的物件使用的是同一個函式程式碼段,它怎麼能夠分別對不同物件中的資料進行操作呢?原來C++為此專門設立了一個名為this的指標,用來指向不同的物件。

        需要說明,不論成員函式在類內定義還是在類外定義,成員函式的程式碼段都用同一種方式儲存。不要將成員函式的這種儲存方式和inline(內聯)函式的概念混淆。不要誤以為用inline宣告(或預設為inline)的成員函式,其程式碼段佔用物件的儲存空間,而不用inline宣告的成員函式,其程式碼段不佔用物件的儲存空間。不論是否用inline宣告(或預設為inline),成員函式的程式碼段都不佔用物件的儲存空間。用inline宣告的作用是在呼叫該函式時,將函式的程式碼段複製插人到函式呼叫點,而若不用inline宣告,在呼叫該函式時,流程轉去函式程式碼段的入口地址,在執行完該函式程式碼段後,流程返回函式呼叫點。inline與成員函式是否佔用物件的儲存空間無關,它們不屬於同一個問題,不應搞混。