参数
C语言函数调用中参数分为形参(formal parameter)和实参(actual parameter)
- 形参:定义函数时候使用的参数,用来接收调用该函数时传入的参数
 - 实参:调用者调用时传递给函数的参数
 
传参
C中主要有三种传参方式值传递地址传递引用传递,其中引用传递再C++中引入
值传递
地址传递
传递的为实参地址的拷贝,一定要清楚:本质依旧是值传递,只不过实参是地址
引用传递
传递的为实参地址,与地址传递区分,引用传递的实参就是变量本身,非其地址,但在函数中的操作都针对原始实参
对比Java
C中值传递和地址传递可对应Java中的基本数据类型传参与对象引用传参,Java本身按值传参:
- 对于int,float等基本类型传递值即可
 - 每个对象引用都是一个指针/地址,指向堆上同一个对象,传递引用即传递地址
 
示例代码
#include <stdio.h>int first;void callee(int value_first, int* addr_first, int& refe_first) {value_first = 2;*addr_first = 2;refe_first = 3;}void main() {first = 1;callee(first, &first, first);}
分析
- value_first=first但是&value_first≠&first =>值传递
 - addr_first=&first且&addr_first单独存在 =>地址传递,且说明该地址本身作为拷贝又存储在别的区域(int**)
 - refe_first=first且&refe_first=&first => 引用传递
 
活动记录
| 定义: 在Ch3中了解过栈帧,即活动记录(Activation Record) C语言作为面向过程的语言,当每个函数被调用时,都会产生一个过程记录,就是程序执行过程中函数”运行时栈”上的内容变化,一个函数被调用,反映在栈上的与之相关的内容被称为一帧,其中包含了参数、返回地址、旧ebp值、局部变量以及esp和ebp  | 
![]()  | 
|---|---|
示例代码
#include <stdio.h>int first;int callee(int value_first, int* addr_first) {value_first = 2;*addr_first = 2;return value_first;}void main() {first = 1;callee(first, &first);}
函数call过程
当发生函数调用时,编译器和硬件(寄存器)会进行下列动作:
- 将参数入栈
 - 返回地址入栈
 - 进入callee,旧的帧指针入栈保存(push ebp)
 - 让帧指针等于当前栈顶指针(mov ebp,esp),成为新帧指针
 - 帧指针偏移一定数值,预留用于保存局部变量的地址空间(sub ebp xxxxh)
 
部分反汇编结果
参考链接: 汇编语言OFFSET运算符**
//-------------1. 函数调用部分----------callee(first, &first);push offset first (020A17Ch) //全局变量first位于数据段偏移020A17Ch处,&first入栈mov eax,dword ptr [first (020A17Ch)] //取&first处数据放入eaxpush eax //eax入栈//对应步骤1,&first和first先后入栈call callee (02013BBh) //call地址02013BBh处指令,当前地址入栈,对应步骤2,进入子函数add esp,8}//-------------2. 子函数部分----------void callee(int value_first, int* addr_first) {push ebp //对应步骤3,保存上一栈帧mov ebp,esp //对应步骤4,移动当前帧到栈顶,为子函数开辟新帧sub esp,0C0h //对应步骤5,预留局部变量地址空间push ebxpush esipush edi //保存原来的寄存器值lea edi,[ebp-0C0h]mov ecx,30hmov eax,0CCCCCCCChrep stos dword ptr es:[edi] //初始化辅助段,见Ch3汇编分析
函数ret过程
函数返回时
- 如果有返回值,先保存返回值到eax
 - esp add,释放预留给局部变量的空间
 - 令帧指针ebp等于栈指针esp,从栈中弹出上一个帧指针
 - 从栈中弹出返回地址,使用eip保存
 - 回到caller,esp add,释放参数占据的空间
```powershell
//——————3.从callee return—————
return value_first;
mov         eax,dword ptr [value_first]  //步骤1,返回值保存在eax,mov中对数值操作数加[]还是数值
}
pop         edi
pop esi
pop ebx //弹出保存的寄存器,恢复之前的状态 add esp,0C0h //步骤2,esp加上之前的偏移,释放预留给局部变量的空间 cmp ebp,esp
call __RTC_CheckEsp (0241212h) //debugger相关,可忽略 mov esp,ebp //步骤3,令帧指针等于栈指针 pop ebp //步骤3,弹出旧的帧指针给ebp ret //步骤4,栈中弹出返回地址,EIP接收 
//——————4.回到caller—————
 call        callee (02413C0h)
 add         esp,8                                              //步骤5,栈指针递增,释放被参数占用空间
}
---<a name="V1UY4"></a>### 选择题知识点1. 活动记录什么时候产生:①程序开始执行(可理解为main()的栈帧)②调用子函数1. 全局变量,静态变量,函数的地址在编译时被compiler确定,但函数内局部变量地址无法被compiler获得1. 当执行函数callee()后,帧指针的值是①caller()帧的top②callee()帧的底部,可以对比活动记录图示1. 递归函数,深入n次即产生n个活动记录,递归返回时最终要从栈中弹出n个活动记录,如下当调用factorial(4),最终弹出4个活动记录```cint factorial(int n) {if (n == 1) return n;return n * factorial(n - 1);}

