最近做 cmu15445 project2 实现 B+ Tree,有一个操作涉及到将一个从 buffer_pool_manager 获取的缓存页通过 reinterpret_cast
直接转成一个类对象指针,并调用了其成员函数。第一次见这样的操作还挺无法理解的,因为这相当于将一段全为 0 的内存直接当作一个类对象去访问,听起来感觉很离经叛道,毕竟这个对象根本没有初始化过。顿感对 c++ 对象模型的了解太少,于是查了一些资料。更深入的细节还是得系统地看看 《深度探索c++对象模型》。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Base {public : void print () { std::cout << this << " " << "Base::print()" << std::endl; } static void sPrint () { std::cout << "Base::sPrint()" << std::endl; } virtual void virtualPrint () { std::cout << "Base::virtualPrint()" << std::endl; } char x; static int s; }; int Base::s = 10 ;
内存模型如下:
占用类对象内存的只有虚函数表指针和非静态成员变量,非虚成员函数的存储其实和普通函数相同,都是存储在代码区,其地址在编译期已经确定,只不过调用时额外传入 this 指针作参数罢了。静态成员变量在全局区,不占用对象空间。所以对象没有初始化对于调用非虚成员函数,静态成员函数,访问静态成员变量没有影响。测试如下:
1 2 3 4 5 6 7 8 9 10 11 int main () { std::cout << sizeof (Base) << std::endl; char data[16 ]; Base* b = reinterpret_cast <Base*>(data); cout << b << endl; b->print (); b->sPrint (); cout << b->s << endl; }
输出
1 2 3 4 5 16 0x7fff03157d00 0x7fff03157d00 Base::print () Base::sPrint() 10
但是如果调用 b->virtualPrint()
就会崩溃,因为要访问虚函数表,而虚函数表指针的内存没有初始化过。但是我们可以“组装”一个带虚函数的对象出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void hack (Base* b) { cout << b << " " << "hack()" << endl; cout << b->x << endl; } int main () { std::cout << sizeof (Base) << std::endl; char data[16 ]; void * vtbl[] = {reinterpret_cast <void *>(hack)}; void * vtbl_ptr = &vtbl; memcpy (data, &vtbl_ptr, 8 ); data[8 ] = 'A' ; Base* b = reinterpret_cast <Base*>(data); cout << b << endl; b->print (); b->sPrint (); cout << b->s << endl; cout << endl; b->virtualPrint (); }
输出
1 2 3 4 5 6 7 8 16 0x7ffdc5d2b670 0x7ffdc5d2b670 Base::print () Base::sPrint() 10 0x7ffdc5d2b670 hack() A
hhh还挺有意思的。
更复杂的继承以及多继承、虚继承下的内存模型暂时还不了解,以后有空再更新吧。
参考链接:
一个很好用的画图网页: VisionOn