函数实际上就是一个普通的地址,而这个地址如何起作用,那就是编译器解析的工作。
举一个例子:
- typedef void (* FuncType)(int);
-
-
...
-
-
void func(int){}
-
-
...
-
-
FuncType pFunc = (FuncType)&func;
-
pFunc(1);
在执行pFunc(1)的时候,编译器去查看pFunc这个指针是什么类型,他发现是void(*)(int)类型(注意:这里是默认的__cdecl的调用类型),好,那就生成一个push指令,压入参数1,然后执行call指令,jmp到pFunc指向的地址(也就是func的地址),当jmp到的地址执行完了过后,因为是__cdecl调用类型,所以将栈指针+4,这样整个的过程结束。
上面的调用过程的伪指令执行过程如下所示:
push 1 // push the parameter for the function invoked
call *pFunc // call instruction will store the eip, and jump to the address pointed by pFunc
-----> push ebp // store the old stack frame pointer
mov ebp, esp // set current stack frame pointer
....
pop ebp
<----- ret
add esp, 4 // restore the stack pointer
而这里要阐述的是,编译器不会去查看这个地址是否有效,或者这个地址是否真的就是这个函数类型。编译器只管根据你提供的类型,如果指针没有问题(是一个合法的地址),那就跳转到到那个地址去,后面的过程怎么样就看这个地址指向的那段内容怎么样了。
既然函数指针就是一个普通的指针,那就可以像一般的指针一样的对待。
可以把一个函数指针强制转到其他内置类型或者用户定义类型的指针,或者其他的函数类型指针,反过来一样。
比如:
type void (*Func)(int);
Func pFunc = (Func) any_type_pointer;
或者
AnyType* pOther = (AnyType*) pFunc;
这里的any_type_pointer是任意类型的指针,比如void*, char*, ClassType*或者其他的函数类型,但是不能为成员函数的指针; AnyType可以是任意类型,比如上面列写的类型,但是同样不能使成员函数指针。
所以函数指针是可以任意转换存储在变量中,像在使用DLL的时候,在程序执行的时候加载一个DLL,这会将一段执行代码映射到进程空间中,然后通过函数的名称符号得到一个地址,再显式转换这个地址为对应的函数类型进行调用。这个过程和上面的过程是类似的。
尽管函数指针可以相互的转换,在通过(*pFunc)(arg)调用函数的时候,pFunc的函数类型必须和实际执行的那段代码的函数类型要对应(包括__cdecl和__stdcall类型),否者会因为栈帧的不匹配,而导致栈破坏。
成员函数指针
成员函数和一般函数不同在于它有一个隐式的this指针传入,其他性质就和一般函数指针很相似。但是成员函数指针不能和一般函数指针相互转换。而成员函数指针可以相互转换,甚至于两个不同类之间的成员函数指针转换。
比如:
- class A
-
{
-
public:
-
void func(){cout<<"A";}
-
};
-
class B
-
{
-
public:
-
void func(){cout<<"B";}
-
};
-
-
...
-
-
typedef void(B::* BMemFunc)();
-
BMemFunc pMemPtr = (BMemFunc)&A::func;
-
B b_obj;
-
(b_obj.*pMemPtr)(); // Where to go ?
在执行(b_obj.*pMemPtr)(),将会执行到A::func()中去,也就是打印出A。
而在A::func()中的隐式this指针是什么?实际就是b_obj的地址。而为什么上面的代码没有崩溃,那因为在A::func()中没有涉及到对对象成员的访问。就像((A*)NULL)->func()这段代码不会崩溃是一个道理。
上面的过程和一般函数的执行过程是类似的,不同的就是有一个this指针的传递。
在这样的一种情况下,this指针是原原本本的调用者对象的地址,没有编译器的对指针进行偏移的行为(这里的偏移行为是指类似于在多继承的情况下往不同父类进行类型转换时,编译器产生的指针修正行为)。这一点要注意。
利用上面这一点,可以用来实现编译期的一种多态决策行为。
- class IClass
-
{
-
public:
-
void func(Arg arg)
-
{
-
(this->*pFunc)(arg);
-
}
-
public:
-
typedef void(IClass::* MemFunc)(Arg);
-
static MemFunc pFunc;
-
};
-
-
class CClass : public IClass
-
{
-
public:
-
void func()
-
{
-
// do something
-
}
-
};
-
-
IClass::MemFunc IClass::pFunc = (IClass::MemFunc)&CClass::func; // Set up the "virtual function" pointer
上面的代码中CClass可以利用IClass作为接口调用,而实际调用都会调用到CClass自己定义的函数中去,但是对于任意的CClass对象都没有一个额外的像v_ptr的开销(在C++多态中,每一个对象都有一个成员指针,这个指针指向虚表)。
但是上面方法不能在多继承情况使用,原因在前面有提到。