C++函數調用內存分配機制


看到網上這篇博客對C++對象模型的內存分配機制至關透徹和簡顯易懂,新手常常會想,C++類中爲啥非虛成員函數貌似不佔地方啊,虛函數爲何要用個表,怎麼調用呢,這裏講解的還算很清晰,因此收藏分享下 html

原文連接:http://blog.csdn.net/hantang2009/article/details/6411738  ios

http://www.cnblogs.com/dolphin0520/archive/2011/04/04/2005061.html 程序員

 函數調用的內存分配機制 數組

1.同一個類的對象 框架

共享同一個成員函數的地址空間,而每一個對象有獨立的成員變量地址空間,能夠說成員函數是類擁有的,成員變量是對象擁有的 函數

2.非虛函數 spa

對於非虛函數的調用,編譯器只根據數據類型翻譯函數地址,判斷調用的合法性,由1可知,這些非虛函數的地址與其對象的內存地址無關(只與該類的成員函數的地址空間相關),故對於一個父類的對象指針,調用非虛函數,不論是給他賦父類對象的指針仍是子類對象的指針,他只會調用父類中的函數(只與數據類型(此爲類類型)相關,與對象無關) .net

3.虛函數 翻譯

虛擬函數的地址翻譯取決於對象的內存地址,而不取決於數據類型(編譯器對函數調用的合法性檢查取決於數據類型)。若是類定義了虛函數,該類及其派生類就要生成一張虛擬函數表,即vtable。而在類的對象地址空間中存儲一個該虛表的入口,佔4個字節,這個入口地址是在構造對象時由編譯器寫入的。因此,因爲對象的內存空間包含了虛表入口,編譯器可以由這個入口找到恰當的虛函數,這個函數的地址再也不由數據類型決定了。故對於一個父類的對象指針,調用虛擬函數,若是給他賦父類對象的指針,那麼他就調用父類中的函數,若是給他賦子類對象的指針,他就調用子類中的函數(取決於對象的內存地址) 指針

4.若是類包含虛擬成員函數,則將此類的析構函數也定義爲虛擬函數

由於派生類對象每每由基類的指針引用,若是使用new操做符在堆中構造派生類對象,並將其地址賦給基類指針,那麼最後要使用delete操做符刪除這個基類指針(釋放對象佔用的堆棧)。這時若是析構函數不是虛擬的,派生類的析構函數不會被調用,會產生內存泄露。

5.純虛擬函數

純虛擬函數沒有函數體,專爲派生類提供重載的形式。只要形象的將虛擬函數賦值爲0,即定義了純虛函數,例如void virtual XXXX(char* XXX) = 0;

定義了純虛函數的類稱爲抽象基類。抽象基類節省了內存空間,但不能用來實例化對象。其派生類必須重載全部的純虛函數,不然產生編譯錯誤。

抽象基類雖然不能實例化,爲派生類提供一個框架。抽象基類爲了派生類提供了虛擬函數的重載形式,能夠用抽象類的指針引用派生類的對象,這爲虛擬函數的應用準備了必要條件。

  

內存分配以及函數調用,返回值問題:

C++編譯器將計算機內存分爲代碼區和數據區,很顯然,代碼區就是存放程序代碼,而數據區則是存放程序編譯和執行過程出現的變量和常量。數據區又分爲靜態數據區、動態數據區,動態數據區包括堆區和棧區。如下是各個區的做用:

(1)代碼區:存放程序代碼;

(2)數據區:

a.靜態數據區在編譯器進行編譯的時候就爲該變量分配的內存,存放在這個區的數據在程序所有執行結束後系統自動釋放,生命週期貫穿於整個程序執行過程。

b.動態數據區:包括堆區和棧區

堆區:這部分存儲空間徹底由程序員本身負責管理,它的分配和釋放都由程序員本身負責。這個區是惟一一個能夠由程序員本身決定變量生存期的區間。能夠用malloc,new申請對內存,並經過freedelete釋放空間。若是程序員本身在堆區申請了空間,又忘記將這片內存釋放掉,就會形成內存泄露的問題,致使後面一直沒法訪問這片存儲區域。

棧區:存放函數的形式參數和局部變量,由編譯器分配和自動釋放,函數執行完後,局部變量和形參佔用的空間會自動被釋放。效率比較高,可是分配的容量頗有限。

注意:

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」是在棧上保存的,當用returnstr的值返回時,將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!

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,防止指針在後面不當心又被使用,形成沒法估計的後果。

相關文章
相關標籤/搜索