C++联编与虚函数表
1.联编
联编:将源代码中的函数调用解释为执行特定的函数代码块叫函数名联编 也就是说:确定执行函数调用会执行哪一个函数
I>静态联编(早期联编)
编译期间,决定调用哪个函数,普通的非虚函数采用静态联编
II>动态联编(晚期联编)
程序运行期间,决定调用哪个函数,虚函数采用动态联编
2.虚函数机制实现——虚函数表
I>虚函数表的内容
虚函数表是一个函数指针数组,数组中存储的指针指向要调用的虚函数
II>存在虚函数的类中的隐藏成员
若一个类中有虚函数,则该类会有一个隐藏的参数(指向虚函数表的指针)
class A1 {
};
class A2 {
public:
virtual void f() { cout << "A::f()" << endl; }
virtual void g() { cout << "A::g()" << endl; }
};
int main() {
cout << "sizeof(A1) : " << sizeof(A1) << endl;
cout << "sizeof(A2) : " << sizeof(A2) << endl;
return 0;
}
输出:
sizeof(A1) : 1
sizeof(A2) : 4
类对象单元存储了成员变量,A1由于没有成员变量,在创建对象时,分配1个字节表示一个对象,主要是让这个成员的this指针有指向;A2也没有成员变量,它的对象单元应该也占一个字节,但是,这里却占了4个字节,说明有隐藏的成员变量,这个隐藏的成员就是指向虚函数表的指针
III>虚函数表的更新
- 派生类会继承基类的虚函数表(基类声明的虚函数,派生类中默认为虚函数)
- 派生类如果重定义虚函数,则修改虚函数表的指针
- 派生类如果声明新的虚函数,则在虚函数表中添加新的指针
基类指针(或引用)指向派生类对象,可以通过虚函数表,调用派生类对象的虚函数
注意:只能调用派生类中声明过的虚函数,因为虚函数的声明个数决定了虚函数表的长度,不能越界访问
IV>通过虚函数表调用虚函数
声明这样的两个类
class A {
public:
virtual void f();
virtual void g();
};
void A::f() { cout << "A::f()" << endl; }
void A::g() { cout << "A::g()" << endl; }
class B : public A {
public:
void f();
};
void B::f() { cout << "B::f()" << endl; }
通过上面的例子得到当前环境地址所占字节数:4B,也就是说,函数指针也占4B
第一步:获取虚函数表首地址(vptr):
由于,没有其他成员,那么唯一的成员就是虚函数表首地址。
由于,指针占4B,同过int型指针可以取出4B的数据(当前环境int也占4B) 得到vptr的值:
B objb;
int * vptr = (int *)(*(int *)(&objb));
//将objb的地址转为int *,以便取出4个字节的首地址
//通过解引用的到vptr,因为函数指针也占4字节,所以转成int *
第二步:通过虚函数表中的函数地址调用函数:
void(*function)() = (void(*)())vptr[0];
//vptr[0]取出地址
//类型转换为返回值为void的函数指针
//给function赋值
第三部:通过指针调用函数:
for(int i = 0; i < 2; ++i){
void(*function)() = (void(*)())vptr[i];
function();
}
//可以直接使用地址调用:
for(int i = 0; i < 2; ++i)
((void(*)())vptr[i])();