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與成員函數是否占用對象的存儲空間無關,它們不屬於同一個問題,不應搞混。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM