本文预览:
- 关于vptr(虚函数表指针)和vtbl(虚函数表)
- 关于this指针
- 关于Dynamic Binding(动态绑定)
- new delete操作符重载
关于vptr(虚函数表指针)和vtbl(虚函数表)
虚函数表指针和虚函数表是C++实现多态的核心机制,理解vtbl和vptr的原理是理解C++对象模型的重要前提。
class里面method分为两类:virtual 和non-virtual。非虚函数在编译器编译是静态绑定的,所谓静态绑定,就是编译器直接生成JMP汇编代码,对象在调用的时候直接跳转到JMP汇编代码执行,既然是汇编代码,那么就是不能在运行时更改的了;虚函数的实现是通过虚函数表,虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址,通过虚函数表在调用的时候才最终确定调用的是哪一个函数,这个就是动态绑定。
class的内部有一个virtual函数,其对象的首个地址就是vptr,指向虚函数表,虚函数表是连续的内存空间,也就是说,可以通过类似数组的计算,就可以取到多个虚函数的地址,还有一点,虚函数的顺序和其声明的顺序是一直的。
通过虚函数表来调用虚函数,绕过private的限制:
typedef void(*Fun)(void);
class Base {
private:
virtual void f() {cout<<"Base::f()"<<endl;}
virtual void g() {cout<<"Base::g()"<<endl;}
virtual void h() {cout<<"Base::h()"<<endl;}
}
int main()
{
Base b;
Fun fp = nullprt;
for(int i = 0; i < 3; i++)
{
fp = (Fun)*((long*)*(long*)(&b) + i);
fp();
}
}
运行结果:
Base::f()
Base::g()
Base::h()
需要注意的是,由于我的电脑是64位的系统,vptr转成long,八个字节,32位的int就可以了,这个根据自己的环境去修改就可以了。&b取vptr,转成long*,取出来是vtbl ,同样需要转成八字节,不然在指针偏移的时候肯定就错了,也就是+i,虚函数地址取出来要转换成可执行的函数指针,这样即使在class声明的时候做了private限制,在指针面前直接就绕过去了。
关于this指针
上一张很久之前的图了:
每一个成员函数都有一个默认参数,那就是this,this代表对象本身,但是this究竟是什么呢?this就是对象的地址。
关于Dynamic Binding(动态绑定)
怎么理解动态绑定和静态绑定,一般来说,对于类成员函数(不论是静态还是非静态的成员函数)都不需要创建一个在运行时的函数表来保存,他们直接被编译器编译成汇编代码,这就是所谓的静态绑定;所谓动态绑定就是对象在被创建的时候,在它运行的时候,其所携带的虚函数表,决定了需要调用的函数,也就是说,程序在编译完之后是不知道的,要在运行时才能决定到底是调用哪一个函数。这就是所谓的静态绑定和动态绑定。
参考: C++this指针-百度百科
动态绑定需要三个条件同时成立:
1 指针调用
2 up-cast (有向上转型,父类指针指向子类对象)
3 调用的是虚函数
通过两张图看看汇编代码:
a.vfunc1()调用虚函数,那么a调用的是A的虚函数,还是B的虚函数?对象调用不会发生动态绑定,只有指针调用才会发生动态绑定。120行下面发生的call是汇编指令,call后面是一个地址,也就是函数编译完成之后的地址了。
再看第二张:
up-cast、指针调用、虚函数三个条件都满足动态调用,call指令后面不再是静态绑定简单的地址,翻译成C语言大概就是(*(p->vptr)[n](p))
,通过虚函数表来调用函数。
new delete操作符重载
举个例子:
String* s = new String("hello world");
new 操作符主要干了两件事
- 调用operator new分配内存空间
- 调用构造函数
这个在之前的文章中C++如何设计一个类2(含指针的类)将过,这里写的就简单一些了。
这里我们要重载operator new,需要注意的是我们可以重载全局和成员操作符,两个影响范围是不一样的。
void* myMalloc(size_t size)
{
void* p = malloc(size);
std::cout<<"myMalloc()"<<std::endl;
return p;
}
void myFree(void* ptr)
{
free(ptr);
std::cout<<"myFree()"<<std::endl;
}
//会覆盖掉前面,影响范围是全局的
void* operator new(size_t size){return myMalloc(size);}
void operator delete(void* ptr) { myFree(ptr);}
class Apple{
public:
Apple(){std::cout<<"Apple::Apple()"<<std::endl;}
void* operator new(size_t size){std::cout<<"+++++++"<<std::endl; return myMalloc(size);}
void operator delete(void* ptr) {std::cout<<"-------"<<std::endl;return myFree(ptr);}
};
int main(int argc, const char * argv[]) {
//调用class重载的
Apple* apple = new Apple;
delete apple;
//强制调用全局的
Apple* app = ::new Apple;
::delete app;
return 0;
}
new[] 和delete[]是一个道理。