本文主要是介绍【散文诗】C语言的本质(基于ARM深入分析C程序),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 1. ARM架构
- ARM通用寄存器及其别名
- 基本汇编指令
- LDR:
- STR:
- ADD:
- SUB:
- BL:
- PUSH:
- POP:
- MOV:
- 2. 局部变量的分配与初始化
- 局部变量初始化
- 3. 全局变量、静态变量初始化
- 4. 栈和堆
- 栈
- 堆
- 堆的malloc函数简单实现
- 5. 函数是什么
- 调用函数
- 往内存地址上拷贝函数
- 函数怎么传递参数
1. ARM架构
- 程序编译后生成 .bin、.hex文件,(汇编代码)烧入Flash中。
- 启动设备,程序在Flash中一条一条执行。
- 程序告诉CPU执行操作,如分配内存、分配栈、计算。
- CPU操作,如从内存中某个地址读写数据、开辟空间,GPIO的读写等。
CPU运行时,先去取得指令,再执行指令:① 把内存a的值读入CPU寄存器R0② 把内存b的值读入CPU寄存器R1③ 把R0、R1累加,存入R0(对于数据的运算是在cpu内部执行)④ 把R0的值写入内存a
ARM通用寄存器及其别名
R# | APCS别名 | 意义 |
---|---|---|
R0 | a1 | 参数/结果/scratch 寄存器1 |
R1 | a2 | 参数/结果/scratch 寄存器2 |
R2 | a3 | 参数/结果/scratch 寄存器3 |
R3 | a4 | 参数/结果/scratch 寄存器4 |
R4 | v1 | arm 状态局部变量寄存器1 |
R5 | v2 | arm 状态局部变量寄存器2 |
R6 | v3 | arm 状态局部变量寄存器3 |
R7 | v4 / wr | arm 状态局部变量寄存器4 / thumb状态工作寄存器 |
R8 | v5 | arm 状态局部变量寄存器5 |
R9 | v6 / sb | arm 状态局部变量寄存器6 / 在支持RWPI的ATPCS中作为静态基址寄存器 |
R10 | v7 / sl | arm 状态局部变量寄存器7 / 在支持数据栈检查的ATPCS中作为数据栈限制指针 |
R11 | v8 / fp | arm 状态局部变量寄存器8 / 帧指针 |
R12 | ip | 内部过程调用 scratch寄存器 |
R13 | sp | 栈指针 |
R14 | lr | 链接寄存器 |
R15 | pc | 程序计数器 |
基本汇编指令
LDR:
读内存指令,即Load之意,加载寄存器,表示读4个字节,可以加后缀,B:LDRB表示读1个字节, H:LDRH表示读2个字节。
LDR r0,[r3]
r3为一个地址,去r3这个地址上读数据放入r0中
STR:
写内存指令,即Store之意,存储的意思,可以加后缀,B:STRB表示写1个字节,STRH:2个字节
STR r0,[r3]
r3为一个地址,把r0上的数据存入r3的地址上
ADD:
加指令,不涉及内存操作即不用访问地址,只在cpu内部来实现
ADD r0,r1,r2
r0等于r1加r2
SUB:
减指令,不涉及内存操作即不用访问地址,只在cpu内部来实现
ADD r0,r1,r2
r0等于r1减r2
BL:
跳转指令,即Branch And Link,跳转到标号地址,并将返回地址保存在 LR 中,R14(LR)来存放当前子程序的返回地址,此指令有两个作用,第一记录返回地址(下一条指令的地址),保存在R14(LR),第二执行函数。
BL my_main
先记录my_main下一条指令的地址保存到R14中(LR),再执行my_main
PUSH:
入栈指令(栈指针由高地址往低地址指,如内存为0x2000000~0x201000,设栈指针为0x201000)
PUSH {r3,lr}
本条语句指将寄存器r3和lr中的值(用来存放当前子程序的返回地址)写入内存栈中,将lr写入sp-4地址,r3写入sp-8地址。注:本质是调用写内存指令STR,将r3和lr寄存器中的值写入内存中去,高标号寄存器写入高地址的栈里,低标号寄存器写入低地址的栈里。先将sp=sp-4,再将lr寄存器的值放进去(即将lr中的值放入sp所指的内存地址,如sp一开始的地址为0x201000,则0x2000FFC地址上的值为lr寄存器中的值),再sp=sp-4,将r3寄存器中的值写入sp里(r3存至0x2000FF8)。
POP:
出栈指令(栈指针由高地址往低地址指,如内存为0x2000000~0x201000,设栈指针为0x201000)
PUSH {r3,pc}
本条语句是将取出内存栈中地址sp中的值放入r3寄存器中,sp+4中的值放入pc寄存器中注:本质是调用读内存指令LDR,高标号寄存器的内容来自高地址的栈,低标号寄存器的内容来自低地址的栈,先读出内存栈地址为sp的内存中的值存入r3寄存器,再sp=sp-4,读出sp中的值存入pc寄存器中。
MOV:
传送指令
MOV r0,#0x1C8
将0x1C8存入r0寄存器中
2. 局部变量的分配与初始化
变量
- 全局变量
- 局部变量
- 局部静态变量
局部变量初始化
C语言
void my_main(void)
{int a = 456;
}
汇编
// 执行my_main()PUSH {r3,lr} //进入函数,寄存器r3、lr的值,都存入内存的栈中(lr保存程序返回地址)// 执行 int a = 456MOV r0,#0x1C8 //0x1c8 = 456STR r0,[sp,#0x00]
3. 全局变量、静态变量初始化
在调用main函数之前,使用copy、SetZero函数对全局变量、静态变量初始化
4. 栈和堆
栈
堆
一块空闲内存,可以使用malloc/free函数来管理
堆的malloc函数简单实现
volatile char my_buf[20*1024];
volatile int index = 0;void *malloc(int size)
{char *ret = &my_buf[index];index += size;return ret;
}
5. 函数是什么
函数就是一些列的机器吗
调用函数
调用函数就是让CPU的PC寄存器等于“一系列机器码”的首地址,就是函数地址
往内存地址上拷贝函数
如函数为16条指令码,往0x20008000的地址上拷贝函数
int add_val(int v){int a = v;a++;return a;
}void copy_add_val_to_ram(void){unsigned char *src = (lunsigned char *)add_val;unsigned char *dest = (unsigned char *)0x20008000;for(int i = 0; i < 16; i++){dest[i] = src[i];}/*等价于 ⬆⬆⬆⬆⬆⬆unsigned char *src;unsigned int val = (unsigned int)add_val;unsigned char *dest = (unsigned char *)0x20008000; src = (unsigned char *)val;for(int i = 0; i < 16; i++){dest[i] = src[i];}*/
}int main(void)
{int (*p)(int );int a = 0;p = (int(*)(int))0x20008000;a = p(1);printf("a = %d\n",a); //a = 2;
}
函数怎么传递参数
在进入函数前,会把函数的参数值拷贝到
r0
寄存器中,后续在函数内使用参数都是直接从r0
寄存器中操作
这篇关于【散文诗】C语言的本质(基于ARM深入分析C程序)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!