c++ 對象的內存布局
對象的影響因素
簡而言之,一個類可能會有如下的影響因素:
1)成員變量
2)虛函數(產生虛函數表)
3)單一繼承(只繼承於一個類)
4)多重繼承(繼承多個類)
5)重復繼承(繼承的多個父類中其父類有相同的超類)
6)虛擬繼承(使用virtual方式繼承,為了保證繼承後父類的內存布局只會存在一份)
上述的東西通常是C++這門語言在語義方面對對象內部的影響因素,當然,還會有編譯器的影響(比如優化),還有字節對齊的影響。在這裡我們都不討論,我們只討論C++語言上的影響。
本篇文章著重討論下述幾個情況下的C++對象的內存布局情況。
1)單一的一般繼承(帶成員變量、虛函數、虛函數覆蓋)
2)單一的虛擬繼承(帶成員變量、虛函數、虛函數覆蓋)
3)多重繼承(帶成員變量、虛函數、虛函數覆蓋)
4)重復多重繼承(帶成員變量、虛函數、虛函數覆蓋)
5)鑽石型的虛擬多重繼承(帶成員變量、虛函數、虛函數覆蓋)
(1)單一的一般繼承
下面,我們假設有如下所示的一個繼承關系:
請注意,在這個繼承關系中,父類,子類,孫子類都有自己的一個成員變量。而了類覆蓋了父類的f()方法,孫子類覆蓋了子類的g_child()及其超類的f()。
可見以下幾個方面:
1)虛函數表在最前面的位置。
2)成員變量根據其繼承和聲明順序依次放在後面。
3)在單一的繼承中,被override的虛函數在虛函數表中得到了更新。(Child的f覆蓋Parent的f函數,然後GrandChild的f覆蓋Child的f;GrandChild的g_child覆蓋Child的g_child)
代碼驗證如下
/*
version: 1.0
author: hellogiser
blog: http://www.cnblogs.com/hellogiser
date: 2014/9/29
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
class Parent
{
public:
int iparent;
Parent (): iparent (10) {}
virtual void f()
{
cout << " Parent::f()" << endl;
}
virtual void g()
{
cout << " Parent::g()" << endl;
}
virtual void h()
{
cout << " Parent::h()" << endl;
}
};
class Child : public Parent
{
public:
int ichild;
Child(): ichild(100) {}
virtual void f()
{
cout << "Child::f()" << endl;
}
virtual void g_child()
{
cout << "Child::g_child()" << endl;
}
virtual void h_child()
{
cout << "Child::h_child()" << endl;
}
};
class GrandChild : public Child
{
public:
int igrandchild;
GrandChild(): igrandchild(1000) {}
virtual void f()
{
cout << "GrandChild::f()" << endl;
}
virtual void g_child()
{
cout << "GrandChild::g_child()" << endl;
}
virtual void h_grandchild()
{
cout << "GrandChild::h_grandchild()" << endl;
}
};
typedef void(*Fun)(void);
/*
vptr ---> Parent::f(),Parent::g(),Parent::h()
iparent
*/
void test_parent()
{
Parent obj;
Fun pFun;
int **pVtab = (int **)(&obj);
int n = 3;
cout << "[0] Parent::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[0][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
cout << "============================================" << endl;
}
/*
vptr ---> Child::f(),Parent::g(),Parent::h(),Child::g_child(),Child::h_child()
iparent
ichild
*/
void test_child()
{
Child obj;
Fun pFun;
int **pVtab = (int **)(&obj);
int n = 5;
cout << "[0] Child::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[0][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
//cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
cout << "============================================" << endl;
}
/*
vptr ---> GrandChild::f(),Parent::g(),Parent::h(),GrandChild::g_child(),Child::h_child()
iparent
ichild
igrandchild
*/
void test_grandchild()
{
GrandChild obj;
Fun pFun;
int **pVtab = (int **)(&obj);
int n = 5;
cout << "[0] GrandChild::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[0][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
cout << "============================================" << endl;
}
int main()
{
test_parent();
test_child();
test_grandchild();
return 0;
}
/*
[0] Parent::_vptr->
------[0] Parent::f()
------[1] Parent::g()
------[2] Parent::h()
[1] Parent.iparent = 10
============================================
[0] Child::_vptr->
------[0] Child::f()
------[1] Parent::g()
------[2] Parent::h()
------[3] Child::g_child()
------[4] Child::h_child()
[1] Parent.iparent = 10
[2] Child.ichild = 100
============================================
[0] GrandChild::_vptr->
------[0] GrandChild::f()
------[1] Parent::g()
------[2] Parent::h()
------[3] GrandChild::g_child()
------[4] Child::h_child()
[1] Parent.iparent = 10
[2] Child.ichild = 100
[3] GrandChild.igrandchild = 1000
============================================
*/
(2)多重繼承
下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系。注意:子類只overwrite了父類的f()函數,而還有一個是自己的函數(我們這樣做的目的是為了用g1()作為一個標記來標明子類的虛函數表)。而且每個類中都有一個自己的成員變量:
我們的類繼承的源代碼如下所示:父類的成員初始為10,20,30,子類的為100
使用圖片表示是下面這個樣子:
我們可以看到:
1) 每個父類都有自己的虛表。
2) 子類的成員函數被放到了第一個父類的表中。
3) 內存布局中,其父類布局依次按聲明順序排列。
4) 每個父類的虛表中的f()函數都被override成了子類的f()。這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。
代碼驗證如下
/*
version: 1.0
author: hellogiser
blog: http://www.cnblogs.com/hellogiser
date: 2014/9/29
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base1
{
public:
int ibase1;
Base1(): ibase1(10) {}
virtual void f()
{
cout << "Base1::f()" << endl;
}
virtual void g()
{
cout << "Base1::g()" << endl;
}
virtual void h()
{
cout << "Base1::h()" << endl;
}
};
class Base2
{
public:
int ibase2;
Base2(): ibase2(20) {}
virtual void f()
{
cout << "Base2::f()" << endl;
}
virtual void g()
{
cout << "Base2::g()" << endl;
}
virtual void h()
{
cout << "Base2::h()" << endl;
}
};
class Base3
{
public:
int ibase3;
Base3(): ibase3(30) {}
virtual void f()
{
cout << "Base3::f()" << endl;
}
virtual void g()
{
cout << "Base3::g()" << endl;
}
virtual void h()
{
cout << "Base3::h()" << endl;
}
};
class Derive : public Base1, public Base2, public Base3
{
public:
int iderive;
Derive(): iderive(100) {}
virtual void f()
{
cout << "Derive::f()" << endl;
}
virtual void g1()
{
cout << "Derive::g1()" << endl;
}
};
typedef void(*Fun)(void);
/*
Base1 vptr---> Derive::f(),Base1::g(),Base1::h(),Derive::g1()
ibase1
Base2 vptr--->Derive::f(),Base2::g(),Base2::h()
ibase2
Base3 vptr--->Derive::f(),Base3::g(),Base3::h()
ibase3
iderive
*/
void test()
{
Derive obj;
Fun pFun;
int **pVtab = (int **)(&obj);
int n = 4;
cout << "[0] Base1::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[0][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
cout << "============================================" << endl;
n = 3;
cout << "[2] Base2::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[2][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[3] Base2.ibase2 = " << (int)pVtab[3] << endl;
cout << "============================================" << endl;
n = 3;
cout << "[4] Base3::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[4][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[5] Base3.ibase3 = " << (int)pVtab[5] << endl;
cout << "============================================" << endl;
cout << "[6] Derive.iderive = " << (int)pVtab[6] << endl;
}
int main()
{
test();
return 0;
}
/*
[0] Base1::_vptr->
------[0] Derive::f()
------[1] Base1::g()
------[2] Base1::h()
------[3] Derive::g1()
[1] Base1.ibase1 = 10
============================================
[2] Base2::_vptr->
------[0] Derive::f()
------[1] Base2::g()
------[2] Base2::h()
[3] Base2.ibase2 = 20
============================================
[4] Base3::_vptr->
------[0] Derive::f()
------[1] Base3::g()
------[2] Base3::h()
[5] Base3.ibase3 = 30
============================================
[6] Derive.iderive = 100
*/
(3)重復繼承
下面我們再來看看,發生重復繼承的情況。所謂重復繼承,也就是某個基類被間接地重復繼承了多次。
下圖是一個繼承圖,我們重載了父類的f()函數。
下面是對於子類實例中的虛函數表的圖:
我們可以看見,最頂端的父類B其成員變量存在於B1和B2中,並被D給繼承下去了。而在D中,其有B1和B2的實例,於是B的成員在D的實例中存在兩份,一份是B1繼承而來的,另一份是B2繼承而來的。所以,如果我們使用以下語句,則會產生二義性編譯錯誤:
void test_error()
{
D d;
// d.ib = 1; //ERROR
d.B1::ib = 1; // ok
d.B2::ib = 100; // ok
}
注意,上面例程中的最後兩條語句存取的是兩個變量。雖然我們消除了二義性的編譯錯誤,但B類在D中還是有兩個實例,這種繼承造成了數據的重復,我們叫這種繼承為重復繼承。重復的基類數據成員可能並不是我們想要的。所以,C++引入了虛基類的概念。
代碼驗證如下
/*
version: 1.0
author: hellogiser
blog: http://www.cnblogs.com/hellogiser
date: 2014/9/29
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
class B
{
public:
int ib;
char cb;
public:
B(): ib(0), cb('B') {}
virtual void f()
{
cout << "B::f()" << endl;
}
virtual void Bf()
{
cout << "B::Bf()" << endl;
}
};
class B1 : public B
{
public:
int ib1;
char cb1;
public:
B1(): ib1(11), cb1('1') {}
virtual void f()
{
cout << "B1::f()" << endl;
}
virtual void f1()
{
cout << "B1::f1()" << endl;
}
virtual void Bf1()
{
cout << "B1::Bf1()" << endl;
}
};
class B2: public B
{
public:
int ib2;
char cb2;
public:
B2(): ib2(12), cb2('2') {}
virtual void f()
{
cout << "B2::f()" << endl;
}
virtual void f2()
{
cout << "B2::f2()" << endl;
}
virtual void Bf2()
{
cout << "B2::Bf2()" << endl;
}
};
class D : public B1, public B2
{
public:
int id;
char cd;
public:
D(): id(100), cd('D') {}
virtual void f()
{
cout << "D::f()" << endl;
}
virtual void f1()
{
cout << "D::f1()" << endl;
}
virtual void f2()
{
cout << "D::f2()" << endl;
}
virtual void Df()
{
cout << "D::Df()" << endl;
}
};
typedef void(*Fun)(void);
void test_error()
{
D d;
// d.ib = 1; //ERROR
d.B1::ib = 1; // ok
d.B2::ib = 100; // ok
}
/*
B1.vptr --->D::f,B::Bf,D::f1,B1::Bf1,D::Df
B.ib
B.cb
B1.ib1
B1.cb1
B2.vptr --->D::f,B::Bf,D::f2,B2::Bf2
B.ib
B.cb
B2.ib2
B2.cb2
D.id
D.cd
*/
void test_public()
{
D obj;
Fun pFun;
int **pVtab = (int **)(&obj);
int n = 5;
cout << "[0] D::B1::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[0][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[1] B.ib = " << (int)pVtab[1] << endl;
cout << "[2] B.cb = " << (char)pVtab[2] << endl;
cout << "[3] B1.ib1 = " << (int)pVtab[3] << endl;
cout << "[4] B1.cb1 = " << (char)pVtab[4] << endl;
n = 4;
cout << "[5] D::B2::_vptr->" << endl;
for (int i = 0; i < n; i++)
{
pFun = (Fun)pVtab[5][i];
cout << "------[" << i << "] ";
pFun();
}
cout << "[6] B.ib = " << (int)pVtab[6] << endl;
cout << "[7] B.cb = " << (char)pVtab[7] << endl;
cout << "[8] B2.ib2 = " << (int)pVtab[8] << endl;
cout << "[9] B2.cb2 = " << (char)pVtab[9] << endl;
cout << "[10] D.id = " << (int)pVtab[10] << endl;
cout << "[11] D.cd = " << (char)pVtab[11] << endl;
}
void test_virtual_public()
{
}
int main()
{
test_public();
return 0;
}
/*
[0] D::B1::_vptr->
------[0] D::f()
------[1] B::Bf()
------[2] D::f1()
------[3] B1::Bf1()
------[4] D::Df()
[1] B.ib = 0
[2] B.cb = B
[3] B1.ib1 = 11
[4] B1.cb1 = 1
[5] D::B2::_vptr->
------[0] D::f()
------[1] B::Bf()
------[2] D::f2()
------[3] B2::Bf2()
[6] B.ib = 0
[7] B.cb = B
[8] B2.ib2 = 12
[9] B2.cb2 = 2
[10] D.id = 100
[11] D.cd = D
*/