C++】透视C++多态:从虚函数表到底层内存布局的完全拆解C++】透视C++多态:从虚函数表到底层内存布局的完全拆解
1. 多态原理下面这段代码中Buy()函数如果传入的是Person调用的就是Person::BuyTicket()传Student调用的是Student::BuyTicket。这样就构成了多态而多态的调用实现是依靠运行时去指向对象的虚表中查调用的函数地址。代码语言javascriptAI代码解释class Person { public: Person(const char* name 张三) :_name(name) {} virtual void BuyTicket() { cout _name 购票,需要排队,每人 100 endl; } protected: string _name; }; class Student : public Person { public: Student(const char* name) :_name(name) {} virtual void BuyTicket() { cout _name 购票,需要排队,每人 50 endl; } private: string _name; }; void Buy(Person* p) { p-BuyTicket(); } int main() { Person p(张三); Buy(p); Student st(张同学); Buy(st); return 0; }通过监视窗口我们可以发现Person指向对象p时p-BuyTicket()在p的虚表中找到虚函数是Person::TicketStudent指向对象st时st-BugTicket在st的虚表中找到虚函数是Student::Ticket通过查找不同的虚函数就实现了不同的对象调用会有不同的行为也就是多态我们再明确一下实现多态的两个条件存在虚函数、需要对象指针或引用调用虚函数通过反汇编窗口可以发现构成了多态之后函数的调用是在运行了程序过程中去对象中取的而不是编译时就决定的如果不是多态函数调用会在编译时就决定好多态调用运行时决议运行时才确定函数的地址普通函数编译时决议编译时确认调用函数的地址2. 动态绑定与静态绑定静态绑定前期绑定编译时就决定了调用哪个函数根据变量或表达式的静态类型决定也就形成了静态多态如函数重载动态绑定后期绑定在程序运行时根据拿到的具体类型来确定函数的具体行为也就形成动态动态如下面这个和上面那个p-BuyTicket()对比就知道动态绑定和静态绑定的区别3. 单继承和多继承中的虚函数表3.1. 单继承中的虚函数表代码语言javascriptAI代码解释class Base { public: virtual void Func1() { cout Base::Func1() endl; } virtual void Func2() { cout Base::Func2() endl; } private: int _b 1; }; class Derive :public Base { public: virtual void Func1() { cout Derive::Func1() endl; } virtual void Func3() { cout Derive::Func3() endl; } virtual void Func4() { cout Derive::Func4() endl; } private: int _d 2; }; int main() { Base b; Derive d; return 0; }通过调试时我们发现似乎有些问题理论上派生类d应该会有三个虚函数继承基类的两个新增加的两个但是我们发现虚表中只有两个指针我们看不到Func3()和Func4()这里是编译器隐藏了这两个函数可以认为是VS的一个Bug。这里我们采用比较底层的方式打印出虚表的指针首先我们明确一点虚函数指针会隐藏的存储在对象内存的开头我们先 b 取地址然后强制转换成三重指针 (void***)(d)这相当于告诉编译器将这块内存看做指向void**的指针然后进行解引用 *(void***)(d)这也就是对象开头的虚函数表指针代码语言javascriptAI代码解释// 打印虚表并执行函数 void PrintVFTable_Safe(void** vtable, int max_entries 10) { cout Virtual Table Address: vtable endl; if (vtable nullptr) { cout Invalid vtable pointer! endl; return; } for (int i 0; i max_entries; i) { // 检查地址是否有效 if (vtable[i] nullptr || (uintptr_t)vtable[i] 0x1000) { cout [ i ]: END OF TABLE endl; break; } cout [ i ]: vtable[i]; // 直接执行函数 typedef void(*FuncPtr)(); FuncPtr func (FuncPtr)vtable[i]; cout - ; func(); // 执行函数 // 安全限制避免无限循环 if (i max_entries - 1) { cout ... (reached max entries) endl; break; } } cout endl; } int main() { Base b; Derive d; void** vtable_b *(void***)(b); void** vtable_d *(void***)(d); cout Base Virtual Table endl; PrintVFTable_Safe(vtable_b); cout Derive Virtual Table endl; PrintVFTable_Safe(vtable_d); return 0; }3.2. 多继承中的虚函数表下面我们给出一段多继承的代码来分析一下代码语言javascriptAI代码解释#include iostream using namespace std; class Base1 { public: virtual void Func1() { cout Base1::Func1() endl; } virtual void Func2() { cout Base1::Func2() endl; } private: int _b1 1; }; class Base2 { public: virtual void Func1() { cout Base2::Func1() endl; } virtual void Func2() { cout Base2::Func2() endl; } private: int _b2 1; }; class Derive :public Base1, public Base2 { public: virtual void Func1() { cout Derive::Func1() endl; } virtual void Func3() { cout Derive::Func3() endl; } private: int _d1 2; }; // 打印虚表并执行函数 void PrintVFTable_Safe(void** vtable, int max_entries 10) { cout Virtual Table Address: vtable endl; if (vtable nullptr) { cout Invalid vtable pointer! endl; return; } for (int i 0; i max_entries; i) { // 检查地址是否有效 if (vtable[i] nullptr || (uintptr_t)vtable[i] 0x1000) { cout [ i ]: END OF TABLE endl; break; } cout [ i ]: vtable[i]; // 直接执行函数 typedef void(*FuncPtr)(); FuncPtr func (FuncPtr)vtable[i]; cout - ; func(); // 执行函数 // 安全限制避免无限循环 if (i max_entries - 1) { cout ... (reached max entries) endl; break; } } cout endl; } int main() { Derive d; void** vtable_d *(void***)(d); PrintVFTable_Safe(vtable_d); return 0; }构成这样一个继承关系我们通过调试先来看一下对象d的虚函数指针然后通过打印的结果我们可看出派生类新增的虚函数一般会储存在第一个继承基类的虚函数表中
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409625.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!