C++学习 - 虚表,虚函数,虚函数表指针学习笔记

http://blog.csdn.net/alps1992/article/details/45052403

虚函数

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

虚表

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

虚函数表指针

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

下面看一段代码:

 1 //
 2 //  main.cpp
 3 //  VirtualTable
 4 //
 5 //  Created by Alps on 15/4/14.
 6 //  Copyright (c) 2015年 chen. All rights reserved.
 7 //
 8 
 9 #include <iostream>
10 using namespace std;
11 
12 class Base{
13 public:
14     virtual void func(){
15         printf("Base\n");
16     }
17     virtual void hunc(){
18         printf("HBase\n");
19     }
20 private:
21     virtual void gunc(){
22         printf("Base Private\n");
23     }
24 };
25 
26 class Derive: public Base{
27 public:
28     virtual void func(){
29         printf("Derive\n");
30     }
31 };
32 
33 class DeriveSecond: public Base{
34 public:
35     void func(){
36         printf("Second!\n");
37     }
38 };
39 
40 class DeriveThird: public Base{
41 };
42 
43 class DeriveForth: public Base{
44 public:
45     void gunc(){
46         printf("Derive Forth\n");
47     }
48 };
49 
50 int main(int argc, const char * argv[]) {
51     Derive d;
52     Base *pb = &d;
53     pb->func();
54     // 1  输出:Derive
55 
56     DeriveSecond sec;
57     pb = &sec;
58     pb->func();
59     // 2 输出:Derive Second
60 
61     DeriveThird thi;
62     pb = &thi;
63     pb->func();
64     //3 输出:Base
65 
66     DeriveForth forth;
67     pb = &forth;
68 //    pb->gunc();
69     // 4 报错
70     return 0;
71 }

 

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

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

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

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

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

虚表详解

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

 1 #include <string>
 2 #include <iostream>
 3 
 4 class Object {
 5   int identity_hash_;
 6 
 7 public:
 8   Object(): identity_hash_(std::rand()) { }
 9 
10   int IdentityHashCode() const     { return identity_hash_; }
11 
12   virtual int HashCode()           { return IdentityHashCode(); }
13   virtual bool Equals(Object* rhs) { return this == rhs; }
14   virtual std::string ToString()   { return "Object"; }
15 };
16 
17 class MyObject : public Object {
18   int dummy_;
19 
20 public:
21   int HashCode() override           { return 0; }
22   std::string ToString() override   { return "MyObject"; }
23 };
24 
25 int main() {
26   Object o1;
27   MyObject o2;
28   std::cout << o2.ToString() << std::endl
29             << o2.IdentityHashCode() << std::endl
30             << o2.HashCode() << std::endl;
31 }
32 
33 /*
34               Object                      vtable
35                                -16 [ offset to top     ]  __si_class_type_info
36                                -8  [ typeinfo Object   ] --> +0 [ ... ]
37 --> +0  [ vptr           ] --> +0  [ &Object::HashCode ]
38     +8  [ identity_hash_ ]     +8  [ &Object::Equals   ]
39     +12 [ (padding)      ]     +16 [ &Object::ToString ]
40 
41              MyObject                     vtable
42                                -16 [ offset to top       ]  __si_class_type_info
43                                -8  [ typeinfo MyObject   ] --> +0 [ ... ]
44 --> +0  [ vptr           ] --> +0  [ &MyObject::HashCode ]
45     +8  [ identity_hash_ ]     +8  [ &Object::Equals     ]
46     +12 [ dummy_         ]     +16 [ &MyObject::ToString ]
47 
48 */

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

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

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

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

虚表和虚函数地址

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

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class animal
 5 {
 6 protected:
 7   int age_;
 8   animal(int age): age_(age) { }
 9 
10 public:
11   virtual void print_age(void) = 0;
12   virtual void print_kind() = 0;
13   virtual void print_status() = 0;
14 };
15 
16 class dog : public animal
17 {
18 public:
19   dog(): animal(2) { }
20   ~dog() { }
21 
22   virtual void print_age(void) {
23     cout << "Woof, my age = " << age_ << endl;
24   }
25 
26   virtual void print_kind() {
27     cout << "I'm a dog" << endl;
28   }
29 
30   virtual void print_status() {
31     cout << "I'm barking" << endl;
32   }
33 };
34 
35 class cat : public animal
36 {
37 public:
38   cat(): animal(1) { }
39   ~cat() { }
40 
41   virtual void print_age(void) {
42     cout << "Meow, my age = " << age_ << endl;
43   }
44 
45   virtual void print_kind() {
46     cout << "I'm a cat" << endl;
47   }
48 
49   virtual void print_status() {
50     cout << "I'm sleeping" << endl;
51   }
52 };
53 
54 void print_random_message(void* something) {
55   cout << "I'm crazy" << endl;
56 }
57 
58 int main(void)
59 {
60   cat kitty;
61   dog puppy;
62   animal* pa = &kitty;
63 
64   intptr_t* cat_vptr = *((intptr_t**)(&kitty));
65   intptr_t* dog_vptr = *((intptr_t**)(&puppy));
66 
67   intptr_t fake_vtable[] = {
68     dog_vptr[0],         // for dog::print_age
69     cat_vptr[1],         // for cat::print_kind
70     (intptr_t) print_random_message
71   };
72   *((intptr_t**) pa) = fake_vtable;
73 
74   pa->print_age();    // Woof, my age = 1
75   pa->print_kind();   // I'm a cat
76   pa->print_status(); // I'm crazy
77 
78   return 0;
79 }

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

虚表地址和虚函数地址

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

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

posted on 2018-03-06 21:33  Shihu  阅读(346)  评论(0编辑  收藏  举报

导航