填个坑
分析类的内存布局
这里我用vs的内存 调试器
首先定义一个类
#include<iostream>
class Basel
{
public:
int base_1;
int base_2;
};
int main()
{
Basel b1;
printf("size of Base1 = %d", sizeof(b1));
}
C++然后我们输出一下对象的大小 发现刚好是两个数据成员变量大小的和
同时可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
可知对象内存布局:
没有虚函数的对象
#include<iostream>
class Basel
{
public:
int base_1;
int base_2;
void hoo();
};
int main()
{
Basel b1;
printf("size of Base1 = %d", sizeof(b1));
}
C++略微修改一下类对象 加入一个没有返回值的函数
发现内存布局和之前一样 因为如果一个函数不作为虚函数 那么他就不可能会发生动态绑定 也不会对对象的布局造成任何影响
拥有仅一个虚函数的类对象
#include<iostream>
class Basel
{
public:
int base_1;//8
int base_2;
virtual void base1_fun1();
};
int main()
{
Basel b1;
printf("size of Base1 = %d", sizeof(b1));
}
C++略微修改一下类对象 加入一个没有返回值的函数
发现内存布局和之前一样 因为如果一个函数不作为虚函数 那么他就不可能会发生动态绑定 也不会对对象的布局造成任何影响
拥有仅一个虚函数的类对象
#include<iostream>
class Basel
{
public:
int base_1;//8
int base_2;
virtual void base1_fun1();
};
int main()
{
Basel b1;
printf("size of Base1 = %d", sizeof(b1));
}
C++
多了四个字节 这四个字节从哪来?
在之前的数据成员上面多了一个变量 __vfptr
也就是我们之前提到的虚函数表指针 类型为void**
可以看出这是一个指向指针数组的指针 当我们对虚函数表指针解引用时候 获得的就是数组的第一个元素 那么这是什么数组。可以看到 【0】存放的是我们定义的虚函数 也就是说数组里面存放的是函数指针 即函数地址
伪代码:
void* __fun[0] = { &Base1::base1_fun1 };
const void** __vfptr = &__fun[0];
C++Vfptr是一个常量指针 不可修改*__vfptr
内存视图:
虚函数指针__vfptr位于所有的成员变量之前定义.
拥有多个虚函数的类对象
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
C++可以看到在我们加了一个虚函数之后 类对象的大小仍然是 12个字节
vs 下__vfptr
所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.
可以再来写一下 伪代码
void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun2 };
const void** __vfptr = &__fun[0];
C++总结: 我们增加一个虚函数并不会重新开辟一个新表 而是继续在虚函数表下面继续加入新项 并不会影响到类对象的大小 因为他仍然是一个指向虚函数表的虚函数表指针 同时我们可以看到 虚函数表里面的函数指针与类内部的地址并没有直接关系
可以看到实例对象b1和b2的地址并不相同 但是两个实例对象的虚函数表指针是一样的 指向同一个虚函数表 即同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.
含继承下的虚函数表的内存布局(子类无重写)
既然说到了虚函数 那么是必须说一下子类和父类继承的关系
基类在上边, 继承类的成员在下边依次定义
经展开后来看, 前面部分完全就是Base1的东西: 虚函数表指针+成员变量定义.
并且, Base1的虚函数表的[0][1]两项还是其本身就拥有的函数: base1_fun1() 和 base1_fun2()
含继承下的虚函数表的内存布局(子类重写)
#include<iostream>
class Basel
{
public:
int base_1;//8
int base_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 :public Basel
{
public:
int Derive1;
int Derive2;
//子类重写父类的虚函数
void base1_fun1() {}
};
int main()
{
//Basel b1;
//Basel b2;
Derive1 d1;
printf("size of Base1 = %d", sizeof(d1));
}
C++我们通过子类重写了父类的void base1_fun1() {}
函数 之前子类没有(重写)虚函数的话 虚函数表是父类的虚函数地址 what will be happened after that?
由于重写了基类的方法 现在虚函数表的第二个元素变成了 所以无论通过父类或者子类的指针调用 都会是被继承类重写的那个方法
定义了基类没有的虚函数的单继承的类对象布局
#include<iostream>
class Basel
{
public:
int base_1;//8
int base_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 :public Basel
{
public:
int Derive1;
int Derive2;
//子类自己的虚函数
virtual void Derive1_fun1() {}
};
int main()
{
//Basel b1;
//Basel b2;
Derive1 d1;
printf("size of Base1 = %d", sizeof(d1));
}
C++
不同的是没有基类虚函数的覆盖. 类的大小也是20个字节 那么既然在vs看不到 可以通过汇编窗口窥探一下
Derive1 d1;
Derive1* pd1 = &d1;
pd1->derive1_fun1();
C++如果不使用指针调用, 虚函数调用是不会发生动态绑定的哦! 你若直接 d1.derive1_fun1(); , 是不可能会发生动态绑定的, 但如果使用指针: pd1->derive1_fun1(); , 那么 pd1就无从知道她所指向的对象到底是Derive1 还是继承于Derive1的对象, 虽然这里我们并没有对象继承于Derive1, 但是她不得不这样做, 毕竟继承类不管你如何继承, 都不会影响到基类, 对吧?
Derive1 d1;
00751ACF lea ecx,[d1]
00751AD2 call Derive1::Derive1 (07513FCh)
//d1.Derive1_fun1();
Derive1* pd1 = &d1;
00751AD7 lea eax,[d1]
00751ADA mov dword ptr [pd1],eax
pd1->Derive1_fun1();
00751ADD mov eax,dword ptr [pd1]
00751AE0 mov edx,dword ptr [eax]
00751AE2 mov esi,esp
00751AE4 mov ecx,dword ptr [pd1]
00751AE7 mov eax,dword ptr [edx+8]
00751AEA call eax
00751AEC cmp esi,esp
00751AEE call __RTC_CheckEsp (0751262h)
C++ecx作为存储对象地址的寄存器
call xxx 目测应该是调用类对象的默认构造函数
eax存放着实例对象的地址 然后把eax赋值给pd1这个指针
着重看pd1->Derive1_fun1();
下面的代码 把d1对象的地址赋值给eax*d1
也就是【eax】表示的是虚函数表的地址 即&d1 == &vfptr
edx+8是关键 edx = 【eax】 说明【edx】是第一个虚函数的地址 【edx+8】就是第三个虚函数的地址 然后call eax 就是调用
总结:继承父类的子类的虚函数被加在了 虚函数表的【2】的位置 如下图
多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Base2
{
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 基类虚函数覆盖
virtual void base1_fun1() {}
virtual void base2_fun2() {}
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
C++子类继承两个父类 且 子类分别覆盖重写了父类的base1_fun1和base2_fun2 同时也有自身的虚函数
和上一个一样 重写的放在对应继承类的虚函数表的对应位置 而子类的虚函数依旧是看不到 继续通过反汇编看一下发现 跟之前是如出一辙
Derive1的虚函数表依然是保存到第1个拥有虚函数表的那个基类的后面的.
如果第1个直接基类没有虚函数
#include<iostream>
//class Basel
//{
//public:
// int base_1;//8
// int base_2;
// virtual void base1_fun1()
// {
// printf("base1_fun1");
// }
// virtual void base1_fun2()
// {
// printf("base1_fun2");
// }
//};
//class Derive1 :public Basel
//{
//public:
// int Derive1;
// int Derive2;
// //子类重写父类的虚函数
// void base1_fun1()
// {
// printf("Derive1::base1_fun1");
// }
//};
class Base1
{
public:
int base1_1;
int base1_2;
//virtual void base1_fun1() {}
// virtual void base1_fun2() {}
};
class Base2
{
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 基类虚函数覆盖
//virtual void base1_fun1() {}
//virtual void base2_fun2() {}
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
int main()
{
//Basel b1;
//Basel b2;
Derive1 d1;
//Derive1 d1;
//b1.base1_fun1();
//Basel* pb1 = &d1;
//pb1->base1_fun1();
//d1.Derive1_fun1();
Derive1* pd1 = &d1;
pd1->derive1_fun2();
printf("size of Base1 = %d", sizeof(d1));
}
C++
特征是第一个父类没有 虚函数表 而子类的虚函数表依然是没有显示出来 所以我们还是进入汇编看一看
和上一个类型是一模一样的 0xc是第三个虚函数 对应我们子类的第二个虚函数
接着我们可以看看对基类成员变量求偏移来观察
即
&d1==0x~d4
&d1.Base1::__vfptr==0x~d4
&d1.base2_1==0x~d8
&d1.base2_2==0x~dc
&d1.base1_1==0x~e0
&d1.base1_2==0x~e4
C++所以 第一个是有虚函数表的对象 也就是b2
两个基类都没有虚函数表
class Base1
{
public:
int base1_1;
int base1_2;
};
class Base2
{
public:
int base2_1;
int base2_2;
};
// 多继承
class Derive1 : public Base1, public Base2
{
public:
int derive1_1;
int derive1_2;
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
C++
可以看到, 现在__vfptr已经独立出来了, 不再属于Base1和Base2
且始终位于最前面 也就是&对象
类虚函数的有无有
class Base1
{
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Base2
{
public:
int base2_1;
int base2_2;
};
class Base3
{
public:
int base3_1;
int base3_2;
virtual void base3_fun1() {}
virtual void base3_fun2() {}
};
// 多继承
class Derive1 : public Base1, public Base2, public Base3
{
public:
int derive1_1;
int derive1_2;
// 自身定义的虚函数
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
C++
可以看到b3的类在b2之前 ,谁有虚函数表, 谁就往前靠!