關於結構體和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 這個單詞就主觀的認為是存放在全局數據區,那是不對的
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的大小。
//-----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,在類對象最開始的內存數據中。之后是類中的成員變量的內存數據。