设为首页 加入收藏

TOP

C++学习 - 虚表,虚函数,虚函数表指针学习笔记
2015-11-21 01:04:50 来源: 作者: 【 】 浏览:1
Tags:学习 虚表 函数 指针 笔记

虚函数

虚函数就是用virtual来修饰的函数。虚函数是实现C++多态的基础。

虚表

每个类都会为自己类的虚函数创建一个表,来存放类内部的虚函数成员。

虚函数表指针

每个类在构造函数里面进行虚表和虚表指针的初始化。

下面看一段代码:

//
//  main.cpp
//  VirtualTable
//
//  Created by Alps on 15/4/14.
//  Copyright (c) 2015年 chen. All rights reserved.
//

#include 
   
     using namespace std; class Base{ public: virtual void func(){ printf("Base\n"); } virtual void hunc(){ printf("HBase\n"); } private: virtual void gunc(){ printf("Base Private\n"); } }; class Derive: public Base{ public: virtual void func(){ printf("Derive\n"); } }; class DeriveSecond: public Base{ public: void func(){ printf("Second!\n"); } }; class DeriveThird: public Base{ }; class DeriveForth: public Base{ public: void gunc(){ printf("Derive Forth\n"); } }; int main(int argc, const char * argv[]) { Derive d; Base *pb = &d; pb->func(); // 1 输出:Derive DeriveSecond sec; pb = &sec; pb->func(); // 2 输出:Derive Second DeriveThird thi; pb = &thi; pb->func(); //3 输出:Base DeriveForth forth; pb = &forth; // pb->gunc(); // 4 报错 return 0; }
   

在这个里面我创建了一个基类Base还有其他派生类。

首先// 1部分,表示了虽然我们声明的是一个Base类的指针,但是指向的是派生类的实例,所以调用的就是派生类的函数。

其次// 2部分,表示的和1差不多,只不过在// 2里不是虚函数了,覆盖了父类的虚函数。但还是存放在派生类的虚表里。

// 3的代码里可以看到,派生类没有覆盖父类的虚函数的时候,虽然指向的是派生类的实例,但是调用的是父类的方法,是因为在继承时候,子类也有一个虚表,里面存放了父类的虚函数表。

// 4里是私有的虚函数是不能直接被外部调用的。

虚表详解

先看如下代码:代码来源:RednaxelaFX,编程语言厨此人我觉得很厉害,这里借用一下他的代码,无任何商用,如果有问题,请联系我删除。

#include 
   
     #include 
    
      class Object { int identity_hash_; public: Object(): identity_hash_(std::rand()) { } int IdentityHashCode() const { return identity_hash_; } virtual int HashCode() { return IdentityHashCode(); } virtual bool Equals(Object* rhs) { return this == rhs; } virtual std::string ToString() { return "Object"; } }; class MyObject : public Object { int dummy_; public: int HashCode() override { return 0; } std::string ToString() override { return "MyObject"; } }; int main() { Object o1; MyObject o2; std::cout << o2.ToString() << std::endl << o2.IdentityHashCode() << std::endl << o2.HashCode() << std::endl; } /* Object vtable -16 [ offset to top ] __si_class_type_info -8 [ typeinfo Object ] --> +0 [ ... ] --> +0 [ vptr ] --> +0 [ &Object::HashCode ] +8 [ identity_hash_ ] +8 [ &Object::Equals ] +12 [ (padding) ] +16 [ &Object::ToString ] MyObject vtable -16 [ offset to top ] __si_class_type_info -8 [ typeinfo MyObject ] --> +0 [ ... ] --> +0 [ vptr ] --> +0 [ &MyObject::HashCode ] +8 [ identity_hash_ ] +8 [ &Object::Equals ] +12 [ dummy_ ] +16 [ &MyObject::ToString ] */
    
   

这里最主要的是我认为R大的这个虚表画的实在是好看。所以直接借用了,一看就比我上面自己写的代码好看多了(T T)。

首先我们学习的时候,可以暂时先无视小于0的虚表内容。从+0开始存放了vptr这个虚表指针指向了类的虚表。可以很清楚的看到在MyObject的虚表里其中HashCode 和 ToString函数已经是派生类的虚函数了,把父类的函数重写了。

所以这两个R大画的类已经很清楚的说明了类的虚表虚函数的操作。

那么有没有比较暴力的办法强行自己来控制虚表呢。其实这个来源于当时我做的一个阿里笔试题,做完当天我就看到知乎的R大已经做了详细的解释,这里还是引用他的代码好了。

虚表和虚函数地址

以下代码同出自R大之手:RednaxelaFX,编程语言厨

#include 
   
     using namespace std; class animal { protected: int age_; animal(int age): age_(age) { } public: virtual void print_age(void) = 0; virtual void print_kind() = 0; virtual void print_status() = 0; }; class dog : public animal { public: dog(): animal(2) { } ~dog() { } virtual void print_age(void) { cout << "Woof, my age = " << age_ << endl; } virtual void print_kind() { cout << "I'm a dog" << endl; } virtual void print_status() { cout << "I'm barking" << endl; } }; class cat : public animal { public: cat(): animal(1) { } ~cat() { } virtual void print_age(void) { cout << "Meow, my age = " << age_ << endl; } virtual void print_kind() { cout << "I'm a cat" << endl; } virtual void print_status() { cout << "I'm sleeping" << endl; } }; void print_random_message(void* something) { cout << "I'm crazy" << endl; } int main(void) { cat kitty; dog puppy; animal* pa = &kitty; intptr_t* cat_vptr = *((intptr_t**)(&kitty)); intptr_t* dog_vptr = *((intptr_t**)(&puppy)); intptr_t fake_vtable[] = { dog_vptr[0], // for dog::print_age cat_vptr[1], // for cat::print_kind (intptr_t) print_random_message }; *((intptr_t**) pa) = fake_vtable; pa->print_age(); // Woof, my age = 1 pa->print_kind(); // I'm a cat pa->print_status(); // I'm crazy return 0; }
   

我们可以看到R大干了什么!!丧心病狂的把vtable自己伪造了一个,然后放到虚表指针后面!简直佩服。看到这个代码我也是才明白,虚表可以这么操作。

虚表地址和虚函数地址

虚函数表的地址(int*)&classname)与虚函数的地址(int*)*(int*)(&classname)实际按照R大的说法,这里的int应该改成intptr_t才更好,这样能够防止在LP64模型下,函数指针是8个字节。而地址获取不全。

虚函数表的地址和虚函数地址的关系类似于: x 和 *x的关系。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇HDU Non-negative Partial Sums .. 下一篇poj 1284 Primitive Roots 求素数..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: