C++类的内存地址存放问题
了解C++類地址的存放和分配等問題,能幫助我們更深入、更清晰了解類的組成及其使用。
自己目前不是很清楚,先收集一些網(wǎng)上資料,而后再慢慢補(bǔ)充增加的了解...
//-------------------------------------------網(wǎng)絡(luò)收集之-------------------------------------
關(guān)于結(jié)構(gòu)體和C++類的內(nèi)存地址問題
今天終于有時(shí)間寫點(diǎn)東西了~ 太爽了? *_*? 很多人都知道C++類是由結(jié)構(gòu)體發(fā)展得來的,所以他們的成員變量(C語言的結(jié)構(gòu)體只有成員變量)的內(nèi)存分配機(jī)制是一樣的。下面我們以類來說明問題,如果類的問題通了,結(jié)構(gòu)體也也就沒問題啦。?類分為成員變量和成員函數(shù),我們先來討論成員變量。?一個(gè)類對(duì)象的地址就是類所包含的這一片內(nèi)存空間的首地址,這個(gè)首地址也就對(duì)應(yīng)具體某一個(gè)成員變量的地址。(在定義類對(duì)象的同時(shí)這些成員變量也就被定義了)我們來以一段代碼說明問題:?//類的定義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);?運(yùn)行上面的代碼,結(jié)果如下:1310588--1310588
4--4
12
很明顯,類的內(nèi)存大小和其唯一的成員變量的內(nèi)存大小是一致的。內(nèi)存地址也是一致的。他們甚至可以相互轉(zhuǎn)換。換成結(jié)構(gòu)體結(jié)果也是一樣。網(wǎng)友可以自己運(yùn)行上面代碼來進(jìn)行確認(rèn)。?這個(gè)時(shí)候,可能有人會(huì)提出疑問了。那么成員函數(shù)又如何?上面得代碼就好像類沒有任何成員函數(shù)一樣,根本說明不了問題。?呵呵,所有的函數(shù)都是存放在代碼區(qū)的,不管是全局函數(shù),還是成員函數(shù)。要是成員函數(shù)占用類的對(duì)象空間,那么將是多么可怕的事情:定義一次類對(duì)象就有成員函數(shù)占用一段空間。?我們?cè)賮硌a(bǔ)充一下靜態(tài)成員函數(shù)的存放問題吧:靜態(tài)成員函數(shù)與一般成員函數(shù)的唯一區(qū)別就是沒有this指針,因此不能訪問非靜態(tài)數(shù)據(jù)成員,就像我前面提到的,所有函數(shù)都存放在代碼區(qū),靜態(tài)函數(shù)也不例外。所有有人一看到 static 這個(gè)單詞就主觀的認(rèn)為是存放在全局?jǐn)?shù)據(jù)區(qū),那是不對(duì)的。(當(dāng)然正在看我博客的網(wǎng)友應(yīng)該不至于犯這樣的問題,但是林子大了什么鳥都有嘛,我在這里多寫兩句,希望各位網(wǎng)友不要嫌我啰嗦哦)?原創(chuàng),請(qǐng)轉(zhuǎn)帖者附上下面網(wǎng)址
鏈接地址
?
?
----------第二篇------------
c++是一種面向?qū)ο蟮木幊陶Z言,它向下保持了對(duì)c的兼容,同時(shí)也允許程序員能夠自由的操控內(nèi)存,雖然會(huì)帶來一些問題,但這不是我們要探討的問題,略過不表。類是對(duì)某種對(duì)象的定義,包含變量和方法,也可以理解為現(xiàn)實(shí)生活中一類具有共同特征的事務(wù)的抽象,他是面向?qū)ο笳Z言的基礎(chǔ)。所以類是不占有內(nèi)存的,可是如果類生成實(shí)例那么將會(huì)在內(nèi)存中分配一塊內(nèi)存來存儲(chǔ)這個(gè)類。
??? 類的實(shí)例在內(nèi)存中是如何分配內(nèi)存的,有什么需要我們注意的,下面將慢慢到來。
??? 比如下面一個(gè)類:
??? class A
??? {};
??? 從形式上看,它似乎什么有沒有,事實(shí)上它不止隱含了一個(gè)構(gòu)造函數(shù)和一個(gè)析構(gòu)函數(shù),還有一些操作符重載函數(shù),比如“=”。如果類A被實(shí)例話,如A a;在內(nèi)存會(huì)占據(jù)多大的空間呢?有人可能會(huì)說4,也有人會(huì)說0,還有人會(huì)說1,說1的就對(duì)了,為什么會(huì)是1呢?原因有很多,如果我們定義一個(gè)數(shù)組A b[10];如果上面是0,這樣的局面將會(huì)很尷尬,所以A這樣一個(gè)空類,編譯器會(huì)給它一個(gè)字節(jié)來填充。??
??? 增加一個(gè)變量,(字節(jié)對(duì)齊默認(rèn)都是4)
??? class? A
?? {
???? public:
????????? int i;
?? }
??
?? 類A的實(shí)例將占據(jù)4個(gè)字節(jié)的內(nèi)存,sizeof(A) = 4
?? 變量i 的初值被編譯器指定位0xcdcdcdcd。
??? 再增加一個(gè)變量,
?? class A
?? {
?????? public:
????? int? i;
????? int? l;
?? }
?? 此時(shí)按照變量生命的先后順序,i被放在低地址上,l緊隨其后。
?? 實(shí)例占用8個(gè)字節(jié),sizeof(A) = 4*2 = 8
?? 如果累里面含有函數(shù):
? class A
? {
???? public:
????? int i;
????? int l;
?????? int add(int x,int y){return (x+y);}
? };
? 有些人可能會(huì)說類的大小是12,事實(shí)上sizeof(A) = 8;
? 為什么會(huì)這樣,這是因?yàn)閟izeof訪問的程序的數(shù)據(jù)段,而函數(shù)地址則被保存在代碼段內(nèi),所以最后的結(jié)果是8.
? 再看下面這個(gè)情況
? class A
? {
????? public:
???????? int i;
???????? int l;
???????? static int s;
??????? int add(int x,int y){return (x+y)};
? };
此時(shí)sizeof(A)大小仍為8,這里留給讀者去思考為什么?(^-^)。
當(dāng)類里面含有虛函數(shù)時(shí),情況會(huì)如何呢?
? class A
? {
????? public:
???????? int i;
???????? int l;
???????? static int s;
???????? virtual void Say(){};
???????? int add(int x,int y){return (x+y)};
? };
? 因?yàn)楹刑摵瘮?shù),所以類里面將含有一個(gè)虛指針vptr,指向該類的虛表vtbl,一個(gè)指針占用四字節(jié)的地址,所以sizeof(A) = 12
? 虛指針放在類實(shí)例地址的最低位置,
? 比如 A *a = new A;
? 我們可以這樣給變量i賦值
int *p = (int *)a;
? p++;
? *p = 1;//把i的值賦為1.
如果類作為派生類,內(nèi)存將如何分配呢?
這種情況雖然有些復(fù)雜,但并不是說不好理解。
他有多少個(gè)父類每個(gè)父類的大小加起來在加上自身就是sizeof的大小。
轉(zhuǎn)自:鏈接地址
?
//-----C++類對(duì)象內(nèi)存結(jié)構(gòu)[講得很好] -------
首先介紹一下C++中有繼承關(guān)系的類對(duì)象內(nèi)存的布局:?
在C++中,如果類中有虛函數(shù),那么它就會(huì)有一個(gè)虛函數(shù)表的指針__vfptr,在類對(duì)象最開始的內(nèi)存數(shù)據(jù)中。之后是類中的成員變量的內(nèi)存數(shù)據(jù)。?
對(duì)于子類,最開始的內(nèi)存數(shù)據(jù)記錄著父類對(duì)象的拷貝(包括父類虛函數(shù)表指針和成員變量)。 之后是子類自己的成員變量數(shù)據(jù)。?
對(duì)于子類的子類,也是同樣的原理。但是無論繼承了多少個(gè)子類,對(duì)象中始終只有一個(gè)虛函數(shù)表指針。?
?
?
?
為了探討C++類對(duì)象的內(nèi)存布局,先來寫幾個(gè)類和函數(shù)?
首先寫一個(gè)基類:?
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:?
};?
然后,我們多種不同的繼承情況來研究子類的內(nèi)存對(duì)象結(jié)構(gòu)。?
1. 無虛函數(shù)集繼承?
?
//子類1,無虛函數(shù)重載?
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:?
};?
這個(gè)子類Child1沒有繼承任何一個(gè)基類的虛函數(shù),因此它的虛函數(shù)表如下圖:?
?
?
我們可以看出,子類的虛函數(shù)表中,先存放基類的虛函數(shù),在存放子類自己的虛函數(shù)。?
?
2. 有一個(gè)虛函數(shù)繼承?
//子類2,有1個(gè)虛函數(shù)重載?
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:?
};?
?
當(dāng)子類重載了父類的虛函數(shù),則編譯器會(huì)將子類虛函數(shù)表中對(duì)應(yīng)的父類的虛函數(shù)替換成子類的函數(shù)。?
3. 全部虛函數(shù)都繼承?
//子類3,全部虛函數(shù)重載?
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. 多重繼承?
多重繼承,即類有多個(gè)父類,這種情況下的子類的內(nèi)存結(jié)構(gòu)和單一繼承有所不同。?
?
我們可以看到,當(dāng)子類繼承了多個(gè)父類,那么子類的內(nèi)存結(jié)構(gòu)是這樣的:?
子類的內(nèi)存中,順序?
?
5. 菱形繼承?
?
?
6. 單一虛擬繼承?
?
?
虛擬繼承的子類的內(nèi)存結(jié)構(gòu),和普通繼承完全不同。虛擬繼承的子類,有單獨(dú)的虛函數(shù)表, 另外也單獨(dú)保存一份父類的虛函數(shù)表,兩部分之間用一個(gè)四個(gè)字節(jié)的0x00000000來作為分界。子類的內(nèi)存中,首先是自己的虛函數(shù)表,然后是子類的數(shù)據(jù)成員,然后是0x0,之后就是父類的虛函數(shù)表,之后是父類的數(shù)據(jù)成員。?
如果子類沒有自己的虛函數(shù),那么子類就不會(huì)有虛函數(shù)表,但是子類數(shù)據(jù)和父類數(shù)據(jù)之間,還是需要0x0來間隔。?
因此,在虛擬繼承中,子類和父類的數(shù)據(jù),是完全間隔的,先存放子類自己的虛函數(shù)表和數(shù)據(jù),中間以0x分界,最后保存父類的虛函數(shù)和數(shù)據(jù)。如果子類重載了父類的虛函數(shù),那么則將子類內(nèi)存中父類虛函數(shù)表的相應(yīng)函數(shù)替換。?
?
7. 菱形虛擬繼承?
?
結(jié)論:?
(1) 對(duì)于基類,如果有虛函數(shù),那么先存放虛函數(shù)表指針,然后存放自己的數(shù)據(jù)成員;如果沒有虛函數(shù),那么直接存放數(shù)據(jù)成員。?
(2) 對(duì)于單一繼承的類對(duì)象,先存放父類的數(shù)據(jù)拷貝(包括虛函數(shù)表指針),然后是本類的數(shù)據(jù)。?
(3) 虛函數(shù)表中,先存放父類的虛函數(shù),再存放子類的虛函數(shù)?
(4) 如果重載了父類的某些虛函數(shù),那么新的虛函數(shù)將虛函數(shù)表中父類的這些虛函數(shù)覆蓋。?
(5) 對(duì)于多重繼承,先存放第一個(gè)父類的數(shù)據(jù)拷貝,在存放第二個(gè)父類的數(shù)據(jù)拷貝,一次類推,最后存放自己的數(shù)據(jù)成員。其中每一個(gè)父類拷貝都包含一個(gè)虛函數(shù)表指針。如果子類重載了某個(gè)父類的某個(gè)虛函數(shù),那么該將該父類虛函數(shù)表的函數(shù)覆蓋。另外,子類自己的虛函數(shù),存儲(chǔ)于第一個(gè)父類的虛函數(shù)表后邊部分。?
(6) 當(dāng)對(duì)象的虛函數(shù)被調(diào)用是,編譯器去查詢對(duì)象的虛函數(shù)表,找到該函數(shù),然后調(diào)用。
來源:鏈接地址
//-------------------------------------更新中的自己的了解...----------------------------------------------
....20/6/2011....
在C++中,如果類中有虛函數(shù),那么它就會(huì)有一個(gè)虛函數(shù)表的指針__vfptr,在類對(duì)象最開始的內(nèi)存數(shù)據(jù)中。之后是類中的成員變量的內(nèi)存數(shù)據(jù)。
總結(jié)
以上是生活随笔為你收集整理的C++类的内存地址存放问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 指令和数据都用二进制代码存放在内存中,从
- 下一篇: 学习中的十七条建议