一、引言
学c语言到现在,用了这么久的函数,可是我们为什么要使用函数呢?
其实就是为了重用一段代码。使用函数,我们可以输入不同的值,通过返回值来得到结果。
相关概念:
在汇编语言中,使用指令"call"和"ret"通过改变PC(程序计数器)的值来改变执行顺序
在这个过程中,也涉及到了数据的移动,像参数和返回值
当然程序内部,还有一些局部变量
二、参数和局部变量的实现
2.1局部变量
问题:变量可以取相同的名字吗?
有时可以,取决于情况
为什么呢?
因为编译器会将相同名字的变量映射到不同的地址。
例子
#include<stdio.h>
int first;
int second;//定义了一个全局的变量second
void callee(int first){
int second;//定义了一个局部的变量second
second=1;
first=2;
printf("callee:first=%d,second=%d\n",first,second);
printf("address of second:%#x\n\n",&second);
}
int main(){
first=1;
second=2;
callee(first);
printf("caller:first=%d,second=%d\n",first,second);
printf("address of second:%#x\n",&second);
return 0;
}
运行结果:
callee:first=2,second=1
address of second:0x61fed8
caller:first=1,second=2
address of second:0x405068
通过这个例子,我们发现:
1.变量占用内存
2.编译器通过将相同名称的变量映射到不同的地址以实现作用域。
2.2参数—形参和实参
首先思考一个问题,形参和实参在内存中是共用同一块地址吗?也就是说,形参和实参的地址相同吗?
答案是否定的,形参和实参的地址不同。
可以通过一个小例子测试一下:
#include<stdio.h>
void callee(int formal_parameter){
printf("address of second:%#x\n\n",&formal_parameter);
}
int main(){
int actual_parameter=1;
printf("address of second:%#x\n",&actual_parameter);
callee(actual_parameter);//传入的实参的值
return 0;
}
运行结果:
address of second:0x61fefc
address of second:0x61fee0
也就是说,编译器会为函数中的形参重新分配一个地址
那么为什么要这样设计:
这允许实际参数不受干扰,也就是说,不管我怎么修改我形参中的值,我实参都不会改变,保护了实参。
按值传递
–这允许实际参数保持不受干扰
通过引用
–C语言不支持引用调用,但是它的&和*操作符很容易达到相同的效果。相关内容可以查看C语言中指针传参
2.3编译器是如何实现局部变量和参数的
先看一个递归的例子
#include<stdio.h>
void callee(int n){
printf("Function calls:%d(0x%08x)\n",n,&n);
if (n==0) {
printf("Function returns:%d(0x%08x)\n",n,&n);
return;
}
callee(n-1);
printf("Function returns:%d(0x%08x)\n",n,&n);
}
int main(int argc,char *argv[]){
int n=5;
callee(n);
return 0;
}
运行结果:
Function calls:5(0x0061fee0)
Function calls:4(0x0061fec0)
Function calls:3(0x0061fea0)
Function calls:2(0x0061fe80)
Function calls:1(0x0061fe60)
Function calls:0(0x0061fe40)
Function returns:0(0x0061fe40)
Function returns:1(0x0061fe60)
Function returns:2(0x0061fe80)
Function returns:3(0x0061fea0)
Function returns:4(0x0061fec0)
Function returns:5(0x0061fee0)
在每次调用中,都需要创建一个本地变量n。思考一个问题,编译器怎么知道这个函数需要调用多少次呢?
编译器无法在程序运行完之前确定,所以只能在运行时动态分配。
这样做的缺点就是程序运行时性能会降低。
那编译器是怎么具体实现的呢?
因为编译器不知道它需要动态地分配多少变量,所以它保留了大量的空间来进行扩展。
堆栈:
堆栈是存储内存的一个特殊区域(用来存储每个函数创建的临时变量)
随着函数Push和Pop变量,栈也相应的增长和缩小。