2012-11-10 wcdj
关键字:C++对象模型, 访问私有成员, 虚函数, 虚函数表(vtbl), 虚函数表指针(vptr), 类成员函数指针
在C++中:
有两种类数据成员:(1) static (2) nonstatic
有三种类成员函数:(1) static (2) nonstatic (3)virtual
C++对象模型的演变过程:
一个object是一系列的slots,每个slot指向一个members。在这个简单模型中,members本身并不放在object之中,只有指向member的指针才放在object内。
此模型提出了一种思想:使用索引的思想,之后被应用到C++的指向成员的指针思想中。
class object本身则内含指向这两个表格的指针,一个指向data member table,一个指向member function table。其中,member function table是一系列的slots,每一个slot指出一个member function;而data member table则直接含有data本身。
此模型也提出了一种思想:member function table的思想成为以后virtual functions的一个有效解决方案。
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++对象模型之后,可以对其进行简单的验证,不同的编译器可能实现有所不同。下面的例子考虑最简单的情况,暂时不考虑继承,主要测试以下几点:
环境: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