2012-11-10 wcdj
关键字:C++对象模型, 访问私有成员, 虚函数, 虚函数表(vtbl), 虚函数表指针(vptr), 类成员函数指针
C++对象模型的演变
在C++中:
有两种类数据成员:(1) static (2) nonstatic
有三种类成员函数:(1) static (2) nonstatic (3)virtual
C++对象模型的演变过程:
(1) 简单对象模型
一个object是一系列的slots,每个slot指向一个members。在这个简单模型中,members本身并不放在object之中,只有指向member的指针才放在object内。
此模型提出了一种思想:使用索引的思想,之后被应用到C++的指向成员的指针思想中。
(2) 表格驱动模型
class object本身则内含指向这两个表格的指针,一个指向data member table,一个指向member function table。其中,member function table是一系列的slots,每一个slot指出一个member function;而data member table则直接含有data本身。
此模型也提出了一种思想:member function table的思想成为以后virtual functions的一个有效解决方案。
(3) C++对象模型
nonstatic data members被置于每一个class object之内,static data members则被存放在所有的class object之外,并且,static和nonstatic function members也被放在所有的class object之外。
virtual functions则以两个步骤支持之:
[1] 存在一个virtual table。每一个class产生一堆指向virtual functions的指针,并存放在这张表中。(根据虚函数声明的顺序存放)
[2] 每一个class object被添加了一个指针,指向相关的virtual table。(这个指针被称为vptr)
注意:
(a) vptr的设定和重置都有每一个class的constructor、destructor和copy assignment运算符自动完成。
(b) 每一个class所关联的type_info object (用以支持runtime type identification, RTTI)也经由virtua ltable 被指出来, 通常是放在表格的第一个slot处。(注意实践表明可能不是这样)
C++对象模型简单case验证
了解了C++对象模型之后,可以对其进行简单的验证,不同的编译器可能实现有所不同。下面的例子考虑最简单的情况,暂时不考虑继承,主要测试以下几点:
环境:Windows Server 2003, 32位 + VS2008
(1) 根据对象模型计算类成员偏移量, 并通过偏移量来访问类成员,包括类的私有成员;
(2) 定义类成员函数指针, 并通过类成员函数指针访问类成员函数;
/* 2012-11-10 wcdj
* C++虚函数表的实例解析
*/
#include <stdio.h>
// 指定按1字节对齐
#pragma pack(1)
// 定义普通函数指针
typedef void(*Fun)(void);
// 定义类成员函数指针
class Base;
typedef void(Base::*CFun)(void);
#define callMemFun(obj, pCFun) ( (obj).*(pCFun) )
#define pcallMemFun(pobj, pCFun) ( (pobj)->*(pCFun) )
class Base
{
public:
// constructor and destructor
Base() {}
Base(int a, char b): m_iA(a), m_cB(b) {}
virtual ~Base() {}
// virtual functions
virtual void f()
{
printf("invork f()\n");
}
virtual void g()
{
printf("invork g()\n");
}
virtual void h()
{
printf("invork h()\n");
}
// non-virtual functions
void test()
{
printf("This is non-virtual function named test\n");
}
void test2()
{
printf("This is non-virtual function named test2\n");
}
private:
int m_iA;
char m_cB;
};
int main()
{
Base a, b(1, 'x');
// 计算类的大小
// sizeof(Base) = sizeof(vfptr) + sizeof(m_iA) + sizeof(m_cB) = 4 + 4 + 1
printf("the size of Base: %d\n", sizeof(a));
printf("the size of Base: %d\n", sizeof(b));
/* [1] 计算类成员偏移量, 并通过偏移量来访问类成员, 包括类的私有成员 */
Fun pFun = NULL;
// 对象实例地址
printf("&b = 0x%x\n", &b);
// 虚函数表地址
/* 可以发现, 虚函数表的指针存在于对象实例中最前面的位置
* 说明:
* 在Inside The C++ Object Model中有注释说, 每一个class所关联的
* type_info object (用以支持runtime type identification, RTTI)也经由
* virtual table 被指出来, 通常是放在表格的第一个slot处
*/
printf("*(int *)(&b) = 0x%x\n", *(int *)(&b));
// 虚函数表中第一个虚函数的地址
printf("*(int*)(*(int *)(&b)) = 0x%x\n", *(int*)(*(int *)(&b)));
// 通过偏移量来分别获取类的成员
// 注意: 虚函数按照其声明的顺序置于虚函数表中
// 需要强制转换为函数指针
// Base的destructor函数, 此时不能调用
pFun = (Fun)*((int *)*(int *)(&b) + 0);
//pFun();
// f()
pFun = (Fun)*((int *)*(int *)(&b) + 1);
pFun();
// g()
pFun = (Fun)*((int *)*(int *)(&b) + 2);
pFun();
// h()
pFun = (Fun)*((int *)*(int *)(&b) + 3);
pFun();
// m_iA
int iA = *((int *)(&b) + 1);
printf("iA=%d\n", iA);// 1
// m_cB
char cB = *(char *)((int *)(&b) + 2);
printf("cB=%c\n", cB);// x
// 注意: 普通成员函数属于类的级别而不属于对象级别
b.test();
printf("&Base::test = 0x%x\n", &Base::test);
printf("&Base::test2 = 0x%x\n", &Base::test2);
/* [2] 定义类成员函数指针, 并通过类成员函数指针访问类成员函数 */
// 注意区别类成员函数指针和普通函数指针定义的方法
CFun pCFun = NULL;
// [1]
pCFun = &Base::test;
((b).*(pCFun))();
// [2]
pCFun = &Base::f;
callMemFun(b, pCFun)();
// [3]
pCFun = &Base::g;
pcallMemFun(&b, pCFun)();
// [4]
pCFun = &Base::test;
pcallMemFun(&b, pCFun)();
return 0;
}
/*
output:
the size of Base: 9
the size of Base: 9
&b = 0x12ff3c
*(int *)(&b) = 0x60f3e8
*(int*)(*(int *)(&b)) = 0x4b0701
invork f()
invork g()
invork h()
iA=1
cB=x
This is non-virtual function named test
&Base::test = 0x4aedca
&Base::test2 = 0x4b263c
This is non-virtual function named test
invork f()
invork g()
This is non-virtual function named test
*/
参考:
[1] C++ 虚函数表解析
[2] 类的普通成员函数的指针
[3] 深度探索C++对象模型,Inside The C++ Object Model