函数指针和函数编译

来源:互联网 发布:大道寺知世情侣头像 编辑:程序博客网 时间:2024/05/22 17:32

函数指针和函数编译

首先要了解一个概念 函数的类型, 函数的类型由函数的返回值和参数列表决定。 比如

函数:

void fun1 (void);
void fun2 (viod);
这个时候我们说fun1,fun2是相同的函数类型。

现在我们来声明一个函数指针

void (*pf)(void)

void                        (*pf)                    (void)
函数返回类型       pf函数指针名     函数参数
(注意这个(*pf)括号, 如果没有就变成了 void * pf (void),这就是一个普通返回一个void*类型的函数了。这里*是表示pf是指针,我们比较一下函数和函数指针的声明会发现他们唯一的区别就是多了这个* )

其实函数指针的声明和我们普通变量的声明是一样的,我们可以把pf就看成一个普通的指针,就像
int * pn
只是这里函数指针的定义不是我们常见的   “数据类型 *变量名”   的格式。


同样的我们看
typedef void (*pftype) (void)
也是定义了一个pftype的函数指针的类型。之后我们可以声明
pftype pf;

理解函数指针之后我们来看函数指针的赋值。
由于pf和fun1,fun2具有相同的函数类型,所以我们赋值:
void main()
{
 pf = fun1;
 pf = fun2;
}

我们看到fun1,fun2声明的是函数,而pf声明的是函数指针。他们怎么可以赋值呢?就像我们怎么可以
int a = 0;
int *pa = a  // error
关于这个问题在c++primer(第3版)中的说法是当一个函数名没有倍调用操作符修饰的时候会被解析成只想该类型函数的指针,就像数组名会被解析成首元素的指针一样,所以可以
pf = fun // fun被解析成指向该函数的指针

同时还提到pf可以这样被赋值
pf = &fun
因为这个时候pf和&fun的类型是一样的。

但这里有个问题是假如把函数看成是一个普通变量,一个普通变量的值(fun)怎么可能和他的地址(&fun)相等?

用vc的反汇编来可以看到,其实
pf = fun;
是把一个常量地址(0040100a)赋值给了pf:

所以更本没有fun这个变量,所有的函数在编译的时候就已经被解析成一个地址。函数调用也是直接call到这个地址

所以函数指针其实就是一个指向函数地址的普通指针。只是他的声明和我们常见的变量声明格式有点不太一样而已。

他的调用就和普通的函数一样

pf();

我们还可以看到pf(0040100a)的值和call(0040100a)的值与真正fun(0x0040b450)的值并不一样:

我们继续单步调试并进入call的地方(0040100a),这时可以看到在0040100a只有一个jump,并且是jump到fun值(0x0040b450),这才是函数真正开始的地方。

我们再来看一下函数被编译,调用的过程。

假如有程序

/* 1 */  void fun()
/* 2 */
/* 3 */  int main()
/* 4 */  {
/* 5 */     fun();
/* 6 */  }
/* 7 */
/* 8 */  void fun()
/* 9 */  {
/* 10*/    printf( "you are fooled!/n" );
/* 11*/  }

函数main在调用函数fun的时候还只看到fun的声明,函数fun的定义可能在main调用fun的后面,当程序编译到调用fun的时候怎么知道该call到什么地方呢?

程序在编译的时候首先根据函数声明或定义(1,3行)把所有函数编译成一个固定连续的地址(0040100a),程序需要调用函数的时候就call这些地址,但函数fun真正开始的地方现在还并不知道,在编译到函数fun的定义之前并不知道。到连接的时候所有的函数都已经被编译完知道函数真正开始的地方(0x0040b450),这个时候在(0040100a)的地方jump到函数真正的地方(0x0040b450),并生成一个完整的exe。