要正确理解这个问题,就要了解C/C++语言的参数传递,以及变量在内存中的存储这两个问题,这两个问题其实也是初学者在C/C++学习很容易出现的问题。
C/C++的参数传递
C/C++规定参数传递用于都是传递数值,而不会把参数的地址传递给子函数。注意不要把指针作为参数混为一谈,以为指针就是传递了参数的地址!本质上指针也是传递的一个数值,初学者一定要理解这点。比如以下两个代码片段:
int fun(int n, int *pn)
{
…
}
int main(void)
{
int a = 5;
int b;
fun(a, &b);
…
}
在函数fun传递a和&b时,是将a的数字(5)和b的地址这个数字传给子函数,虽然在这里第二个参数的意义是一个变量的地址没错,但是在传给fun时,它只认为他是一个数,不会因为加了&符号就把它认为是一个变量的地址。比如b的地址为0x00401234,也就是&b为0x00401234,但是在汇编层面的参数传递(可能放到寄存器中,也可能放到某个内存地址),它只管把0x00401234这个数值传给子函数,不会关心这个数本身是一个地址,还是地址的地址,甚至地址的n次方地址^_^,所以说C/C++传递的永远都是值传递。至于在子函数里面你想怎么理解这个数,那是你在编写子函数的时候决定的,所以C/C++中你可以把指针作为整数进行运算,这也是C/C++灵活的一个方面。
变量在内存中的存储
这个问题网上有大量的文章,程序员互动联盟网站(http://www.coderonline.net)以及以往的文章中也出现过,这个只是强调一下在子函数中申请的变量(包括形参变量)都是在调用子函数是有栈指针ESP直接移动产生的。也就是说当需要一个栈中的变量时,栈指针就一定一个位置,于是留下一个32位的内存空间就作为这个变量的存储空间。这个过程在所有的函数中都一样,也包括主函数。这里需要强调的是每个函数在调用时都有一个栈帧(基地址就是BSP寄存器决定)。所以实际上每一个函数内部的变量内存单元都可能跟其他不一样,因为他们的栈帧都很可能不同,位置也就不同了。至于什么静态变量、全局变量等等就不在这里讨论,因为跟今天的这个问题没有什么关系。
OK,现在两个概念都基本说清楚了,我们来分一下这个问题是怎么出现的。
首先,主函数分配了三个变量c、d、f (a,b,sum作为指针变量在这里意义不大,可以直接传变量的地址),并在调用子函数时把三个变量c、d、f的地址作为数值传递给sun这个函数。根据上面的分析,c、d、f这三个变量的内存单元是在主函数main这个栈帧里面存在。在子函数处理时,定义了一个变量s,这个s在子函数的栈帧中,用于接收形参a和b这个数值分别作为指针所指向的内存单元的值的和。到这里为止,一切都很正常。但是接下来的一句sum = &s这个语句把一切都搞砸了。我们看看sum是主函数传递过来的f的地址这个数值,假设这个数值是0x00401234,但是这里我们看到它把sum重新改写为s的地址,假设为0x00405678,原本sum的数值是一个位于主函数栈帧中的存储单元f的地址,也是希望向这个地址中0x00401234写入结果,自然就能再主函数中访问的,但是现在换成了一个子函数sun栈帧中的一个变量的地址0x00405678。所以在子函数中打印这个sum所指向的地址的值(s)是没有问题的,但子函数已把f的地址偷梁换柱了,执行完后,主函数的f的地址所在的单元0x00401234根本没有被赋过值,所以主函数的f原来是什么还是什么。
理解之后,要正确解决这个问题就是不要覆盖sum这个数,并且把加的结果放到sum这个里面即可,比如*sum = s。
总结起来就是,本来要用sum这个包接收物品,但是子函数却另外拿了一个包替换了这个包,并且往新的包里放了,原来那个包的所有者把包收回去后,当然在包里面没有想要的物品。但是要理解整个过程,需要理解上面的两个方面的C/C++知识。