目录
一、引言二、参数和局部变量的实现2.1局部变量2.2参数---形参和实参2.3编译器是如何实现局部变量和参数的一、引言
学c语言到现在,用了这么久的函数,可是我们为什么要使用函数呢?
其实就是为了重用一段代码。使用函数,我们可以输入不同的值,通过返回值来得到结果。
相关概念:
在汇编语言中,使用指令"call"和"ret"通过改变PC(程序计数器)的值来改变执行顺序
在这个过程中,也涉及到了数据的移动,像参数和返回值
当然程序内部,还有一些局部变量
二、参数和局部变量的实现
2.1局部变量
问题:变量可以取相同的名字吗?
有时可以,取决于情况
为什么呢?
因为编译器会将相同名字的变量映射到不同的地址。
例子
#include<stdio.h>int first;int second;//定义了一个全局的变量secondvoid callee(int first){int second;//定义了一个局部的变量secondsecond=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=1address of second:0x61fed8caller:first=1,second=2address 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:0x61fefcaddress 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变量,栈也相应的增长和缩小。
如果觉得《从底层来看函数的调用和返回》对你有帮助,请点赞、收藏,并留下你的观点哦!