以下是我对嵌入式的学习安排,有一起学习伙伴可以参考一下
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)