标题：函数调用
函数调用的一般形式
前面已经说过，在程序中是通过对函数的调用来执行函数体的，其过程与其它语言的子程序调用相似。 
C语言中，函数调用的一般形式为： 
函数名(实际参数表) 
对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数、变量或其它构造类型数据及表达式。各实参之间用逗号分隔。
在C语言中，可以用以下几种方式调用函数：
1. 函数表达式：函数作为表达式中的一项出现在表达式中，以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如：z=max(x,y)是一个赋值表达式，把max的返回值赋予变量z。2. 函数语句：函数调用的一般形式加上分号即构成函数语句。例如： printf ("%d",a);scanf ("%d",&b);都是以函数语句的方式调用函数。3. 函数实参：函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送，因此要求该函数必须是有返回值的。例如： printf("%d",max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。所谓求值顺序是指对实参表中各量是自左至右使用呢，还是自右至左使用。对此，各系统的规定不一定相同。介绍printf 函数时已提到过，这里从函数调用的角度再强调一下。 
【例】 
main() 
{ 
int i=8; 
printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--); 
} 
如按照从右至左的顺序求值。运行结果应为： 
8 
7 
7 
8 
如对printf语句中的++i，--i，i++，i--从左至右求值，结果应为： 
9 
8 
8 
9 
应特别注意的是，无论是从左至右求值， 还是自右至左求值，其输出顺序都是不变的， 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值，所以结果为8，7，7，8。上述问题如还不理解，上机一试就明白了。 
被调用函数的声明和函数原型 
在主调函数中调用某函数之前应对该被调函数进行说明（声明），这与使用变量之前要先进行变量说明是一样的。在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型，以便在主调函数中按此种类型对返回值作相应的处理。 
其一般形式为： 
类型说明符 被调函数名(类型 形参，类型 形参…)； 
或为： 
类型说明符 被调函数名(类型，类型…)； 
括号内给出了形参的类型和形参名，或只给出形参类型。这便于编译系统进行检错，以防止可能出现的错误。 
例 main函数中对max函数的说明为： 
int max(int a,int b); 
或写为: 
int max(int,int)； 
C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。 
1) 如果被调函数的返回值是整型或字符型时，可以不对被调函数作说明，而直接调用。这时系统将自动对被调函数返回值按整型处理。例8.2的主函数中未对函数s作说明而直接调用即属此种情形。 
2) 当被调函数的函数定义出现在主调函数之前时，在主调函数中也可以不对被调函数再作说明而直接调用。例如例8.1中，函数max的定义放在main 函数之前，因此可在main函数中省去对max函数的函数说明int max(int a,int b)。 
3) 如在所有函数定义之前，在函数外预先说明了各个函数的类型，则在以后的各主调函数中，可不再对被调函数作说明。例如： 
char str(int a); 
float f(float b); 
main() 
{ 
…… 
} 
char str(int a) 
{ 
…… 
} 
float f(float b) 
{ 
…… 
} 
其中第一，二行对str函数和f函数预先作了说明。因此在以后各函数中无须对str和f函数再作说明就可直接调用。 
4) 对库函数的调用不需要再作说明，但必须把该函数的头文件用include命令包含在源文件前部。
函数的嵌套调用
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的，不存在上一级函数和下一级函数的问题。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图。 
图表示了两层嵌套的情形。其执行过程是：执行main函数中调用a函数的语句时，即转去执行a函数，在a函数中调用b 函数时，又转去执行b函数，b函数执行完毕返回a函数的断点继续执行，a函数执行完毕返回main函数的断点继续执行。 
【例】计算s=2∧2!+3∧2! 
本题可编写两个函数，一个是用来计算平方值的函数f1，另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值，再在f1中以平方值为实参，调用 f2计算其阶乘值，然后返回f1，再返回主函数，在循环程序中计算累加和。 
long f1(int p) 
{ 
int k; 
long r; 
long f2(int); 
k=p*p; 
r=f2(k); 
return r; 
} 
long f2(int q) 
{ 
long c=1; 
int i; 
for(i=1;i<=q;i++) 
c=c*i; 
return c; 
} 
main() 
{ 
int i; 
long s=0; 
for (i=2;i<=3;i++) 
s=s+f1(i); 
printf("\ns=%ld\n",s); 
} 
在程序中，函数f1和f2均为长整型，都在主函数之前定义，故不必再在主函数中对f1和f2加以说明。在主程序中，执行循环程序依次把i值作为实参调用函数f1求i2值。在f1中又发生对函数f2的调用，这时是把i2的值作为实参去调f2，在f2 中完成求i2!的计算。f2执行完毕把C值(即i2!)返回给f1，再由f1返回主函数实现累加。至此，由函数的嵌套调用实现了题目的要求。由于数值很大，所以函数和一些变量的类型都说明为长整型，否则会造成计算错误。
函数调用的实际（汇编）实现
指针寄存器EBP和ESPEBP是所谓的帧指针，指向当前活动记录的上方（上一个活动记录的最下方）
ESP是所谓的栈指针，指向当前活动记录的最下方（下一个将要插入的活动记录的最上方）
这两个指针的值规定了当前活动记录的位置参数传递将函数参数压栈：mov eax,dword ptr [n] ;（n为参数变元）
push eax函数调用分配空间函数调用将执行如下操作：
⒈将帧指针压入栈中：push ebp 
⒉使得帧指针等于栈指针：mov ebp,esp 
⒊使栈指针自减，自减得到的内存地址应当能够（足够）用来存储被调用函数的本地状态：sub esp,0CCh 
注意：0CCh为0xCC，随着具体函数的不同而不同。传入保存状态push ebx ;保存ebx寄存器的值
push esi ;保存esi寄存器的值
push edi ;保存edi寄存器的值装入edilea edi,[ebp-0CCh] ;0cch是当前活动记录的大小。
EDI是目的变址寄存器。恢复传入的保存状态00411417 pop edi 
00411418 pop esi 
pop ebx栈指针上移，恢复空间add esp,0CCh函数返回释放空间当函数返回时，编译器和硬件将执行如下操作：
⒈使栈指针等于帧指针： mov esp,ebp 
⒉从栈中将旧的帧指针弹出： pop ebp 
⒊返回：ret一个函数调用的实例;void function(int n)
;{
push ebp 
mov ebp,esp 
sub esp,0CCh 
push ebx 
push esi 
push edi 
lea edi,[ebp-0CCh] 
mov ecx,33h 
mov eax,0CCCCCCCCh 
rep stos dword ptr es:[edi] 
;char a=1;
mov byte ptr [a],1 
;if(n==0)return;
cmp dword ptr [n],0 
jne function+2Ah (4113CAh) 
jmp function+77h (411417h) 
;printf("%d\t(0x%08x)\n",n,&n);
mov esi,esp 
lea eax,[n] 
push eax 
mov ecx,dword ptr [n] 
push ecx 
push offset string "%d\t(0x%08x)\n" (415750h) 
call dword ptr [__imp__printf (4182B8h)] 
add esp,0Ch 
cmp esi,esp 
call @ILT+305(__RTC_CheckEsp) (411136h) 
;function(n-1);
mov eax,dword ptr [n] 
sub eax,1 
push eax 
call function (411041h) 
add esp,4 
;printf("----%d\t(0x%08x)\n",n,&n);
mov esi,esp 
lea eax,[n] 
push eax 
mov ecx,dword ptr [n] 
push ecx 
push offset string "----%d\t(0x%08x)\n" (41573Ch) 
call dword ptr [__imp__printf (4182B8h)] 
add esp,0Ch 
cmp esi,esp 
call @ILT+305(__RTC_CheckEsp) (411136h) 
;}
pop edi 
pop esi 
pop ebx 
add esp,0CCh 
cmp ebp,esp 
call @ILT+305(__RTC_CheckEsp) (411136h) 
mov esp,ebp 
pop ebp 
ret