从底层来看函数的调用和返回

一、引言

学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变量,栈也相应的增长和缩小。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋千水竹马道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值