C10-函数调用过程


以下是我对嵌入式的学习安排,有一起学习伙伴可以参考一下
python只是对于C语言的调剂作用,比如做做小网页什么的。

嵌入式学习安排

  • C
  • Python
  • C++
  • 数据结构
  • Linux编程
  • Qt
  • 数据库
  • Linux驱动
  • ARM汇编

函数调用过程

函数是模块化的基本单位
C语言的模块化,接口(即参数与返回值)
函数之间是相互独立的,但是调用过程中又有一定交集
这种交集是如何产生的?
答:函数的调用过程

栈(先进后出
内存是分区管理的(举例一个房子有卧室,客厅,洗手间)
目的:便于管理
大致的将程序的内存分为:全局,栈区,堆区。
栈区:它是用于函数交互的一块内存区域。使用的局部变量函数形参都在内存的栈区域

void MyFun(int x, int y)
{
 int nValue = 1;
 printf("%d %d\r\n", x, y);
}
int main()
{
 MyFun(10, 20);
}

观察到x y nValue 都是0x19 说明都在栈区
以下两个的nValue是不同地址的

void MyFunB()
{
 int nValue = 0x123;
 printf("FunB:%d", nValue);
}
void MyFunA(int x, int y)
{
 int nValue = 0x1122;
 MyFunB();
 printf("FunA:%d\r\n", nValue);
 printf("%d %d\r\n", x, y);
}
int main()
{
 MyFunA(10, 20);
}

函数形参 顾名思义 是不知道未来是什么
函数实参 已经有实例化对象(数值)

函数的参数是如何交互的

void MyFunA(int x, int y,int z,int k)
{
 int nValue = 0x11;
 printf("%d %d\r\n", x, y);
}

int main()
{
 MyFunA(0x10, 0x20,0x30,0x40);
}

k z y x nValue
地址都是按高地址向地址生长的
在这里插入图片描述

栈的增长方向是 高地址低地址增长(先有高地址的,才有低地址的)
那就是传参数的时候 就是由右往左传递的

int
4个字节 占32位
一个字节两个空
也就是两个空 占8位

函数对应的栈帧

void MyFunB(int x, int y)
{
 int nValue = 0x1234;

}

void MyFunA(int x, int y,int z,int k)
{
 int nValue = 0x11;
 MyFunB(0x1111, 0x2222);
 printf("%d %d\r\n", x, y);
}

int main()
{
 MyFunA(0x10, 0x20,0x30,0x40);
}

通过对&nValue查看
可以知道FUNA的栈地址是高于FUNB地址的
所以更可以推定,越是先实现的 地址越高
FUNB:
在这里插入图片描述

FUNA:
在这里插入图片描述

可见,他们的栈帧隔得不是很远,函数调用的过程和栈紧密相连的
就是正在运行函数都会被 分配栈帧区域

在这里插入图片描述

函数调用过程的细节

函数的代码是存在全局区里面
函数的参数是存在栈区里面的
A函数传给参数给B
是把参数放在栈区里面
B要拿就区栈里面拿

函数调用到底要解决哪些问题

  • 传递参数
  • 流程转移(汇编中 call jmp指令)跳过去
  • 跳的回 (函数返回地址问题)
  • 栈帧的切换(就是 在A中调用了B函数,那么就会从A栈帧到B栈帧,B回来的时候怎么又是知道是A栈帧的)
    对于切换 是因为栈只能从栈顶操作,也就是某一时刻只能指向一个栈帧
    这一切问题交给了C编译器处理

就是当我们走到函数的时候,会做很事情如下:

  • 按调用约定传参
  • 保存返回地址(留下一个返回的标志) (保存的地方是跳过去的函数保存的 就是B保存下 返回A的地址 找到可以返回的路)
  • 流程转移(A跳到B)

在B里面

  • 保存上一层的信息(A函数的栈帧信息)
  • 分配局部变量(花括号开始)
  • 保存寄存器环境
  • 开始执行B的汇编代码

传过去的时候 会留下一个返回标志

{ 花括号 是重新分配局部变量的 开端 0xcccccccc

在这里插入图片描述

那么反过来的流程
由B到A

  • 读取寄存器环境并且恢复
  • 释放局部变量(相当于退房 就是这里可用了,不是摧毁)
  • 返回上一层栈帧
  • 从内存中读取返回地址返回

局部变量释放问题

char* MyFun()
{
 char chAry[] = "hello world";
 return chAry;
}
void MyFun2()
{
 printf("DO NOTHING!\r\n");
}
int main()
{
 char * pszContent = MyFun();
 MyFun2();
 printf("%s\r\n", pszContent);
 return 0;
}

相当于 租房退房 不是摧毁房子
是标志那个区域可以用
{ 花括号 重新分配局部变量 房子被清理
就是数据被清理

传参约定

传参的约定是什么?
函数之间交互,是通过栈来间接交互的,为了函数的调用方(FunA)和被调用方(FunB)能够正确的交互信息
必须提前做好约定,使得

传参约定关心以下几个事情

  • 传参顺序
  • 用什么传参(内存还是寄存器)
  • 谁去平衡栈(调用方还是被调用方去释放空间)(就是平衡栈的指令放在调用方还是被调用方里面区别)

下面有调用约定
在这里插入图片描述

C语言中,传参约定有三种

  • C约定(_cdecl):从右到左传递,通过内存传参,由调用方去平衡栈。
  • _stdcall: 从右往左传参数,通过内存传参,由被调用方平衡栈
  • _fastcall:从右往左传参,左边几个参数由寄存器传参,其他由内存传参,由被调用方平衡栈

面试题目

为什么要平衡栈
因为传参的时候要分配栈
C约定是哪一方平衡栈?为什么?
调用方平衡栈 (但是调用方所占内存 比 被调用方 平衡栈多)
为什么?
(printf 就是典型的例子)
因为对于变参函数必须使用C约定
因为对于变参函数传递的参数的个数是不确定的
只有调用方(实参的传的地方)才知道函数到底要平衡多少个字节的栈空间

用以下代码进行分析
#include"stdafx.h"
#include"stdio.h"

void MyFunB(int nValue1, int nValue2)
{
 int nValue = 0x111111;
}

void MyFunA(int nValue1, int nValue2)
{
 int nValue = 0x123123;
 MyFunB(0x1111, 0x2222);
 printf("0x%x,0x%x\r\n", nValue1, nValue2);
}

int main(int argc, char argv[])
{
 int nValue = 0x6666;
 MyFunA(0x3333, 0x4444);
 return 0;
}

B:

在这里插入图片描述

现在我们认证一下
在这里插入图片描述

通过反汇编观看结果

在这里插入图片描述

再认证B保存A的栈帧地址(保存上一层栈帧信息) 看看圈出的来的是否正确

在这里插入图片描述
根据图可知 B保存的上一层 栈帧信息 是正确的。
我圈出来的也是正确的

同时证明了 B的栈帧信息是 在A 上面的 因为 B保存的 是0x0019fe3c A保存的是 0x0019ff18 所以 按照VS排序
即通过汇编也可以查看到 栈帧信息的排列
在这里插入图片描述
在这里插入图片描述

也验证了 上面笔记 A栈帧是 B栈帧的下面 也说明了 栈是先进后出的。

好,我们现在来看A

A:
在这里插入图片描述

我们认证一下所圈出来的信息
在这里插入图片描述

再来看看栈帧信息

在这里插入图片描述

所以 我们圈出0x0019ff18的栈帧信息是正确的

main函数的

在这里插入图片描述

补充与总结

函数的调用过程是及其复杂的以下对上面信息进行补充

  • 按约定传参(从右到左)→保存返回地址→流程转移→保存上一层栈帧信息→分配局部变量→分配寄存器环境→执行代码
    补充:保存地址 是谁保存的呢?,放在哪个区域呢?(全局还是栈)
    答案: 是流程转移跳过去的函数保存数据的,且保存在全局区域里面。(但是 最终的返回地址是调用方的)
    (可以这么理解,是被调用的函数要知道回家的路,所以就是它保存。并且是保存在全局区里面的0x0041xxx)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值