高分求运行结果和原因(函数指针)
#include <iostream>
using namespace std;
int FunA(int , int);
void FunB(int* pA);
int main(int argc, char* argv[])
{
int(*p)(int,int);
p = FunA;
int nA = (*p)(1,2);
cout < < "nA= " < < nA < < endl;
typedef int(*pA)(int,int);
p = (pA)FunB;
int nB = (*p)(7,4);
cout < < "nB= " < < nB < < endl;
return 0;
}
int FunA(int nA, int nB)
{
return nA + nB;
}
void FunB(int* pA)
{
*pA++;
}
结果如何,并说出为什么?第一个答对给分!!!!
[解决办法]
看大家争论得这么激烈,我也说上两句:
你的代码,出现问题的根源在于使用了C风格的强制类型转换:
typedef int(*pA)(int,int);
p = (pA)FunB;
我们看一下,这两行代码,很显然函数指针pA就是p的类型。
对于C风格的强制类型转换,编译器不会进行类型检查,按照指定类型给你转换,这时候
编译器很相信你(例如long到int的强制转换,那么你只得到long类型变量的低sizeof
(int)个字节),
那么在这里呢,p现在指向的地址的确是FunB的首地址,也就是说当你通过函数指针p调用
时,执行的是FunB的函数体,这点相信大家都清楚。
那么参数呢,编译器会如何处理,既然你告诉了编译器,参数是两个整型,所以编译器会
根据这个做参数类型检查,然后传递参数。
那么如何传递参数呢?对于这个传递过程是通过实参压栈(函数调用前,编译器给你安排
的代码)、出栈(函数体前面,编译器给你安排插入的代码)来实现的,但是这里有一点
还要提到的就是,不同的实现对于压栈的顺序是不同的,这也就是为什么我们不能依靠参
数的实行顺序类进行计算(例如(i++,i),结果未定义)。所以
int nB = (*p)(7,4);执行时。这就牵涉到7和4作为参数压栈(我的Dev-C++4.9.9.2上,
是4先,7后),然后执行FunB函数体FunB的参数值(即int *pA),恰好它也是占用int大
小的空间,所以在我的实现上得到的是pA==7,
这个地址大家知道肯定是非法的(因为这种小地址内存肯定被系统占用了,你如果用,那
就是访问非法,异常终止)。
然而你会说在我这里能运行,而且能得到结果。
哈哈,我的也是,但是如果你将FunB改为如下
void FunB(int* pA)
{
cout < < "pA = " < < pA < < endl;
cout < < "*pA = " < < *pA < < endl; //这里有问题了!
int b = *pA++;
cout < < "b = " < < b < < endl; //这里导致上一行有问题了!
}
你在运行看看,那就是访问违例,异常终止!
为什么呢:
首先看一下原先的代码,实际上编译器都会对函数做一些优化,最典型的就是RV,即返回
值优化,所以你的调用实际上:
int nB; //注意未初始化,为什么?嗬嗬局部变量,所以。。
int _temp_nB_002; //返回值优化通过它保存返回值
{//加上或括号是进入一个局部作用域
//压栈,出栈
int *_temp_pA_001 = pA, pA+=1, *_temp_pA_001;
}
nB = _temp_nB_002; //注意!
当然我这里是表示一个执行过程,实际上复杂得多。
再看看,实际上nB没有在内部被赋值(因为没有return语句,所以返回值优化无法给
_temp_nB_002正确的值)
所以你得到的nB值实际上是随即的(没有初始化)。
那为什么没有访问违例,异常终止?
问得很好,那是因为,编译器发现int *_temp_pA_001 = pA, pA+=1, *_temp_pA_001;根
本没有意义,因为变量定义了后面没有使用,大多的编译器会对这种情况进行优化,所以
经过优化的代码实际上就没有了这些操作。
但是当我们加上
cout < < "*pA = " < < *pA < < endl;
这时候,的确是要对pA指向的内存进行访问了,所以这时候问题就出现了!
同样的道理:
cout < < "b = " < < b < < endl; //这里导致上一行有问题了!
这一行如果没有,那么
int b = *pA++;
也是没问题的,因为编译器给它优化掉了!