本人萌新,刚刚粗学了C语言,目前正在学习C数据结构与算法,想在博客里写下自己的学习经历和收获。这是第一篇。说说C语言中的形参和实参。
据我的学习,形参和实参 1.个数相等;
2.位置关系对应;
3.类型一致;
4.效果等于 值传递(实参表达式的值,复制一份,传递给形参);
下面有个例子:实现一个交换两个整形变量的交换
#include <stdio.h>
void exchange (int,int);
void exchange (int *one,int *another)
{
int tmp;
tmp = *one;
*one = *another;
*another = tmp;
}
int main ()
{
int num1;
int num2;
scanf ("%d",&num1);
scanf ("%d",&num2);
exchange (&num1,&num2);
printf ("结果为:%d,%d\n",num1,num2);
return 0;
}
从内存角度分析:
- num1 入栈,栈顶指针上移4字节;
- num2 入栈,栈顶指针上移4字节;
- 保存主函数现场信息,栈顶指针上移8字节;
- 将num2的地址信息入栈,栈顶指针上移4字节;
- 将num1的地址信息入栈,栈顶指针上移4字节;
- 将num1的地址信息(首地址)传递给one;
- 将num2的地址信息(首地址)传递给another; !!!实参表达式计算出的值在堆栈里所占的空间就是形参变量的空间,形参和实参是值传递的关系,形参所占的空间就是占用了之前系统分配给实参的空间!
- tmp入栈,栈顶指针上移4字节;
- 将one所指向空间的值(num1的值)赋值给tmp;
- 将another所指向空间的值(num2的值)赋值给one所指向的空间;
- 将tmp赋值给another所指向的空间;
- 栈顶指针下移12个字节,即指向了 主地址现场信息 ;
- 继续执行主函数。。。输出。。
在此提一点:形参与实参是单向传递,对行参变量的任何修改都不会更改实参表达式原本的值!
如果继续向本质去探寻,就不得不扯上关于汇编的内容,命令行编译该程序(cl /FAs exchange.c),生成格式为asm的文件,打开该文件:
TITLE exchange.c .386P include listing.inc if @Version gt 510 .model FLAT else _TEXT SEGMENT PARA USE32 PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT DWORD USE32 PUBLIC 'DATA' _DATA ENDS CONST SEGMENT DWORD USE32 PUBLIC 'CONST' CONST ENDS _BSS SEGMENT DWORD USE32 PUBLIC 'BSS' _BSS ENDS _TLS SEGMENT DWORD USE32 PUBLIC 'TLS' _TLS ENDS FLAT GROUP _DATA, CONST, _BSS ASSUME CS: FLAT, DS: FLAT, SS: FLAT endif PUBLIC _exchange _TEXT SEGMENT _one$ = 8 _another$ = 12 _tmp$ = -4 _exchange PROC NEAR ; 6 : { push ebp mov ebp, esp push ecx //ebp值入栈(将main() ebp保护起来) //再次形成“空栈”,就是exchange()的空栈 ; 7 : int tmp; ; 8 : tmp = *one; mov eax, DWORD PTR _one$[ebp] //mov eax, 4B ebp[4] mov ecx, DWORD PTR [eax] mov DWORD PTR _tmp$[ebp], ecx ; 9 : *one = *another; mov edx, DWORD PTR _one$[ebp] mov eax, DWORD PTR _another$[ebp] mov ecx, DWORD PTR [eax] mov DWORD PTR [edx], ecx ; 10 : *another = tmp; mov edx, DWORD PTR _another$[ebp] mov eax, DWORD PTR _tmp$[ebp] mov DWORD PTR [edx], eax ; 11 : } mov esp, ebp pop ebp //用栈顶当前的值(main() ebp)赋值给ebp,并esp--4 //使ebp回到main函数的ebp状态 ret 0 //mov eip,栈顶 //恢复CPU运行顺序,回到主函数被中断时的状态 _exchange ENDP _TEXT ENDS PUBLIC _main //主函数 EXTRN _printf:NEAR EXTRN _scanf:NEAR _DATA SEGMENT //数据区 $SG347 DB '%d', 00H ORG $+1 $SG348 DB '%d', 00H ORG $+1 $SG349 DB 0bdH, 0e1H, 0b9H, 0fbH, 0ceH, 0aaH, ':%d,%d', 0aH, 00H _DATA ENDS _TEXT SEGMENT _num1$ = -4 // _num2$ = -8 _main PROC NEAR ; 14 : { push ebp //将ebp值入栈,esp会-4 mov ebp, esp //将esp的值赋值给ebp; So,esp和ebp此时指向同一个空间,即 形成了一个空栈,每一个函数都有 sub esp, 8 //将esp的值-8, 意思是将栈顶指针抬高8B,意味着栈顶指针和栈底指针之间有了8B的间隔,8B应该为两个int空间;就是num1和num2 //调用主函数的“函数”(操作系统的代码),他的ebp被保护起来(通过push和mov),原栈底空间被保护起来 ; 15 : int num1; ; 16 : int num2; ; 17 : scanf ("%d",&num1); lea eax, DWORD PTR _num1$[ebp] //-4[ebp] 3[a] = a[3] //lea eax,DWORD PTR -4[ebp] <=> lea eax,DWORD PTR ebp[-4] //eax存入以ebp-4为首地址的int空间 //DWORD_PTR就是int的对应 //系统堆栈向低端增长,意思是,入栈(push)会--esp,而出栈(pop)会++esp //esp:系统堆栈栈顶指针。ebp:系统堆栈栈底指针 //ebp这个栈底指针的上方,隔4B空间,是主函数的局部变量num1的空间 //再看主函数局部变量num2,(_num2$ = -8),同理,他离栈底指针更远,间隔8B空间 //!!!这就说明栈底和第一个局部变量(num1)之间存在一个4B空间!!! //5. 这4B空间就是num1的空间!!! 因为系统堆栈中越往上地址值越小,越往下地址值越大! push eax push OFFSET FLAT:$SG347 call _scanf add esp, 8 ; 18 : scanf ("%d",&num2); lea ecx, DWORD PTR _num2$[ebp] push ecx push OFFSET FLAT:$SG348 call _scanf add esp, 8 ; 19 : ; 20 : exchange (&num1,&num2); // 调用函数 lea edx, DWORD PTR _num2$[ebp] push edx lea eax, DWORD PTR _num1$[ebp] push eax //将num2和num1首地址入栈; call _exchange //call指令内部会执行push eip 的操作!!! 保护eip的值,以便返回时能继续执行主调函数的下一条指令! //这条指令就是下面的add esp, 8; 接着call会执行mov eip, exchange; exchange就是子函数exchange的首地址常量 //CPU将取出exchange函数的第一条指令开始执行,即,开始执行子函数exchange //这里的call由于执行的是push eip;所以,此时esp又会-4 add esp, 8 //esp下落8字节,此时正好回到调用前状态 ; 21 : printf ("结果为:%d,%d\n",num1,num2); mov ecx, DWORD PTR _num2$[ebp] push ecx mov edx, DWORD PTR _num1$[ebp] push edx push OFFSET FLAT:$SG349 //字符串常量的本质是该字符串常量的首地址常量 call _printf add esp, 12 ; 0000000cH ; 22 : return 0; xor eax, eax ; 23 : } mov esp, ebp pop ebp ret 0 _main ENDP _TEXT ENDS END建议根据汇编代码和我给的理解和注释画图理解!