コンパイラの補助情報からc++オブジェクトメモリレイアウトを見る
20701 ワード
事前知識
本稿では,32ビットのコンパイラを用いてコンパイルした結果,クラスのメモリレイアウト情報を印刷できる.
DevCPP IDE
このIDEは私の好きなwindowsの下のcppのIDEの1つで、それはツール->コンパイルオプションがあって、コンパイラのタイプを選択することができて、コンパイルオプションの中でいくつかの情報を加えることができて、メモリのレイアウトの情報を出力することができるため、私はコンパイルの時に以下のコマンドに参加します
--std=c++11 -fdump-class-hierarchy
-fdump-class-hierarchyこのオプションはgccコンパイル時にクラスのレイアウト情報を生成することができ、生成されたファイル名は6に類似する.cpp.002t.class
vs2017
vsで使用されるコンパイラはclです.コマンドは/d 1 reportAllClassLayoutです.すべてのクラス関連レイアウトを出力します.
clang
clang -Xclang -fdump-record-layouts
メモリレイアウト情報の理解
テストコードは次のとおりです.
class father{
int a;
int b;
};
class child: public father{
};
int main(){
return 0;
}
g++-fdump-class-hierarchy testを用いる.cppはtestを生成する.cpp.002t.classの内容は以下の通りです
Class father
size=8 align=4
base size=8 base align=4
father (0x0x16662d8) 0
Class child
size=8 align=4
base size=8 base align=4
child (0x0x3984e00) 0
father (0x0x1666310) 0
クラスのサイズと配置情報が表示されます.
标题:メモリの位置合わせを理解する
マルチステートとダミーテーブル
マルチステートとは、単純に言えば、継承階層で親のポインタが複数の形態を有することを意味します.親の関数ではなく、子のオブジェクトを指す場合、子の関数を呼び出すことができます.
ダミー関数ポインタは、通常、オブジェクトメモリレイアウトの最初の位置に配置されます.これは、多層継承または多重継承の場合に、ダミー関数テーブルを最も効率的に取得できるようにするためです.vprtがオブジェクトメモリの一番前にある場合、オブジェクトのアドレスは虚関数ポインタアドレスです.虚関数ポインタのアドレスを取得できます.
以下のように虚関数ポインタのアドレス(虚関数ポインタが指すアドレスではなく)を取得する
Base b(1000);
int * vptrAdree = (int *)(&b);
vptrAdreeは虚関数ポインタ(虚関数テーブルを指す)を指し、
最初の虚関数のアドレスは次のように得ることができます
Base b(1000);
using fun=void(*) ();
fun fun1 = (fun)*((int *)*(int *)(&b));
fun fun2 = (fun)*((int *)*(int *)(&c)+1);
単純な場合、単層継承と虚関数の全オーバーライド
コンパイラによって生成されたメモリレイアウト情報を参照してください.
#include
using namespace std;
class Base{
int a;
virtual int print(){
cout<
g++コンパイル後に生成されるメモリレイアウト情報は:
Vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI4Base)
8 (int (*)(...))Base::print
12 (int (*)(...))Base::print2
Class Base
size=8 align=4
base size=8 base align=4
Base (0x0x4e057a8) 0
vptr=((& Base::_ZTV4Base) + 8u)
Vtable for child
child::_ZTV5child: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5child)
8 (int (*)(...))child::print
12 (int (*)(...))child::print2
Class child
size=8 align=4
base size=8 base align=4
child (0x0x4e4b280) 0
vptr=((& child::_ZTV5child) + 8u)
Base (0x0x4e057e0) 0
primary-for child (0x0x4e4b280)
Vtableは虚関数テーブルを表しており、Vtable for Baseにはvptrが保存されており、位置はvptr=((&Base:_ZTV4 Base)+8 u)(ここでは8のオフセットが加算されていることに注意)であり、この位置の最初の内容は(int(*)(...)である.Base::print.
複雑な場合:多重継承と一部の虚関数のオーバーライド
#include
using namespace std;
class Parent {
public:
int iparent;
Parent ():iparent (10) {}
virtual void g() { cout << " Parent::g()" << endl; }
virtual void f() { cout << " Parent::f()" << endl; }
virtual void h() { cout << " Parent::h()" << endl; }
};
class Child : public Parent {
public:
int ichild;
Child():ichild(100) {}
virtual void g_child() { cout << "Child::g_child()" << endl; }
virtual void h_child() { cout << "Child::h_child()" << endl; }
virtual void f() { cout << "Child::f()" << endl; }
};
class GrandChild : public Child{
public:
int igrandchild;
GrandChild():igrandchild(1000) {}
virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
virtual void f() { cout << "GrandChild::f()" << endl; }
virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
int main(){
typedef void(*Fun)(void);
GrandChild gc;
int** pVtab = (int**)&gc;
cout << "[0] GrandChild::_vptr->" << endl;
for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){
Fun pFun = (Fun)pVtab[0][i];
cout << " ["<Vtable for Parent
Parent::_ZTV6Parent: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI6Parent)
8 (int (*)(...))Parent::g
12 (int (*)(...))Parent::f
16 (int (*)(...))Parent::h
Class Parent
size=8 align=4
base size=8 base align=4
Parent (0x0x4de37a8) 0
vptr=((& Parent::_ZTV6Parent) + 8u)
Vtable for Child
Child::_ZTV5Child: 7u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Child)
8 (int (*)(...))Parent::g
12 (int (*)(...))Child::f
16 (int (*)(...))Parent::h
20 (int (*)(...))Child::g_child
24 (int (*)(...))Child::h_child
Class Child
size=12 align=4
base size=12 base align=4
Child (0x0x4e2a5c0) 0
vptr=((& Child::_ZTV5Child) + 8u)
Parent (0x0x4de37e0) 0
primary-for Child (0x0x4e2a5c0)
Vtable for GrandChild
GrandChild::_ZTV10GrandChild: 8u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI10GrandChild)
8 (int (*)(...))Parent::g
12 (int (*)(...))GrandChild::f
16 (int (*)(...))Parent::h
20 (int (*)(...))GrandChild::g_child
24 (int (*)(...))Child::h_child
28 (int (*)(...))GrandChild::h_grandchild
Class GrandChild
size=16 align=4
base size=16 base align=4
GrandChild (0x0x4e2aa80) 0
vptr=((& GrandChild::_ZTV10GrandChild) + 8u)
Child (0x0x4e2aac0) 0
primary-for GrandChild (0x0x4e2aa80)
Parent (0x0x4de3818) 0
primary-for Child (0x0x4e2aac0)
調べてみると,子クラスの虚函数表の優先度は先親クラス,再子クラス再孫クラスであり,同一優先度は宣言順にアドレスを並べ,親クラスの虚関数が上書きされても元の位置に書くことで,親クラスのポインタが関数名でそのアドレスを見つけることが保証される.
プログラムの実行結果は次のとおりです.
[0] GrandChild::_vptr->
[0] GrandChild::f()
[1] Parent::g()
[2] Parent::h()
[3] GrandChild::g_child()
[4] Child::h1()
[5] GrandChild::h_grandchild()
[1] Parent.iparent = 10
[2] Child.ichild = 100
[3] GrandChild.igrandchild = 1000
わかる テーブルは の にあります. メンバー は、 および の に って に します. の ではoverwriteの が テーブルで される.
#include
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; }
};
int main(){
typedef void(*Fun)(void);
Derive d;
int** pVtab = (int**)&d;
cout << "[0] Base1::_vptr->" << endl;
Fun pFun = (Fun)pVtab[0][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] ";pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] ";pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; cout<"<"<Vtable for Base1
Base1::_ZTV5Base1: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base1)
8 (int (*)(...))Base1::f
12 (int (*)(...))Base1::g
16 (int (*)(...))Base1::h
Class Base1
size=8 align=4
base size=8 base align=4
Base1 (0x0x4d907a8) 0
vptr=((& Base1::_ZTV5Base1) + 8u)
Vtable for Base2
Base2::_ZTV5Base2: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base2)
8 (int (*)(...))Base2::f
12 (int (*)(...))Base2::g
16 (int (*)(...))Base2::h
Class Base2
size=8 align=4
base size=8 base align=4
Base2 (0x0x4d907e0) 0
vptr=((& Base2::_ZTV5Base2) + 8u)
Vtable for Base3
Base3::_ZTV5Base3: 5u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI5Base3)
8 (int (*)(...))Base3::f
12 (int (*)(...))Base3::g
16 (int (*)(...))Base3::h
Class Base3
size=8 align=4
base size=8 base align=4
Base3 (0x0x4d90818) 0
vptr=((& Base3::_ZTV5Base3) + 8u)
Vtable for Derive
Derive::_ZTV6Derive: 16u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI6Derive)
8 (int (*)(...))Derive::f
12 (int (*)(...))Base1::g
16 (int (*)(...))Base1::h
20 (int (*)(...))Derive::g1
24 (int (*)(...))-8
28 (int (*)(...))(& _ZTI6Derive)
32 (int (*)(...))Derive::_ZThn8_N6Derive1fEv
36 (int (*)(...))Base2::g
40 (int (*)(...))Base2::h
44 (int (*)(...))-16
48 (int (*)(...))(& _ZTI6Derive)
52 (int (*)(...))Derive::_ZThn16_N6Derive1fEv
56 (int (*)(...))Base3::g
60 (int (*)(...))Base3::h
Class Derive
size=28 align=4
base size=28 base align=4
Derive (0x0x4ddeb40) 0
vptr=((& Derive::_ZTV6Derive) + 8u)
Base1 (0x0x4d90850) 0
primary-for Derive (0x0x4ddeb40)
Base2 (0x0x4d90888) 8
vptr=((& Derive::_ZTV6Derive) + 32u)
Base3 (0x0x4d908c0) 16
vptr=((& Derive::_ZTV6Derive) + 52u)
プログラムの は[0] Base1::_vptr->
[0] Derive::f()
[1] Base1::g()
[2] Base1::h()
[3] Derive::g1()
[4] 1
[1] Base1.ibase1 = 10
[2] Base2::_vptr->
[0] Derive::f()
[1] Base2::g()
[2] Base2::h()
[3] 1
[3] Base2.ibase2 = 20
[4] Base3::_vptr->
[0] Derive::f()
[1] Base3::g()
[2] Base3::h()
[3] 0
[5] Base3.ibase3 = 30
[6] Derive.iderive = 100
: には の があります. サブクラスのメンバー は、 の のテーブルに されます. メモリレイアウトでは、 レイアウトが に べられています. クラスの のf() はoverwriteによって クラスのf()になった.これは, なる クラスタイプのポインタが じサブクラスインスタンスを すことを するために, の を び すことができる.
#include
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;}
};
int main(){
typedef void(*Fun)(void);
int** pVtab = NULL;
Fun pFun = NULL;
D d;
pVtab = (int**)&d;
cout << "[0] D::B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; pFun();
pFun = (Fun)pVtab[0][5];
cout << " [5] 0x" << pFun << endl;
cout << "[1] B::ib = " << (int)pVtab[1] << endl;
cout << "[2] B::cb = " << static_cast((int)(pVtab[2]))<< endl;
cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
cout << "[4] B1::cb1 = " << (char)(int)pVtab[4] << endl;
cout << "[5] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[5][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[5][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[5][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[5][4];
cout << " [4] 0x" << pFun << endl;
cout << "[6] B::ib = " << (int)pVtab[6] << endl;
cout << "[7] B::cb = " << (char)(int)pVtab[7] << endl;
cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
cout << "[9] B2::cb2 = " << (char)(int)pVtab[9] << endl;
cout << "[10] D::id = " << (int)pVtab[10] << endl;
cout << "[11] D::cd = " << (char)(int)pVtab[11] << endl;
return 0;
}
メモリの は のとおりです.
Vtable for B
B::_ZTV1B: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1B)
8 (int (*)(...))B::f
12 (int (*)(...))B::Bf
Class B
size=12 align=4
base size=9 base align=4
B (0x0x4dc27a8) 0
vptr=((& B::_ZTV1B) + 8u)
Vtable for B1
B1::_ZTV2B1: 6u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI2B1)
8 (int (*)(...))B1::f
12 (int (*)(...))B::Bf
16 (int (*)(...))B1::f1
20 (int (*)(...))B1::Bf1
Class B1
size=20 align=4
base size=17 base align=4
B1 (0x0x4e09780) 0
vptr=((& B1::_ZTV2B1) + 8u)
B (0x0x4dc27e0) 0
primary-for B1 (0x0x4e09780)
Vtable for B2
B2::_ZTV2B2: 6u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI2B2)
8 (int (*)(...))B2::f
12 (int (*)(...))B::Bf
16 (int (*)(...))B2::f2
20 (int (*)(...))B2::Bf2
Class B2
size=20 align=4
base size=17 base align=4
B2 (0x0x4e09c40) 0
vptr=((& B2::_ZTV2B2) + 8u)
B (0x0x4dc2818) 0
primary-for B2 (0x0x4e09c40)
Vtable for D
D::_ZTV1D: 14u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1D)
8 (int (*)(...))D::f
12 (int (*)(...))B::Bf
16 (int (*)(...))D::f1
20 (int (*)(...))B1::Bf1
24 (int (*)(...))D::f2
28 (int (*)(...))D::Df
32 (int (*)(...))-20
36 (int (*)(...))(& _ZTI1D)
40 (int (*)(...))D::_ZThn20_N1D1fEv
44 (int (*)(...))B::Bf
48 (int (*)(...))D::_ZThn20_N1D2f2Ev
52 (int (*)(...))B2::Bf2
Class D
size=48 align=4
base size=45 base align=4
D (0x0x4e27040) 0
vptr=((& D::_ZTV1D) + 8u)
B1 (0x0x4e27080) 0
primary-for D (0x0x4e27040)
B (0x0x4dc2850) 0
primary-for B1 (0x0x4e27080)
B2 (0x0x4e270c0) 20
vptr=((& D::_ZTV1D) + 40u)
B (0x0x4dc2888) 20
primary-for B2 (0x0x4e270c0)
は[0] D::B1::_vptr->
[0] D::f()
[1] B::Bf()
[2] D::f1()
[3] B1::Bf1()
[4] D::f2()
[5] 0x1
[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()
[4] 0x0
[6] B::ib = 0
[7] B::cb = B
[8] B2::ib2 = 12
[9] B2::cb2 = 2
[10] D::id = 100
[11] D::cd = D
:
の クラスBは,そのメンバ と がB 1とB 2に し,Dに されることが かった. 、DにはB 1とB 2のインスタンスがあり、BのメンバーはDのインスタンスに2つ し、1つはB 1が し、もう1つはB 2が したため、メモリが される.
び し を に すことで を することができるが, の はまだ されていないので, によりDクラスにibエンティティを1つしか たせることができる.
ダミー
ダミー は、 で も したクラスが の インスタンスを つ を します. の クラスのメモリレイアウトは、 の とは きく なり、 に のようになります. のサブクラス.それ が しい を している 、コンパイラは ポインタ(vptr)と テーブルを します.このvptrはオブジェクトメモリの にあります.vs : テーブルを します. のサブクラスも、 のvprtと テーブルを に します.この は、サブクラスコンテンツと1つの4バイトの0で られます. ダミー サブクラスオブジェクトには、4バイトのダミーテーブルポインタオフセット が まれます.
#include
using namespace std;
class B
{
public:
int ib;
public:
B(int i=1) :ib(i){}
virtual void f() { cout << "B::f()" << endl; }
virtual void Bf() { cout << "B::Bf()" << endl; }
};
class B1 : virtual public B
{
public:
int ib1;
public:
B1(int i = 100 ) :ib1(i) {}
virtual void f() { cout << "B1::f()" << endl; }
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
};
int main(){
B1 b;
using Fun=void(*)();
Fun pFun = NULL;
int ** pvtable = (int**)&b;
for (int i=0; i<9; i++){
if (pvtable[0][i]==NULL) {
cout<
メモリレイアウトVtable for B
B::_ZTV1B: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1B)
8 (int (*)(...))B::f
12 (int (*)(...))B::Bf
Class B
size=8 align=4
base size=8 base align=4
B (0x0x4f527a8) 0
vptr=((& B::_ZTV1B) + 8u)
Vtable for B1
B1::_ZTV2B1: 12u entries
0 8u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI2B1)
12 (int (*)(...))B1::f
16 (int (*)(...))B1::f1
20 (int (*)(...))B1::Bf1
24 0u
28 4294967288u
32 (int (*)(...))-8
36 (int (*)(...))(& _ZTI2B1)
40 (int (*)(...))B1::_ZTv0_n12_N2B11fEv
44 (int (*)(...))B::Bf
VTT for B1
B1::_ZTT2B1: 2u entries
0 ((& B1::_ZTV2B1) + 12u)
4 ((& B1::_ZTV2B1) + 40u)
Class B1
size=16 align=4
base size=8 base align=4
B1 (0x0x4f9a540) 0
vptridx=0u vptr=((& B1::_ZTV2B1) + 12u)
B (0x0x4f527e0) 8 virtual
vptridx=4u vbaseoffset=-12 vptr=((& B1::_ZTV2B1) + 40u)
プログラム
B1::f()
B1::f1()
B1::Bf1()
here:0
B::Bf()
size == 16
[1]:B1::ib1=100
[3]:B::ib=1
[2]:B::ib=4781096
ここでのメモリ は、vc++の とは なる です.
しかし、ここでsizeは16で、2つのポインタがあることを しています.
マルチダミー
の で, も したクラスDクラスのオブジェクトモデルはまた なる となっている.Dクラスオブジェクトのメモリ には、 の があります. Dクラスオブジェクトメモリにおいて、ベースクラスが れる は、まずB 1( も の )、 にB 2( の の )、 にB( ) である. DクラスオブジェクトのデータメンバーidはBクラスの に かれ、2つの のデータは として0で られている. コンパイラは、Dクラスに のvptrを するのではなく、 も の の ベースクラステーブルを きして し、 に されたオブジェクトモデルと じです. スーパークラスBの は、Dクラスオブジェクトのメモリレイアウトの に される.
C/C++メモリアライメントの
C++オブジェクトのメモリレイアウト-coolshell
C++オブジェクトモデル:オブジェクトメモリレイアウトの
コンパイラはメモリレイアウトを します