c语言中函数的调用分为两种方式:传址调用、传值调用。
传值调用我们都知道就是将实参的值传送给被调函数,让被调函数的形参接收这个值,从而形参内存中的数据就变成了实参的一份拷贝。
而传址调用则是将实参的地址传送过去,然后令实参为一指针变量,这个指针变量接收实参的地址,从而使这个指针变量内保存的数据变成实参的地址。
传值调用的功能更加强大,因为它是一个指针,可以通过解引用直接访问到实参。
但是,随着我了解了函数栈帧的开辟以及形参的压栈,我发现了一个问题:
就是这个函数
请问,这个函数里面的形参到底是个什么东西,它真的像它的表面一样,是个数组形参吗? ArrInit函数栈帧空间进行开辟时,压栈的是什么呢?
数组是如何进行传参的?在学校中,我们的老师给我们讲的是:我们可以理解为因为数组名等于首地址,将数组名传送过去之后,形参数组名接受到这个地址,那么两者就共用同一块空间。
是的,老师为了让我们不太混乱,给了我们一个挺好的理解。但是这个理解我现在有点感觉不太对了。首先,数组名是首地址,是一个常量,一个指针常量。它不是变量!这个很重要,数组名一般情况下都是数组的首地址,是一个常量,不能作为一个变量来使用。只有两种情况下数组不是数组的首地址:
第一:使用sizeof计算数组大小时,此时数组名不是首地址,数组名此时就是一个抽象的变量名,类似于int a = 10 中的a这个变量名, 而它所代表的空间就是数组一整块空间。所以sizeof求出来的就是这一整块空间的大小。
第二:&数组名。 &是取地址符号, 取的是一个对象的地址。那么当&加数组名时,取出来的就是一个数组的地址。数组的地址,是一种指针类型,需要用数组指针接收(数组指针,一种情况就是用在此场景,另一种常见场景就是二维数组的传参)
除了以上两种情况,其他情况下数组名都作为首地址进行处理。
带着这个结论,我们再看上面的两个问题
既然数组名等于函数首地址,那么函数调用传参传过去的必定是一个地址。也就是传址调用。那么接收这个地址的必定是一个指针。( 数组名是一个常量,不可能用来接收,所以形参处的数组名不可能与实参的数组名代表同一地址。) 既然是传址调用,那么形参一定是个指针。
所以,我们不妨假设, int farr【10】这里其实不是一个数组,而是一个指针。那么如何证明我们的假设是否成立,我们只要使用sizeof。 使用sizeof进行计算farr这个对象的大小时,假如我们的假设成立,那么sizeof计算出来的就是4 / 8(取决于你的环境)。假如我们的假设不成立, 它如果还是个数组,那么farr身为数组名,使用sizeof进行计算,就一定是40.
现在我们来看一下结果:
结果是8, 我们的假设成立。证明farr确实是一个指针变量。至于为什么形参写成int farr[10]的形式,结果还是一个指针,我认为可能是因为这样:
我们都知道*(arr + i) = arr[i];这里我认为同样可以这样理解,farr【10】其实只是一个指针解引用的形式。而且其实farr【10】里面的这个10,在这里无论是任何数,它都没有语法问题。这其实就说明这个10是一个没有意义的数字。要知道,函数调用的形参是一份拷贝,是要进行压栈的,拷贝的数据越大,占用内存也就越大。传送数组函数进行接收时,必定要创建一个形参,假如创建一个相同的数组,这个空间就占用太大了,而如果是一个指针的话,空间花费就少了太多。而运用方式相差不大。从这个方面考虑,就大概可以理解为什么这个数组的形参默认被降维成为一个指针了。
所以,我对于我之前的疑问就有了一个自己的答案,函数的数组形参其实并不是一个数组。形参因为压栈要进行拷贝占用空间,出于节省的考虑。将形参降维成了一个指针。而压栈的必定也不会是整个数组,本质上只是一个指向实参数组首地址的指针