天天看点

C++虚函数、虚函数表地址

总结自:https://blog.csdn.net/haoel/article/details/1948051/

https://blog.csdn.net/qianghaohao/article/details/51356718

编译器:VS2017

一、虚函数

当我们使用基类的引用或者指针调用一个虚成员函数时会执行动态绑定,直到运行时才知道到底调用了哪个版本的虚函数,被调用的函数是与绑定到指针或者引用上的对象的动态类型相匹配的那一个。所有虚函数都必须有定义。虚函数的作用主要是实现多态机制。

using namespace std;

class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void funA() { cout << "funA of Derive" << endl; }
};

void display(Base* ptr) {
	ptr->funA();
}
int main()
{
	Derive d;
	Base b;
	Base* ptr = &b;
	display(ptr);
        ptr = &d;
        display(ptr);
	return 0;
}
           

上面输出结果为 funA of Base 和 funA of Derive;函数display()接受一个基类指针参数,通过指针调用虚成员函数funA(),当基类指针指向一个基类对象实例时,调用的是基类的成员函数funA();当基类指针指向一个派生类对象实例时,调用的是派生类的成员函数funA()。如果funA()为非虚函数,则上面程序中display()调用的函数都为基类的funA(),因为基类指针只能访问调用基类的成员,即使这个基类指针指向的是一个子类的对象实例。

二、虚函数表

虚函数是通过虚函数表实现的,虚函数表实际上是一个保存类中虚函数地址的数组。

当类中存在虚函数时,这个类的对象实例就会有一个指向虚函数表的指针,同属于一个类的对象共享虚函数表,在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

当通过指针调用类中的虚函数时,会根据指针所指对象的虚函数表地址访问虚函数表,找到相应的虚函数。

using namespace std;

class A {
public:
	void funA() { cout << "funA of Base" << endl; }
	void funB() { cout << "funB of Base" << endl; }
	void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};
class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
	virtual void funB() { cout << "funB of Base" << endl; }
	virtual void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};
int main()
{
	A a;
	Base b;
	cout << "A:" << sizeof(a) << endl;
	cout << "Base:" << sizeof(b) << endl;
	return 0;
}
           

程序输出“A:4”和“Base: 8”,A对象比Base对象小的4个字节存储的就是虚函数表的地址。

下面的程序是通过虚函数表调用虚函数

using namespace std;

class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
	virtual void funB() { cout << "funB of Base" << endl; }
	virtual void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void funA() { cout << "funA of Derive" << endl; }
	virtual void funB() { cout << "funB of Derive" << endl; }
	virtual void funC() { cout << "funC of Derive" << endl; }
};

int main()
{
	Derive d;
	Base b;
	typedef void(*Fun)();
	Fun fA = (Fun)*((int*)*(int*)(&b));
        Fun fC = (Fun)*((int*)*(int*)(&b)+2);
        Fun f = (Fun)*((int*)*(int*)(&d));
	fA();
        fC();
	return 0;
}
           

输出结果为"funA of Base" 、"funC of Base"、"funA of Derive"。即通过函数指针调用虚函数。

说明:

VS2017中虚函数表指针在对象中占四个字节,虚函数指针占四个字节;

(int*)(&b)指向对象实例的int类型指针;

*(int*)(&b)就是虚函数表的地址,int类型占4个字节,解引用int类型指针读取地址前四个字节,(int*)*(int*)(&b)转换为int类型的指针;

(int*)*((int*)*(int*)(&b))为虚函数表第一个虚函数的地址;

(int*)*(int*)(&b)与*(int*)(&b)指向的位置是一样的,(int*)*(int*)*(int*)(&b)与*(int*)*(int*)(&b)指向的位置也是一样的。

暂时这些,后续再补充修改g++下的情况。

继续阅读