1. 程式人生 > >C++類的記憶體地址存放問題

C++類的記憶體地址存放問題

瞭解C++類地址的存放和分配等問題,能幫助我們更深入、更清晰瞭解類的組成及其使用。

自己目前不是很清楚,先收集一些網上資料,而後再慢慢補充增加的瞭解...

//-------------------------------------------網路收集之-------------------------------------

關於結構體和C++類的記憶體地址問題

今天終於有時間寫點東西了~ 太爽了  *_*  很多人都知道C++類是由結構體發展得來的,所以他們的成員變數(C語言的結構體只有成員變數)的記憶體分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結構體也也就沒問題啦。 類分為成員變數和成員函式,我們先來討論成員變數。 一個類物件的地址就是類所包含的這一片記憶體空間的首地址,這個首地址也就對應具體某一個成員變數的地址。(在定義類物件的同時這些成員變數也就被定義了)我們來以一段程式碼說明問題: //類的定義class K{

public:
 K(){k = 12;}
 ~K(){}
 int k;
}; //類的使用//... K kTemp;
 printf("%d--%d\n",&kTemp,&kTemp.k);
 printf("%d--%d\n",sizeof(K),sizeof(kTemp.k));
 int *i = (int*)(&kTemp);
 int w = *i;
 printf("%d\n",w); 執行上面的程式碼,結果如下:1310588--1310588

4--4
12
很明顯,類的記憶體大小和其唯一的成員變數的記憶體大小是一致的。記憶體地址也是一致的。他們甚至可以相互轉換。換成結構體結果也是一樣。網友可以自己執行上面程式碼來進行確認。 這個時候,可能有人會提出疑問了。那麼成員函式又如何?上面得程式碼就好像類沒有任何成員函式一樣,根本說明不了問題。 呵呵,所有的函式都是存放在程式碼區的,不管是全域性函式,還是成員函式。要是成員函式佔用類的物件空間,那麼將是多麼可怕的事情:定義一次類物件就有成員函式佔用一段空間。 我們再來補充一下靜態成員函式的存放問題吧:靜態成員函式與一般成員函式的唯一區別就是沒有this指標,因此不能訪問非靜態資料成員,就像我前面提到的,所有函式都存放在程式碼區,靜態函式也不例外。所有有人一看到 static 這個單詞就主觀的認為是存放在全域性資料區,那是不對的。(當然正在看我部落格的網友應該不至於犯這樣的問題,但是林子大了什麼鳥都有嘛,我在這裡多寫兩句,希望各位網友不要嫌我囉嗦哦) 原創,請轉帖者附上下面網址

http://blog.chinaunix.net/u3/102921/showart_2123414.html

 

 

----------第二篇------------

c++是一種面向物件的程式語言,它向下保持了對c的相容,同時也允許程式設計師能夠自由的操控記憶體,雖然會帶來一些問題,但這不是我們要探討的問題,略過不表。類是對某種物件的定義,包含變數和方法,也可以理解為現實生活中一類具有共同特徵的事務的抽象,他是面嚮物件語言的基礎。所以類是不佔有記憶體的,可是如果類生成例項那麼將會在記憶體中分配一塊記憶體來儲存這個類。

    類的例項在記憶體中是如何分配記憶體的,有什麼需要我們注意的,下面將慢慢到來。

    比如下面一個類:

    class A

    {};

    從形式上看,它似乎什麼有沒有,事實上它不止隱含了一個建構函式和一個解構函式,還有一些操作符過載函式,比如“=”。如果類A被例項話,如A a;在記憶體會佔據多大的空間呢?有人可能會說4,也有人會說0,還有人會說1,說1的就對了,為什麼會是1呢?原因有很多,如果我們定義一個數組A b[10];如果上面是0,這樣的局面將會很尷尬,所以A這樣一個空類,編譯器會給它一個位元組來填充。  

    增加一個變數,(位元組對齊預設都是4)

    class  A

   {

     public:

          int i;

   }

  

   類A的例項將佔據4個位元組的記憶體,sizeof(A) = 4

   變數i 的初值被編譯器指定位0xcdcdcdcd。

    再增加一個變數,

   class A

   {

      public:

      int  i;

      int  l;

   }

   此時按照變數生命的先後順序,i被放在低地址上,l緊隨其後。

   例項佔用8個位元組,sizeof(A) = 4*2 = 8

   如果累裡面含有函式:

  class A

 {

     public:

      int i;

      int l;

      int add(int x,int y){return (x+y);}

 };

 有些人可能會說類的大小是12,事實上sizeof(A) = 8;

 為什麼會這樣,這是因為sizeof訪問的程式的資料段,而函式地址則被儲存在程式碼段內,所以最後的結果是8.

 再看下面這個情況

 class A

 {

      public:

         int i;

         int l;

         static int s;

         int add(int x,int y){return (x+y)};

 };

此時sizeof(A)大小仍為8,這裡留給讀者去思考為什麼?(^-^)。

當類裡面含有虛擬函式時,情況會如何呢?

 class A

 {

      public:

         int i;

         int l;

         static int s;

         virtual void Say(){};

         int add(int x,int y){return (x+y)};

 };

 因為含有虛擬函式,所以類裡面將含有一個虛指標vptr,指向該類的虛表vtbl,一個指標佔用四位元組的地址,所以sizeof(A) = 12

 虛指標放在類例項地址的最低位置,

 比如 A *a = new A;

 我們可以這樣給變數i賦值

int *p = (int *)a;
 p++;
 *p = 1;//把i的值賦為1.

如果類作為派生類,記憶體將如何分配呢?

這種情況雖然有些複雜,但並不是說不好理解。

他有多少個父類每個父類的大小加起來在加上自身就是sizeof的大小。

轉自:http://blog.csdn.net/alexwei2009/archive/2011/01/22/6157926.aspx

 

//-----C++類物件記憶體結構[講得很好] -------

首先介紹一下C++中有繼承關係的類物件記憶體的佈局: 
在C++中,如果類中有虛擬函式,那麼它就會有一個虛擬函式表的指標__vfptr,在類物件最開始的記憶體資料中。之後是類中的成員變數的記憶體資料。 
對於子類,最開始的記憶體資料記錄著父類物件的拷貝(包括父類虛擬函式表指標和成員變數)。 之後是子類自己的成員變數資料。 
對於子類的子類,也是同樣的原理。但是無論繼承了多少個子類,物件中始終只有一個虛擬函式表指標。 
 
 
 
為了探討C++類物件的記憶體佈局,先來寫幾個類和函式 
首先寫一個基類: 
class Base 

public: 
virtual void f() { cout << "Base::f" << endl; } 
virtual void g() { cout << "Base::g" << endl; } 
virtual void h() { cout << "Base::h" << endl; } 
int base; 
protected: 
private: 
}; 
然後,我們多種不同的繼承情況來研究子類的記憶體物件結構。 
1. 無虛擬函式集繼承 
 
//子類1,無虛擬函式過載 
class Child1 : public Base 

public: 
virtual void f1() { cout << "Child1::f1" << endl; } 
virtual void g1() { cout << "Child1::g1" << endl; } 
virtual void h1() { cout << "Child1::h1" << endl; } 
int child1; 
protected: 
private: 
}; 
這個子類Child1沒有繼承任何一個基類的虛擬函式,因此它的虛擬函式表如下圖: 
 
 
我們可以看出,子類的虛擬函式表中,先存放基類的虛擬函式,在存放子類自己的虛擬函式。 
 
2. 有一個虛擬函式繼承 
//子類2,有1個虛擬函式過載 
class Child2 : public Base 

public: 
virtual void f() { cout << "Child2::f" << endl; } 
virtual void g2() { cout << "Child2::g2" << endl; } 
virtual void h2() { cout << "Child2::h2" << endl; } 
int child2; 
protected: 
private: 
}; 
 
當子類過載了父類的虛擬函式,則編譯器會將子類虛擬函式表中對應的父類的虛擬函式替換成子類的函式。 
3. 全部虛擬函式都繼承 
//子類3,全部虛擬函式過載 
class Child3 : public Base 

public: 
virtual void f() { cout << "Child3::f" << endl; } 
virtual void g() { cout << "Child3::g" << endl; } 
virtual void h() { cout << "Child3::h" << endl; } 
protected: 
int x; 
private: 
}; 
 
 
 
4. 多重繼承 
多重繼承,即類有多個父類,這種情況下的子類的記憶體結構和單一繼承有所不同。 
 
我們可以看到,當子類繼承了多個父類,那麼子類的記憶體結構是這樣的: 
子類的記憶體中,順序 
 
5. 菱形繼承 
 
 
6. 單一虛擬繼承 
 
 
虛擬繼承的子類的記憶體結構,和普通繼承完全不同。虛擬繼承的子類,有單獨的虛擬函式表, 另外也單獨儲存一份父類的虛擬函式表,兩部分之間用一個四個位元組的0x00000000來作為分界。子類的記憶體中,首先是自己的虛擬函式表,然後是子類的資料成員,然後是0x0,之後就是父類的虛擬函式表,之後是父類的資料成員。 
如果子類沒有自己的虛擬函式,那麼子類就不會有虛擬函式表,但是子類資料和父類資料之間,還是需要0x0來間隔。 
因此,在虛擬繼承中,子類和父類的資料,是完全間隔的,先存放子類自己的虛擬函式表和資料,中間以0x分界,最後儲存父類的虛擬函式和資料。如果子類過載了父類的虛擬函式,那麼則將子類記憶體中父類虛擬函式表的相應函式替換。 
 
7. 菱形虛擬繼承 
 
結論: 
(1) 對於基類,如果有虛擬函式,那麼先存放虛擬函式表指標,然後存放自己的資料成員;如果沒有虛擬函式,那麼直接存放資料成員。 
(2) 對於單一繼承的類物件,先存放父類的資料拷貝(包括虛擬函式表指標),然後是本類的資料。 
(3) 虛擬函式表中,先存放父類的虛擬函式,再存放子類的虛擬函式 
(4) 如果過載了父類的某些虛擬函式,那麼新的虛擬函式將虛擬函式表中父類的這些虛擬函式覆蓋。 
(5) 對於多重繼承,先存放第一個父類的資料拷貝,在存放第二個父類的資料拷貝,一次類推,最後存放自己的資料成員。其中每一個父類拷貝都包含一個虛擬函式表指標。如果子類過載了某個父類的某個虛擬函式,那麼該將該父類虛擬函式表的函式覆蓋。另外,子類自己的虛擬函式,儲存於第一個父類的虛擬函式表後邊部分。 
(6) 當物件的虛擬函式被呼叫是,編譯器去查詢物件的虛擬函式表,找到該函式,然後呼叫。

來源:http://blog.csdn.net/jimmy54/archive/2010/03/26/5418766.aspx

//-------------------------------------更新中的自己的瞭解...----------------------------------------------

....20/6/2011....

在C++中,如果類中有虛擬函式,那麼它就會有一個虛擬函式表的指標__vfptr,在類物件最開始的記憶體資料中。之後是類中的成員變數的記憶體資料。