关于c++成员函数指针的声明,类型定义,赋值和调用就不再赘述了,需要了解的朋友可以看这篇文章:
http://www.cppblog.com/colys/archive/2009/08/18/25785.html
写这篇文章的目的在于看到有文章说c++的成员函数指针并不是普通函数指针,现在我要证明,在我的编译环境中,
所看到的c++的成员函数指针就是普通的函数指针。
这里我介绍一下我“看”到的c++成员函数指针。
首先介绍一下我的编译环境:vs2013 win7系统 x86处理器
先看成员函数指针指向非虚成员函数时的情况
现在来看下测试代码
#include "stdafx.h"
#include <iostream>
class BaseClass
{
public:
BaseClass(){}
~BaseClass(){}
int calculate(int a, int b)
{
return a - b;
}
};
class ChildClass : public BaseClass
{
public:
ChildClass(){}
~ChildClass(){}
int calculate(int a, int b)
{
return a + b;
}
};
typedef int (BaseClass::*Func)(int, int);
int _tmain(int argc, _TCHAR* argv[])
{
Func func;
func = &(BaseClass::calculate);
BaseClass test;
printf("%d", (test.*func)(3, 2));
system("pause");
return 0;
}
上面的代码的main函数中,成员函数指针func指向非虚成员函数BaseClass::calculate的首地址,所以(test.*func)(3, 2)等价于test.calculate(3, 2),printf打印出的结果为1,现在我要将test的类型改为ChildClass,修改后的主函数如下:
int _tmain(int argc, _TCHAR* argv[])
{
Func func;
func = &(BaseClass::calculate);
ChildClass test;
printf("%d", (test.*func)(3, 2));
system("pause");
return 0;
}
现在打印的结果仍为1,(test.*func)(3, 2)等价于test.BaseClass::calculate(3, 2),相当于派生类的对象调用了基类的成员函数,所以在成员函数指针指向非虚成员函数时,其本质就是指向非虚成员函数的首地址。通过反汇编VS生成的目标文件(.obj)亦可证明这一点。
接下来我们来看成员函数指针指向虚函数时的情况
继续看测试代码
#include "stdafx.h"
#include <iostream>
class BaseClass
{
public:
BaseClass(){}
~BaseClass(){}
virtual int calculate(int a, int b)
{
return a - b;
}
};
class ChildClass : public BaseClass
{
public:
ChildClass(){}
~ChildClass(){}
virtual int calculate(int a, int b)
{
return a + b;
}
};
typedef int (BaseClass::*Func)(int, int);
int _tmain(int argc, _TCHAR* argv[])
{
Func func;
func = &(BaseClass::calculate);
ChildClass test;
printf("%d", (test.*func)(3, 2));
system("pause");
return 0;
}
相比之前,BaseClass::calculate被声明为一个虚函数,成员函数指针fun指向虚函数BaseClass::calculate,(test.*func)(3, 2)等价于test.calculate(3, 2),printf打印结果为5,我们知道c++在调用非静态成员函数时,会传入一个参数——对象的指针this,将其保存在ecx寄存器中,当成员函数需要访问非静态成员变量时,就根据传入的this指针和非静态成员变量相对对象的内存偏移找到存储该成员变量的内存空间, 而this指针指向的地址中,存储着虚函数表的首地址,即对象内存空间的最开始的位置存放着虚函数表的首地址,当调用虚函数时,往往通过传入的this指针找到虚函数表,再通过虚函数表和对应虚函数相对虚函数表的偏移找到对应虚函数的首地址,并跳转到对应虚函数, test.calculate(3, 2)实际调用的函数取决于test中的虚函数表,即取决于传入的this指针。
以上说了这么多,只是证明成员函数指针也具有多态特性。
那当成员函数指针指向虚函数时,它实际指向的是什么地址呢,这里我通过IDA(反汇编工具)查看了使用测试代码生成的目标文件(.obj)的汇编代码。
成员函数指针指向的函数
得出的结论如下:
在windows上用IDA(反汇编器)反编译包含main函数的目标文件(.obj),发现当c++的类成员函数指针引用一个非虚函数时,该成员函数指针指向非虚成员函数的首地址;但当c++的类成员函数指针指向一个虚函数时,该成员函数指向一个编译器临时生成的寻址函数(假设为findAddr)的首地址,findAddr函数只有两条命令
1 从ecx寄存器中取得this指针,再对this指针取内容即虚函数表的首地址放入eax寄存器
2 通过虚函数表的首地址加上偏移取得对应虚函数地址,跳转到对应虚函数
注意,findAddr并不返回,使用jump跳转到对应虚函数,传递虚函数所需参数在调用findAddr之前
同时,我发现this指向地址存储的内容是虚函数表的首地址
[this] -> 虚函数表的地址
[虚函数表的地址] -> 第一个虚函数的地址
[虚函数表的地址+sizeof(void*)] -> 第二个虚函数的地址
....
当链接器链接时,链接器可能会去查虚函数表,生成的可执行文件中将成员函数指针赋值为指向虚函数时,就把所对应的虚函数的首地址直接赋给成员函数指针,所以可能需要通过反汇编目标文件才能看到这个过程。
后来看到有的文章说成员函数指针包含了this指针调整的信息,所占内存空间会大于一个指针的大小,在我的编译环境中并没有发现这种情况,当出现需要调整this指针时,编译器根据调用的成员函数生成调整this指针的信息,但并没有将其存储在成员函数指针的内存空间或其他内存空间中,在目标文件的汇编代码中以立即数的形式表现出来。
转载请注明出处,如有错误与缺漏之处,还请不吝指正。