Vtable内存布局分析
vtale 内存布局分析
虚函数表指针与虚函数表布局
考虑如下的 class:
class A {
public:
int a;
virtual void f1() {}
virtual void f2() {}
};
int main() {
A *a1 = new A();
return 0;
}
首先明确,sizeof(A)的输出是 16,因为:class A 中含有一个 int 是 4 字节,然后含有虚函数,所以必须含有一个指向 vtable 的 vptr,而 vptr 是 8 字节,8 + 4 = 12,对齐到 8 的边界,也就是 16
上述 class 的 AST record layout 如下:
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int a
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
可以证明对齐边界为 8 字节
需要注意的是:由于含有指针,而 64 位系统,指针为 8 字节,所以对齐边界是 8
虚函数表指针 vptr
为了完成多态的功能,现代的 C++编译器都采用了表格驱动的对象模型,具体来说,所有虚函数的地址都存放在一个表格之中,而这个表格就被称为虚函数表vtable
,这个虚函数表的地址被存在放类中,称为虚函数表指针vptr
使用 clang 导出上述 class A 的对象布局,有如下输出:
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int a
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
可以看到,在 class A 的对象布局中,第一个就是 vptr(8 字节)
虚函数表 vtable
利用 clang 的导出虚函数表的功能,可以看到上述 class A 的虚函数表具体内容如下:
Vtable for 'A' (4 entries).
0 | offset_to_top (0)
1 | A RTTI
-- (A, 0) vtable address --
2 | void A::f1()
3 | void A::f2()
VTable indices for 'A' (2 entries).
0 | void A::f1()
1 | void A::f2()
需要注意的是:-- (A, 0) vtable address -- 的意思是,class A 所产生的对象的 vptr 指向的就是这个地址
我们经常所说的vtable仅仅含有虚函数的地址,实际上,这不是完整的vtable
一个完整的 vtable,有以下内容(虚函数表中的内容被称为条目
或者实体
,另外并不是所有的条目都会出现,但是如果出现,一定是按照下面的顺序出现):
- virtual call (vcall) offsets:用于对虚函数执行指针调整,这些虚函数在虚基类或虚基类的子对象中声明,并在派生自虚基类的类中重写
- virtual base (vbase) offsets:用来访问某个对象的虚基
- offset to top:记录了对象的这个虚函数表地址偏移到该对象顶部地址的偏移量
- typeinfo pointer:用于 RTTI
- vitual function pointers:一系列虚函数指针
各种情况下的 vtable 布局
1 单一继承
下面讨论,单一继承情况下,虚函数表里面各种条目的具体情况,考虑如下代码:
class A {
public:
int a;
virtual void f1() {}
virtual void f2() {}
};
class B : public A {
public:
int b;
void f1() override {}
};
int main() {
A *a1 = new A();
B *b1 = new B();
return 0;
}
首先需要明确的是:sizeof(A)与 sizeof(B)的大小:
- sizeof(A):4 + 8 = 12,调整到 8 的边界,所以是 16
- sizeof(B):4 + 4 + 8 = 16,不需要进行边界对齐,所以也是 16
利用 clang 查看 class A 与 class B 的所产生的对象 a1 与 b1 的布局,有如下输出:
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int a
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
// 对于b1来说:在构造b1时,首先需要构造一个A父类对象,所以b1的布局最开始上半部分是一个A父类对象
// 但是b1中的 vtable pointer指向的是class B的虚表
*** Dumping AST Record Layout
0 | class B
0 | class A (primary base)
0 | (A vtable pointer)
8 | int a
12 | int b
| [sizeof=16, dsize=16, align=8,
| nvsize=16, nvalign=8]
利用 clang 查看 class A 与 class B 的虚函数表内容,有如下输出:
Vtable for 'A' (4 entries).
0 | offset_to_top (0)
1 | A RTTI
-- (A, 0) vtable address --
2 | void A::f1()
3 | void A::f2()
VTable indices for 'A' (2 entries).
0 | void A::f1()
1 | void A::f2()
Vtable for 'B' (4 entries).
0 | offset_to_top (0)
1 | B RTTI
-- (A, 0) vtable address --
-- (B, 0) vtable address --
2 | void B::f1()
3 | void A::f2()
VTable indices for 'B' (1 entries).
0 | void B::f1()
在 class B 的虚函数表内容中,有如下两条:
-- (A, 0) vtable address --
-- (B, 0) vtable address --
意思是:
- 如果以 A 类型的引用或者指针来看待 class B 的对象,那么此时的 vptr 指向的就是-- (A, 0) vtable address --
- 如果以 B 类型的引用或者指针来看待 class B 的对象,那么此时的 vptr 指向的就是-- (B, 0) vtable address --
虽然在上述里例子中,这两个地址是相同的,这也意味着单链继承的情况下,动态向下转换和向上转换时,不需要对 this 指针的地址做出任何修改,只需要对其重新“解释”
(这里需要说明一下:指针或者引用的类型,真正的意义是影响编译器如何解释或者说编译器如何看待该指针或者引用指向的内存中的数据)
此处还有另一种情况,即 class A 不含有虚函数,而 class B 含有虚函数,且 class B 继承于 class A:
class A {
public:
int a;
};
class B : public A {
public:
int b;
virtual void f1() {}
};
int main() {
A *a1 = new A();
B *b1 = new B();
return 0;
}
打印 class A 与 class B 的对象布局如下:
*** Dumping AST Record Layout
0 | class A
0 | int a
| [sizeof=4, dsize=4, align=4,
| nvsize=4, nvalign=4]
*** Dumping AST Record Layout
0 | class B
0 | (B vtable pointer)
8 | class A (base)
8 | int a
12 | int b
| [sizeof=16, dsize=16, align=8,
| nvsize=16, nvalign=8]
在这种情况下,把一个 derived class object 指定给 base class 的指针或者引用,就需要编译器的介入了(编译器需要调整地址,因为 class B object 中多了一根 vptr)
但是这种情况很少出现,因为:如果一个类要作为基类,那么它的析构函数基本上都要是虚的,否则通过指向基类的指针删除对象将会触发未定义的行为
单一继承情况下的虚函数表所含条目也比较少,理解起来也很容易
2 多重继承
考虑如下代码:
class A {
public:
int a;
virtual void f1() {}
};
class B {
public:
int b;
virtual void f2() {}
};
class C : public A, public B {
public:
int c;
void f1() override {}
void f2() override {}
};
int main() {
A *a1 = new A();
B *b1 = new B();
C *c1 = new C();
return 0;
}
首先,依然讨论一下 A,B,C 三个 class 的大小:
- sizeof(A):4 + 8 = 12,调整到 8 的边界,即 16
- sizeof(B):4 + 8 = 12,调整到 8 的边界,即 16
- sizeof(C):4 + 4 + 4 +8 + 8 = 28,调整到 8 的边界,即 32
这里有一个问题,为什么计算 C 的大小时,加了两次 8?因为这两个 8 是两个 vptr,那怎么 C 会有两根 vptr 呢,后面会进行解释,此处先不讨论
查看 class A、B、C 三个对象的布局,如下:
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int a
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class B
0 | (B vtable pointer)
8 | int b
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class C
0 | class A (primary base)
0 | (A vtable pointer)
8 | int a
16 | class B (base)
16 | (B vtable pointer)
24 | int b
28 | int c
| [sizeof=32, dsize=32, align=8,
| nvsize=32, nvalign=8]
查看 class A、B、C 的虚函数表的所有条目:
Vtable for 'A' (3 entries).
0 | offset_to_top (0)
1 | A RTTI
-- (A, 0) vtable address --
2 | void A::f1()
VTable indices for 'A' (1 entries).
0 | void A::f1()
Vtable for 'B' (3 entries).
0 | offset_to_top (0)
1 | B RTTI
-- (B, 0) vtable address --
2 | void B::f2()
VTable indices for 'B' (1 entries).
0 | void B::f2()
Vtable for 'C' (7 entries).
0 | offset_to_top (0)
1 | C RTTI
-- (A, 0) vtable address --
-- (C, 0) vtable address --
2 | void C::f1()
3 | void C::f2()
4 | offset_to_top (-16)
5 | C RTTI
-- (B, 16) vtable address --
6 | void C::f2()
[this adjustment: -16 non-virtual]
Thunks for 'void C::f2()' (1 entry).
0 | this adjustment: -16 non-virtual
VTable indices for 'C' (2 entries).
0 | void C::f1()
1 | void C::f2()
此时可以看到,在多重继承下,虚函数表多出了许多单一继承没有的条目,接下来进行仔细讨论
2.1 为什么 C 的布局中有两个 vptr?
与单链继承不同,由于 A 和 B 完全独立,它们的虚函数没有顺序关系,即 f1 和 f2 有着相同对虚表起始位置的偏移量,所以不可以按照偏移量的顺序排布;并且 A 和 B 中的成员变量也是无关的,因此基类间也不具有包含关系;这使得 A 和 B 在 C 中必须要处于两个不相交的区域中,同时需要有两个虚指针分别对它们虚函数表索引
2.2 class C 对象的内存布局中 primary base 是何意义?
再次关注一下 class C 的对象的内存布局:
*** Dumping AST Record Layout
0 | class C
0 | class A (primary base)
0 | (A vtable pointer)
8 | int a
16 | class B (base)
16 | (B vtable pointer)
24 | int b
28 | int c
| [sizeof=32, dsize=32, align=8,
| nvsize=32, nvalign=8]
已经知道 class C 是 public 方式继承了 class A 与 class B,而 class A 被标记为primary base
,其意义是:class C 将 class A 作为主基类
,也就是将 class C 的虚函数并入
class A 的虚函数表之中
2.3 多重继承情况下,class C 的虚函数表 vtable 的特点?
多重继承情况下,class C 的虚函数表内容如下:
Vtable for 'C' (7 entries).
0 | offset_to_top (0)
1 | C RTTI
-- (A, 0) vtable address --
-- (C, 0) vtable address --
2 | void C::f1()
3 | void C::f2()
4 | offset_to_top (-16)
5 | C RTTI
-- (B, 16) vtable address --
6 | void C::f2()
[this adjustment: -16 non-virtual]
Thunks for 'void C::f2()' (1 entry).
0 | this adjustment: -16 non-virtual
VTable indices for 'C' (2 entries).
0 | void C::f1()
1 | void C::f2()
可以看到,class C 的整个虚函数表其实是两个虚函数表拼接而成
(这也就对应了 class C 为什么由两个 vptr)
一步步分析,先看上半部分的虚函数表:
0 | offset_to_top (0)
1 | C RTTI
-- (A, 0) vtable address --
-- (C, 0) vtable address --
2 | void C::f1()
3 | void C::f2()
前面已经提到过,class C 会把 class A 当作主基类
,并把自己的虚函数并入到 class A 的虚函数表之中,所以,可以才会看到如上的内容
所以,class C 中的一根 vptr 会指向这个虚函数表
再看下半部分的虚函数表:
4 | offset_to_top (-16)
5 | C RTTI
-- (B, 16) vtable address --
6 | void C::f2()
[this adjustment: -16 non-virtual]
Thunks for 'void C::f2()' (1 entry).
0 | this adjustment: -16 non-virtual
注意,此时的 offset_to_top 中的偏移量已经是 16 了
之前说过,offset_to_top 的意义是:将对象从当前这个类型转换为该对象的实际类型的地址偏移量
在多继承中,以 class A、B、C 为例,class A 和 class B 以及 class C 类型的指针或者引用都可以指向 class C 类型的实例,比如:
C cc = new C();
B &bb = cc;
bb.f1(); // 我们知道,由于多态,此时实际调用的class C中的虚函数f1(),即相当于cc.f1()
// 回顾class C的对象的内存布局
// 当我们用 B类型的引用接收cc对象时,this指针相当于指在了`16 | class B (base)`这个地方,要想实现多态,需要将this指针向上偏移16个字节,这样this指针才能指向cc对象的起始地址,编译器才能以C类型来解释cc这个对象而不会出错
*** Dumping AST Record Layout
0 | class C
0 | class A (primary base)
0 | (A vtable pointer)
8 | int a
16 | class B (base)
16 | (B vtable pointer)
24 | int b
28 | int c
| [sizeof=32, dsize=32, align=8,
| nvsize=32, nvalign=8]
在多继承中,由于不同的基类起点可能处于不同的位置,因此当需要将它们转化为实际类型时,this 指针的偏移量也不相同,且由于多态的特性,cc 的实际类型在编译时期是无法确定的;那必然需要一个东西帮助我们在运行时期确定 cc 的实际类型,这个东西就是offset_to_top
。通过让this指针
加上offset_to_top
的偏移量,就可以让 this 指针指向实际类型的起始地址
class C 下半部分的虚函数表还有一个值得注意的地方:
6 | void C::f2()
[this adjustment: -16 non-virtual]
Thunks for 'void C::f2()' (1 entry).
0 | this adjustment: -16 non-virtual
意思是,当以 B 类型的指针或者引用接受了 class C 的对象并调用 f2 时:需要将 this 指针调整-16 个字节,然后再进行调用(这跟上面所说的一样,将 this 向上调整 16 个字节就是让 this 指向 class C 对象的起始地址,从而编译器会以 class C 这个类型来看待 this 指针),然后再调用 f2,也就确保了调用的是 class C 的虚函数表中自己的 f2
3 虚拟继承
首先考虑如下代码中的 class A、B、C、D:
class A {
public:
int a;
virtual void fa() {}
};
class B : public virtual A {
public:
int b;
virtual void fb() {}
};
class C : public virtual A {
public:
int c;
virtual void fc() {}
};
class D : public B, public C {
public:
int c;
void fa() override {}
virtual void fd() {}
};
int main() {
A *a1 = new A();
B *b1 = new B();
C *c1 = new C();
D *d1 = new D();
return 0;
}
class B、C 都是以虚拟继承
的方式继承 class A
对于编译器来说,要支持虚拟继承实在要花费很大的一番功夫,因为编译器不仅需要在 class D 中只保存一份 class A 的成员变量,还要确保多态行为的正确性
还是先打印出相应的对象布局以及 vtable 布局:
*** Dumping AST Record Layout
0 | class A
0 | (A vtable pointer)
8 | int a
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class B
0 | (B vtable pointer)
8 | int b
16 | class A (virtual base)
16 | (A vtable pointer)
24 | int a
| [sizeof=32, dsize=28, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class C
0 | (C vtable pointer)
8 | int c
16 | class A (virtual base)
16 | (A vtable pointer)
24 | int a
| [sizeof=32, dsize=28, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class D
0 | class B (primary base)
0 | (B vtable pointer)
8 | int b
16 | class C (base)
16 | (C vtable pointer)
24 | int c
28 | int c
32 | class A (virtual base)
32 | (A vtable pointer)
40 | int a
| [sizeof=48, dsize=44, align=8,
| nvsize=32, nvalign=8]
Vtable for 'A' (5 entries).
0 | offset_to_top (0)
1 | A RTTI
-- (A, 0) vtable address --
2 | void A::f1()
3 | void A::f2()
4 | void A::f3()
VTable indices for 'A' (3 entries).
0 | void A::f1()
1 | void A::f2()
2 | void A::f3()
Vtable for 'B' (14 entries).
0 | vbase_offset (16)
1 | offset_to_top (0)
2 | B RTTI
-- (B, 0) vtable address --
3 | void B::f1()
4 | void B::f2()
5 | void B::fb()
6 | vcall_offset (0)
7 | vcall_offset (-16)
8 | vcall_offset (-16)
9 | offset_to_top (-16)
10 | B RTTI
-- (A, 16) vtable address --
11 | void B::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
12 | void B::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
13 | void A::f3()
Virtual base offset offsets for 'B' (1 entry).
A | -24
Thunks for 'void B::f1()' (1 entry).
0 | this adjustment: 0 non-virtual, -24 vcall offset offset
Thunks for 'void B::f2()' (1 entry).
0 | this adjustment: 0 non-virtual, -32 vcall offset offset
VTable indices for 'B' (3 entries).
0 | void B::f1()
1 | void B::f2()
2 | void B::fb()
Vtable for 'C' (14 entries).
0 | vbase_offset (16)
1 | offset_to_top (0)
2 | C RTTI
-- (C, 0) vtable address --
3 | void C::f1()
4 | void C::f2()
5 | void C::fc()
6 | vcall_offset (0)
7 | vcall_offset (-16)
8 | vcall_offset (-16)
9 | offset_to_top (-16)
10 | C RTTI
-- (A, 16) vtable address --
11 | void C::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
12 | void C::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
13 | void A::f3()
Virtual base offset offsets for 'C' (1 entry).
A | -24
Thunks for 'void C::f1()' (1 entry).
0 | this adjustment: 0 non-virtual, -24 vcall offset offset
Thunks for 'void C::f2()' (1 entry).
0 | this adjustment: 0 non-virtual, -32 vcall offset offset
VTable indices for 'C' (3 entries).
0 | void C::f1()
1 | void C::f2()
2 | void C::fc()
Vtable for 'D' (21 entries).
0 | vbase_offset (32)
1 | offset_to_top (0)
2 | D RTTI
-- (B, 0) vtable address --
-- (D, 0) vtable address --
3 | void D::f1()
4 | void D::f2()
5 | void B::fb()
6 | void D::fd()
7 | vbase_offset (16)
8 | offset_to_top (-16)
9 | D RTTI
-- (C, 16) vtable address --
10 | void D::f1()
[this adjustment: -16 non-virtual]
11 | void D::f2()
[this adjustment: -16 non-virtual]
12 | void C::fc()
13 | vcall_offset (0)
14 | vcall_offset (-32)
15 | vcall_offset (-32)
16 | offset_to_top (-32)
17 | D RTTI
-- (A, 32) vtable address --
18 | void D::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
19 | void D::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
20 | void A::f3()
Virtual base offset offsets for 'D' (1 entry).
A | -24
Thunks for 'void D::f1()' (2 entries).
0 | this adjustment: -16 non-virtual
1 | this adjustment: 0 non-virtual, -24 vcall offset offset
Thunks for 'void D::f2()' (2 entries).
0 | this adjustment: -16 non-virtual
1 | this adjustment: 0 non-virtual, -32 vcall offset offset
VTable indices for 'D' (3 entries).
0 | void D::f1()
1 | void D::f2()
3 | void D::fd()
Construction vtable for ('B', 0) in 'D' (14 entries).
0 | vbase_offset (32)
1 | offset_to_top (0)
2 | B RTTI
-- (B, 0) vtable address --
3 | void B::f1()
4 | void B::f2()
5 | void B::fb()
6 | vcall_offset (0)
7 | vcall_offset (-32)
8 | vcall_offset (-32)
9 | offset_to_top (-32)
10 | B RTTI
-- (A, 32) vtable address --
11 | void B::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
12 | void B::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
13 | void A::f3()
Construction vtable for ('C', 16) in 'D' (14 entries).
0 | vbase_offset (16)
1 | offset_to_top (0)
2 | C RTTI
-- (C, 16) vtable address --
3 | void C::f1()
4 | void C::f2()
5 | void C::fc()
6 | vcall_offset (0)
7 | vcall_offset (-16)
8 | vcall_offset (-16)
9 | offset_to_top (-16)
10 | C RTTI
-- (A, 32) vtable address --
11 | void C::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
12 | void C::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
13 | void A::f3()
先分析 classB、C;由于 class B、C 基本相同,所以此处只分析 class B,先单独看 class B 的对象的内存布局:
*** Dumping AST Record Layout
0 | class B
0 | (B vtable pointer)
8 | int b
16 | class A (virtual base)
16 | (A vtable pointer)
24 | int a
| [sizeof=32, dsize=28, align=8,
| nvsize=12, nvalign=8]
对比可以看出,在 class B 的对象的内存布局上,虚拟继承与普通继承的最大区别在于:虚拟继承下,class B 的内存布局不再是 class A 的内容在最前面然后紧接着 class B 的内容,而是先是 class B 的内容,然后再接着 class A 的内容
这种布局看起来就像在 class B 对象的后面接上一个 class A 对象,观察一下左边显示的偏移量:
可以看到 class A 的 vptr 的偏移量为 16,在 class A 之前,就是 class B 的内容了,class B 只含有一根 vptr(8 字节)+一个 int(4 字节)=12 字节,然而 class A 的 vptr 的偏移量却是 16,也就是说,class B 的对象完成了边界调整(12 调整到 16),然后再在后面拼接上 class A 的对象
再分析一下 class B 的 vtable:
Vtable for 'B' (14 entries).
0 | vbase_offset (16)
1 | offset_to_top (0)
2 | B RTTI
-- (B, 0) vtable address --
3 | void B::f1()
4 | void B::f2()
5 | void B::fb()
6 | vcall_offset (0)
7 | vcall_offset (-16)
8 | vcall_offset (-16)
9 | offset_to_top (-16)
10 | B RTTI
-- (A, 16) vtable address --
11 | void B::f1()
[this adjustment: 0 non-virtual, -24 vcall offset offset]
12 | void B::f2()
[this adjustment: 0 non-virtual, -32 vcall offset offset]
13 | void A::f3()
Virtual base offset offsets for 'B' (1 entry).
A | -24
Thunks for 'void B::f1()' (1 entry).
0 | this adjustment: 0 non-virtual, -24 vcall offset offset
Thunks for 'void B::f2()' (1 entry).
0 | this adjustment: 0 non-virtual, -32 vcall offset offset
VTable indices for 'B' (3 entries).
0 | void B::f1()
1 | void B::f2()
2 | void B::fb()
vbase_offset (16):用来访问虚基类子对象的偏移量(结合 class B 的对象内存布局观察)
vcall_offset(-16):当 class A 的引用 a 实际接受的是 class B 对象,然后执行 a→f1()(或 f2),由于 f1(或 f2)在 class B 中被重写过了,而此时的 this 表示的是一个 class A 类型的对象,所以需要对 this 进行调整才能正确的调用到 B::f1()(或 f2),this 如何调整?靠的就是这个 vcall_offset(-16)即将 this 指针向上调整 16 个字节,然后再调用 f1()(或 f2)
vcall_offset(0):当 class A 的引用 a 实际接受的是 class B 对象,然后执行 a→f3(),由于 f3 并没有被 class B 重写,所以此时的 this 不需要进行调整,所以 vcall_offset 为 0
对于 class D 的 vtable 来说,只是变得更加复杂而已,其中的条目在之前已经全部介绍过了,可以自行进行分析
PS:个人分析,不对的地方请指正
Vtable内存布局分析的更多相关文章
- JVM 系列(4)一看就懂的对象内存布局
请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...
- HotSpot源码分析之C++对象的内存布局
HotSpot采用了OOP-Klass模型来描述Java类和对象.OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象的具体类型.为了更好理解这个模型, ...
- 从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态
一.封装模型的内存布局 常见类对象的成员可能包含以下元素: 内建类型.指针.引用.组合对象.虚函数. 另一个角度的分类: 数据成员:静态.非静态 成员函数:静态.非静态.虚函数 1.仅包含内建类型的场 ...
- 从汇编看c++中的虚拟继承及内存布局(二)
下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } virtual int getTop() { cout <&l ...
- C++对象的内存布局以及虚函数表和虚基表
C++对象的内存布局以及虚函数表和虚基表 本文为整理文章, 参考: http://blog.csdn.net/haoel/article/details/3081328 http://blog.csd ...
- 图说C++对象模型:对象内存布局详解
0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...
- C++ 系列:内存布局
转载自http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html 为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内 ...
- c/c++ 对象内存布局
一.对象内存查看工具 VS 编译器 CL 的一个编译选项可以查看 C++ 类的内存布局,非常有用.使用如下,从开始程序菜单找到 Visual Stdio 2012. 选择 VS 的命令行工具,按如下格 ...
- C++ 多继承和虚继承的内存布局(转)
转自:http://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中,我们 ...
随机推荐
- apache ignite系列(一): 简介
apache-ignite简介(一) 1,简介 ignite是分布式内存网格的一种实现,其基于java平台,具有可持久化,分布式事务,分布式计算等特点,此外还支持丰富的键值存储以及SQL语法(基于 ...
- sparkSql使用hive数据源
1.pom文件 <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-lib ...
- Python网络爬虫实战(四)模拟登录
对于一个网站的首页来说,它可能需要你进行登录,比如知乎,同一个URL下,你登录与未登录当然在右上角个人信息那里是不一样的. (登录过) (未登录) 那么你在用爬虫爬取的时候获得的页面究竟是哪个呢? 肯 ...
- Tomcat9 安装与配置
一.下载 到http://tomcat.apache.org/下载绿色解压包 二.启动 1.解压后打开tomcat/bin目录下的startup.bat即可启动 打开后发现出现乱码 解决方法: 打开t ...
- SpringBoot——Web开发(静态资源映射)
静态资源映射 SpringBoot对于SpringMVC的自动化配置都在WebMVCAutoConfiguration类中. 其中一个静态内部类WebMvcAutoConfigurationAdapt ...
- MOOC web前端开发笔记(一)
网站和网页 网站 互联网上用于展示特定内容的相关网页的集合. 网页 网站中的一页,一个网站中的网页通过"超链接"的方式被组织在一起. 主页 进入网站看到的第一个网页,主页的文件名通 ...
- js vue 页面添加水印
vue 微信页面添加水印 this.$nextTick(function() { watermark({ watermark_txt ...
- JAVASE知识点总结(二)
第十三章:多态 一.instanceof 判断一个类是否是指定的类 真则返回true 假则返回false. 二.字段没有多态,只有方法有多态,字段前面是的什么类型,字段就调用谁的,在编译时就已经确 ...
- Android Studio [Activity的生命周期]
package com.xdw.a122; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; imp ...
- selenium-03-常用操作
基本介绍: Selenium工具专门为WEB应用程序编写的一个验收测试工具. Selenium的核心:browser bot,是用JavaScript编写的. Selenium工具有4种:Seleni ...