🎯 程序功能:
计算 (10 - 3) * 4 = 28,然后输出 "Result: 28"
平台:ARM32 Linux(树莓派 / QEMU)
编译:gcc -o calc calc.s
输出:Result: 28
✅ 完整代码 + 超详细逐行注释
@ =============================================
@ 文件名: calc.s
@ 功能: 计算 (10 - 3) * 4 = 28,调用子函数完成,输出结果
@ 演示新指令: sub, mul, push/pop, bl, bx, ldr/str, cmp/bne
@ =============================================
.section .data
output_template:
.ascii "Result: \n" @ 模板:"Result: __\n"(预留两位放结果)
template_len = . - output_template
.section .text
.global _start
_start:
@ ========== 主程序:准备参数并调用函数 ==========
mov r0, #10 @ 第一个参数 a = 10(按ABI,r0~r3传参)
mov r1, #3 @ 第二个参数 b = 3
mov r2, #4 @ 第三个参数 c = 4
bl calculate_expression @ 调用子函数!bl 会自动把返回地址存入 lr (r14)
@ 相当于高级语言:result = calculate_expression(10, 3, 4)
@ ========== 函数返回后,r0 中是结果(28),现在转换并输出 ==========
mov r4, r0 @ 把结果从 r0 保存到 r4(因为 r0 待会要用于系统调用)
@ --- 把数字 28 转成两个字符 '2' 和 '8' ---
mov r5, #10 @ 除数 = 10
udiv r6, r4, r5 @ r6 = 28 / 10 = 2(十位)
mls r7, r6, r5, r4 @ r7 = 28 - (2*10) = 8(个位)
add r6, r6, #'0' @ 十位数字 2 → 字符 '2'
add r7, r7, #'0' @ 个位数字 8 → 字符 '8'
@ --- 存入输出模板 ---
ldr r1, =output_template @ r1 = 字符串地址
strb r6, [r1, #8] @ 把 '2' 存到第9个字节("Result: " 第一个空格)
strb r7, [r1, #9] @ 把 '8' 存到第10个字节(第二个空格)
@ --- 调用 write 输出 ---
mov r0, #1 @ fd = stdout
mov r2, #template_len @ 输出长度
mov r7, #4 @ sys_write
svc #0
@ --- 退出程序 ---
mov r0, #0
mov r7, #1 @ sys_exit
svc #0
@ =============================================
@ 子函数: calculate_expression(a, b, c) → return (a - b) * c
@ 参数: r0=a, r1=b, r2=c
@ 返回: r0 = (a - b) * c
@ =============================================
calculate_expression:
@ ========== 函数开头:保存会用到的寄存器到栈(保护现场) ==========
push {r4, r5, lr} @ 把 r4, r5, lr 压入栈(lr 必须保存,因为函数内可能再调用别人)
@ ========== 执行计算:result = (a - b) * c ==========
sub r4, r0, r1 @ r4 = a - b (新指令!sub = subtract 减法)
mul r5, r4, r2 @ r5 = r4 * c (新指令!mul = multiply 乘法)
mov r0, r5 @ 把结果放到 r0(返回值按ABI放在r0)
@ ========== 函数结尾:恢复寄存器,返回调用者 ==========
pop {r4, r5, lr} @ 从栈弹出,恢复之前保存的寄存器
bx lr @ 跳转到 lr 保存的地址(即返回到主程序)→ 新指令!bx = branch and exchange
@ =============================================
@ 可选:加一个“死循环”演示 bne(不等于则跳转)
@ =============================================
@ 下面这段是额外演示,当前程序不会执行到这里
@ 你可以注释掉上面的 bx lr,取消注释下面代码来测试
/*
loop_demo:
mov r8, #0 @ 计数器 = 0
check_loop:
cmp r8, #5 @ 比较 r8 和 5(新指令组合!cmp + bne)
bne keep_looping @ 如果不相等(r8 != 5),则跳转 → 新指令!bne = branch if not equal
b exit_loop @ 如果相等,退出循环
keep_looping:
add r8, r8, #1 @ r8++
b check_loop @ 跳回去继续检查
exit_loop:
bx lr @ 返回(演示用)
*/
📘 重点新指令详解(前面没出现过的)
| 指令 | 全称 | 作用 | 示例 | 说明 |
|---|---|---|---|---|
sub | Subtract | 减法 | sub r4, r0, r1 → r4 = r0 - r1 | 和 add 对应 |
mul | Multiply | 乘法 | mul r5, r4, r2 → r5 = r4 × r2 | 注意:不能用立即数,只能寄存器×寄存器 |
push | Push | 压栈(保存寄存器) | push {r4, lr} | 一次可保存多个寄存器,用 {} 包裹 |
pop | Pop | 出栈(恢复寄存器) | pop {r4, lr} | 顺序要和 push 对应! |
bl | Branch with Link | 跳转并保存返回地址到 lr | bl function | 用于调用函数,返回用 bx lr |
bx | Branch and eXchange | 跳转到 lr(或其他寄存器) | bx lr | 通用返回指令,也支持切换 Thumb 模式 |
cmp + bne | Compare + Branch if Not Equal | 比较 + 条件跳转 | cmp r0, #5 + bne label | 实现 while(x != 5) 逻辑 |
💡 关键概念说明
1. 函数调用规范(ARM AAPCS)
- 参数通过
r0~r3传递 - 返回值通过
r0返回 r4~r11是“被调用者保存寄存器”,函数内若使用,必须push/poplr (r14)必须保存,因为bl会覆盖它
2. 栈(Stack)操作
push {r4, lr}→ 把寄存器值存入栈(内存),栈指针sp自动减小pop {r4, lr}→ 从栈恢复值,sp自动增大- 保证函数调用前后寄存器值不变 → 程序稳定!
3. 条件跳转 cmp + bne
cmp r8, #5→ 计算r8 - 5,但不保存结果,只更新标志位(Z=Zero, N=Negative...)bne label→ 如果 Zero 标志位 没置位(即不相等),则跳转- 类似
if (r8 != 5) goto label;
✅ 编译 & 运行
# 保存为 calc.s
gcc -o calc calc.s
./calc
输出:
Result: 28
🧩 如果你想看到 bne 循环演示:
- 注释掉
bx lr - 取消注释
loop_demo那一段 - 在
calculate_expression最后加一行:b loop_demo
程序会进入死循环(你可以用 Ctrl+C 终止),用于观察跳转行为。