欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言函数的参数传递实例:利用函数交换两个变量的值实现教程

程序员文章站 2022-09-28 18:34:27
交换两个变量的值,容易联想到【两量交换借助中间变量】的方法。 若用函数调用的方式来实现交换,沿用上述思路初步实现如下: #include void exchange(in...

交换两个变量的值,容易联想到【两量交换借助中间变量】的方法。

若用函数调用的方式来实现交换,沿用上述思路初步实现如下:

#include
void exchange(int,int);
void exchange(int one,int another){
    int t;
    t = one;
    one = another;
    another = t;
}
int main(){
    int num1;
    int num2;
    printf("输入需要交换的两个数:\n");
    scanf("%d %d",&num1,&num2);
    exchange(num1,num2);
    printf("交换后的两个数为:%d %d\n",num1,num2);
    return 0;
}

C语言函数的参数传递实例:利用函数交换两个变量的值实现教程

结果说明,未能完成交换。

回看上述程序实现的方式,涉及到了函数传递中至关重要的问题—实参和形参之间的关系。

【notes】

1.形参: formal argument,形式参数

它是被调函数中的局部变量,每当调用函数时,这些变量就会被赋值。它只在被调用时才会被分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。

2.实参: actual argument

它是调用函数分配给被调函数变量的特定数值,可以是常量/变量或一个表达式。执行时,首先要计算其值,然后将该值复制一份给被调函数中相应的形参。

既然被调函数使用的值是从调用函数中复制而来的,所以不管在被调函数中对复制的数值进行什么操作,调用函数中的原数值不会受到任何影响。

这好比原件和复印件之间的关系:在复印件上做的任何改动都不会对原文件造成丝毫影响。

因此,上述程序中,num1和num2虽然成功把值传给了one、another,one、another也完成了它们之间数值的交换,但是也仅仅是one、another之间交换了,和num1和num2本身没有关系。

这是一次单向的值传递过程,函数exchange的实参能传给形参,但**形参**one、another是定义在函数exchange中的局部变量,当函数调用结束后,它俩就会被释放,因此它们没有渠道把交换的值传回给需要交换的num1,num2。

既然想要通过函数调用的方式来解决这个问题,先来了解函数调用的具体实现过程。

首先,各个函数都有一定的堆栈空间。函数形参和实参的传递需要依赖系统堆栈来实现

【堆栈基础知识】

栈:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收

在堆栈中变量分布是从高地址到低地址分布

现在继续来看函数调用的过程.

step1:

>--主函数调用子函数,实参入栈。(若实参为表达式,则从右向左依次的将实参表达式的值计算出来)

按本文一开始提到的例子来说,即就是num1和num2入栈,而实参num1,num2在系统堆栈中所占用的空间,就是与之对应的形参变量的空间。

这意味着已经完成了”参数传递”:

num1的地址赋值给了one所指向的空间—> * one = &num1;

num2的地址赋值给了another所指向的空间—> * another= &num2;

C语言函数的参数传递实例:利用函数交换两个变量的值实现教程

step2:

>-- 通过call汇编指令,调用子函数,将eip入栈,再通过ret指令出栈,并赋值给eip,从而保证函数调用及返回顺序的正常。

由于函数调用的时候是汇编中的地址的跳转,而在汇编中的跳转源于标号地址IP。

所以这里就要涉及到汇编相关的知识

【note】

eip:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

esp:寄存器存放当前线程的栈顶指针

ebp:寄存器存放当前线程的栈底指针

ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针。

汇编相关的具体细节待日后充实完善。

call指令的执行过程大致如下:

1) call指令首先将主调函数在子函数返回后要执行的下一条指令的地址入栈(返回地址此时在栈顶),然后,更改eip的值,以开始执行子函数的第一条指令,从而开启子函数的执行。

2) 当子函数运行结束后,执行ret指令,这将导致处在栈顶的返回地址出栈,并更改eip的值,返回主调函数下一条指令。

step3:

>--编译器将栈底指针移动到栈顶位置,再将堆栈当前栈底向上的空间分配给子函数的局部变量

step4:

>-- 子函数运行结束后,恢复到以前的栈底和栈顶指针。栈顶指针再减去形参变量所占用的空间,恢复到子函数调用之前的状态。

现在再来解决最初的问题:利用函数调用来实现两个变量的交换:

#include
void Exchange(int *one, int *another);
void Exchange(int *one, int *another){
    int temp;
    temp = *one;
    *one = *another;
    *another = temp;
}
void main(void){
    int num1;
    int num2;
    printf("输入两个需要交换的数\n",num1,num2);
    scanf("%d %d",&num1,&num2);
    Exchange(&num1,&num2);//实参类型为首地址
    printf("交换完毕后两个数的值为%d和%d\n",num1,num2);
}


运行结果:

C语言函数的参数传递实例:利用函数交换两个变量的值实现教程