前言
不同平台下的字节宽度,以下的示例均在x64的平台上测试,编译器vs2015。
| 数据类型 | 说明 | 32位字节数 | 64位字节数 | 取值范围 |
|---|---|---|---|---|
| bool | 布尔型 | 1 | 1 | true,false |
| char | 字符型 | 1 | 1 | -128~127 |
| unsigned char | 无符号字符型 | 1 | 1 | 0~255 |
| short | 短整型 | 2 | 2 | -32768~32767 |
| unsigned short | 无符号短整型 | 2 | 2 | 0~65535 |
| int | 整型 | 4 | 4 | -2147483648~2147483647 |
| unsigned int | 无符号整型 | 4 | 4 | 0~4294967295 |
| long | 长整型 | 4 | 8 | – |
| unsigned long | 无符号长整型 | 4 | 8 | – |
| long long | 长整型 | 8 | 8 | -264~264-1 |
| float | 单精度浮点数 | 4 | 4 | 范围-2128~2128 精度为6~7位有效数字 |
| double | 双精度浮点数 | 8 | 8 | 范围-21024~21024 精度为15~16位 |
| long double | 扩展精度浮点数 | 8 | 8 | 范围-21024~21024 精度为15~16位 |
| * | 地址 | 4 | 8 | – |
一、单个的类
1、不包含虚函数的类
1.1 代码示例
注意类CBase0中的析构函数,不是虚函数。类中不存在任何的虚函数。
#include <iostream>
#include <string>
using namespace std;
class CBase0 {
public:
CBase0() :m_n1(0), m_l1(1) {}
~CBase0() {}
void print() { cout << "CBase0::print" << endl; }
public:
short m_n1;
long long m_l1;
};
int main()
{
CBase0*pBase = new CBase0;
return 0;
}
1.2 调试结果

gdb调试下的结果,因为没有虚汗数,所以不包含虚表:

1.3 小结
没有虚函数的类,不存在虚表。内存分布只有成员变量,成员变量根据声明的先后顺序排列。占用的字节空间根据平台的不同不一样,且会内存对齐到最大的占用字节数。上图黄色表示short m_n1绿色表示long long的m_l1
2、包含虚函数
类中包含虚函数(虚析构函数或者普通的虚函数)
2.1 在1.1源码的基础上给析构函数增加了virtual关键字,声明为虚函数
2.2 调试结果的截图

2.3 小结
包含有虚函数的类,存在虚表,2.2中监视1中_vfptr的内容。虚表地址0x00007ff77955ebe0,是对象的内存中前8个字节(2.2内存1第一组框表示),后续的2个框为类的成员变量的空间,同样的是内存对齐。
当包含有多个虚函数的时候,在虚表中多个虚函数的地址根据声明的顺序依次排列。将2.2中类的成员函数print加上了virtual关键字修饰,如下图:

二、单层继承的类
1、无多态重载的虚函数
1.1 代码示例——基类CBase和派生类CDriver,派生类中没有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
1.1.1 调试的结构
1.1.1.1 派生类实例化对象的内存分布

上图中pDriver为派生类的对象,和一大节2.1小结一样,包含有同样的结构:前8个字节为虚表(虚表中有两个虚函数的地址,虚析构函数和继承自基类的虚函数);虚表后面紧随的是基类的成员函数,同样是根据声明顺序一次排列的。在虚表描述结束后是派生类CDriver的成员函数空间(注意:当前派生类是没有虚函数的)。
1.1.1.2 基类指针
将实例化的对象指针强制转到基类指针上就是pBase指针,他的结构如下描述:

红色部分是派生类CDriver的实例化对象的内存分布,和上面1.2.1描述一致。蓝色部分是基类CBase自己的内存分布,因为这里的CBase并不是一个纯接口类(虚函数为纯虚函数的基类),所以蓝色部分__vfptr是CBase的虚表。可以通过观察成员变量很好的区分基类和派生类的关系。
因为是将派生类的指针指向了基类,右边的内存分布和1.2.1描述一致。
1.2 代码示例——基类CBase和派生类CDriver,派生类中有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
virtual void driver_print() { cout << "CDriver::driver_print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
1.2.1 调试的结构
1.2.1.1 派生类对象内存结构分布

从上图看,使用vs2015编译器 蓝色部分未显示出CDriver的虚函数driver_print,【留疑问,使用gdb验证】。按理说是根据声明顺序依次排列的。
1.2.1.2 基类对象的内存分布结构

注意图中第6行 00.exe!CBase::print(void),这时候是基类的函数。
2、有多态重载的虚函数
2.1 代码示例——派生类中有多态结构
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
上面的代码和1.1中的代码差别就是CDriver中使用多态重载了基类的虚函数print,派生类中打印了子类的字符串。
2.2 调试的结果
2.2.1 派生类的内存对象

红的部分已经替换成了派生类的函数,这其实就是多态的真正实现。
三、多层继承
1、示例代码
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
//virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_n2;
};
class CDriver1 :public CDriver {
public:
CDriver1() :m_n2(2) {}
virtual ~CDriver1() {}
//virtual void print() { cout << "CDriver1::print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver1* pDriver = new CDriver1;
CBase*pBase = pDriver;
return 0;
}
2、不同层级派生类重载虚函数时
2.1 派生类1和派生类2不重载基类虚函数
代码同1描述,调试的结果:

2.2 派生类1重载基类的虚函数
代码1描述中,解开屏蔽的第16行代码,调试的结果:

2.1中的CBase::print()变成了CDriver::print(),已经发生了替换,运行时体现多态。
2.3 派生类2中重载基类的虚函数
代码1描述中,解开屏蔽的第24行,调试的结果:

红色部分已经变成了CDriver1::print(),同样已经发生了替换,运行时体现多态。
3、多基类继承的派生类
派生类CDriver继承两个基类,实例化的对象指针指向两个不同的基类指针。
3.1 示例代码
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_nBase(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_nBase;
};
class CBase1 {
public:
CBase1() :m_nBase1(2) {}
virtual ~CBase1() {}
virtual void print1() { cout << "CBase1::print" << endl; }
public:
short m_nBase1;
};
class CDriver :public CBase, public CBase1 {
public:
CDriver() :m_nDriver(3) {}
virtual ~CDriver() {}
//virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_nDriver;
};
int main()
{
CBase*pBase = new CDriver;
CBase1*pBase1 = new CDriver;
return 0;
}
3.2 调试结果
3.2.1 当派生类中不重载基类的虚函数时,基类指针CBase所指向的对象内存分布:

红色部分是基类CBase的虚表(虚函数指针)和成员变量的内存分布,绿色部分是实例对象CDriver类的分布,两个基类的虚表按继承的顺序分布,展开如下:

注意看基类的虚函数CBase::print和CBase1::print,并未重载,都是基类的实现,另一个基类的对象内存分布如下:

3.2.2 派生类重载基类的虚函数时
去掉源码中的第25行屏蔽,重载CBase的虚函数实现,不对基类CBase1重载,如下:


从上面不难发现,派生类重载了哪一个基类的虚函数,则派生类的虚表中的哪一个基类的虚函数会被替换成派生类的,这就是多态的实现了。
本文深入探讨C++中虚函数与多态的概念,详细解析不同情况下虚函数表的生成与使用,包括单个类、单层继承、多层继承及多基类继承的虚函数内存布局。通过实例代码与调试结果,揭示了编译器如VS和GCC在处理虚函数时的差异。

被折叠的 条评论
为什么被折叠?



