【ARM汇编】0x01_ARM和C混合编程实现基本运算

【ARM汇编】0x01_ARM和C混合编程实现基本运算 文章目录系列文章目录更新日志代码编译方法Android环境ARM环境运行程序调用流程图示功能说明拆解「调用→执行→返回」全流程步骤 1C 调用 add_asm 前CPU 自动给 lr 赋值步骤 2汇编函数执行核心逻辑步骤 3bx lr 完成返回回到 C 层继续执行调用约定要点先明确核心概念新手友好版要点 1r0–r3 用作参数 / 临时寄存器r0 存返回值被调用者无需保存规则拆解结合咱们的代码代码示例符合规则违反规则的后果反面例子要点 2使用 r4–r11 需保存 / 恢复被调用者负责正面示例使用 r4 的正确写法关键步骤解释违反规则的后果反面例子调用约定部分总结结合前文代码总结bx lr 说明系列文章目录【ARM汇编】0x00_基于AndroidStudio构建ARM32-v7a以及ARM64-v8a环境【ARM汇编】0x01_ARM和C混合编程实现基本运算更新日志日期变更内容2026-03-18完成初稿代码math_asm.s.text ; ; ARM汇编基础算术运算函数 (ARMv7架构APCS调用规范) ; 遵循ARM EABI调用约定 ; - 参数传递: r0-r3 用于传递前4个参数 ; - 返回值: r0 用于返回函数结果 ; - 函数返回: bx lr 返回到调用者 ; .text ; 声明代码段可执行代码存储区 .global add_asm ; 将add_asm函数导出为全局符号允许外部调用 .global multiply_asm ; 将multiply_asm函数导出为全局符号允许外部调用 ; 加法函数r0a, r1b返回r0ab add_asm: add r0, r0, r1 ; 执行加法r0 r0 r1 bx lr ; 跳回调用者lr保存返回地址 ; 乘法函数r0a, r1b返回r0a*b multiply_asm: mul r0, r0, r1 ; 执行乘法r0 r0 * r1 bx lr ; 跳回调用者main.c#includestdio.h#includestdlib.hexternintadd_asm(inta,intb);externintmultiply_asm(inta,intb);intmain(intargc,char**argv){printf(ARM汇编与C语言混合编程示例\n);printf(\n\n);// 1. 简单加法运算intx15,y25;intsumadd_asm(x,y);printf(1. 加法运算:\n);printf( %d %d %d\n\n,x,y,sum);// 2. 乘法运算inta12,b8;intproductmultiply_asm(a,b);printf(2. 乘法运算:\n);printf( %d × %d %d\n\n,a,b,product);return0;}编译方法Android环境Android.mkLOCAL_PATH : $(call my-dir) LOCAL_ARM_MODE : arm LOCAL_MODULE : math_demo LOCAL_SRC_FILES : src/main.c src/math_asm.s LOCAL_LDLIBS : -llog include $(BUILD_EXECUTABLE)ARM环境将main.c和math_asm.s编译为可执行文件math_demogcc-omath_demo main.c math_asm.s-marm运行程序调用流程图示C层main函数开始C层赋值 x15, y25C层调用 add_asm(x, y)CPU自动操作1. x15 → r0y25 → r12. 把C层下一条指令printf1的地址存入 lr3. 跳转到汇编 add_asm 入口汇编层执行 add r0, r0, r1 → r040汇编层执行 bx lr → 跳回 lr 保存的地址printf1C层sum r040→ 赋值给sum变量C层执行 printf1 → 打印 152540C层赋值 a12, b8C层调用 multiply_asm(a, b)CPU自动操作1. a12 → r0b8 → r12. 把C层下一条指令printf2的地址存入 lr3. 跳转到汇编 multiply_asm 入口汇编层执行 mul r0, r0, r1 → r096汇编层执行 bx lr → 跳回 lr 保存的地址printf2C层product r096→ 赋值给product变量C层执行 printf2 → 打印 12×896C层main函数结束功能说明1、 关键知识点解释extern 函数声明C 代码中必须用extern int add_asm(int a, int b);声明汇编函数告诉编译器这个函数的返回值是int参数是两个int函数的实现不在当前 C 文件中需要链接时从汇编文件中找。2、 参数 / 返回值的映射C 代码中调用add_asm(x, y)时编译器会自动把x放入r0、y放入r1汇编函数执行完后r0中的值会被编译器当作返回值赋值给 C 代码中的sum。3、 编译选项-marm强制 gcc 使用ARM指令集而非Thumb指令集。4、bx lr 是汇编函数返回的关键确保执行完汇编逻辑后回到 C 代码继续运行。拆解「调用→执行→返回」全流程C代码sum add_asm(x, y)调用汇编函数汇编层add_asm执行加法后靠bx lr回到 C 代码继续执行。步骤 1C 调用add_asm前CPU 自动给lr赋值当 C 代码执行到int sum add_asm(x, y);这一行时编译器先把 C 参数映射到 ARM 寄存器x15放入r0y25放入r1CPU 会把「调用完add_asm后要执行的下一条指令地址」自动存入lr寄存器—— 这个地址就是 C 代码中printf(1. 加法运算:\n);这行代码的内存地址CPU 跳转到汇编函数add_asm的第一条指令add r0, r0, r1开始执行。此时lr里存的是printf(1. 加法运算:\n);的内存地址记为0xXXXX1234。步骤 2汇编函数执行核心逻辑add_asm执行add r0, r0, r1r0 15 25 40加法结果此时lr里的地址0xXXXX1234没有被修改依然保存着返回地址。步骤 3bx lr完成返回回到 C 层继续执行执行bx lr时bx指令的作用是「跳转到指定寄存器存储的地址」这里就是跳转到lr里的0xXXXX1234也就是printf那行代码的地址CPU 跳过去后C 代码就从printf(1. 加法运算:\n);开始继续执行同时把汇编函数中r040的值赋值调用约定要点r0–r3用作参数和临时寄存器r0为返回值寄存器被调用者无需保存caller-saved。如果函数使用了需保留的寄存器如r4–r11需在函数内保存/恢复。先明确核心概念新手友好版在 ARM EABI 调用约定中寄存器被分为两类核心目的是平衡 “执行效率” 和 “程序稳定性”表格寄存器分类包含寄存器核心规则谁负责保存通俗理解调用者保存Caller-Savedr0-r3, lr被调用函数可随意修改调用者自己负责保存“临时工作台”用完不用还被调用者保存Callee-Savedr4-r11被调用函数如果用了必须先保存、用完恢复“公用储物柜”用完要还原要点 1r0–r3 用作参数 / 临时寄存器r0 存返回值被调用者无需保存规则拆解结合咱们的代码参数传递C 层调用add_asm(x, y)时编译器自动把第 1 个参数x放入r0第 2 个参数y放入r1r2/r3 用于第 3/4 个参数超过 4 个参数用栈咱们的add_asm/multiply_asm直接用r0/r1计算完全符合这个规则。返回值传递汇编函数计算完后结果必须放在r0中比如add r0, r0, r1直接把和存入r0C 层会自动从r0读取值赋值给sum/product这就是为什么咱们的代码能正确拿到返回值。“被调用者无需保存” 的含义咱们的汇编函数可以随意修改r0-r3比如把r0改成加法结果不用管修改前的值因为调用者C 层的 main 函数如果需要保留r0-r3里的原有数据会自己提前保存比如压入栈不需要被调用的汇编函数操心。代码示例符合规则add_asm: add r0, r0, r1 ; 直接修改r0无需保存原有值 bx lr ; 符合规则r0-r3无需保存违反规则的后果反面例子如果把返回值存到r1而不是r0add_asm: add r1, r0, r1 ; 错误结果存到r1 bx lr→ C 层读取r0时拿到的还是原来的15最终打印152515完全错误。要点 2使用 r4–r11 需保存 / 恢复被调用者负责咱们的加法 / 乘法函数只用到了r0-r1所以不需要处理r4-r11但如果汇编函数用到了r4-r11必须遵循 “先保存、后使用、最后恢复” 的原则否则会破坏调用者C 层的寄存器数据导致程序崩溃。正面示例使用 r4 的正确写法假设写一个 “累加 10 次” 的汇编函数需要用r4做循环计数器; 函数sum_10_asm(r0初始值) → 返回r0初始值12...10 sum_10_asm: push {r4, lr} ; 1. 保存r4被调用者需保存和lr防止嵌套调用覆盖 mov r4, #1 ; 2. 使用r4作为计数器初始值1 mov r1, r0 ; 临时存初始值到r1 loop: add r0, r0, r4 ; 累加r0 r0 r4 add r4, r4, #1 ; 计数器1 cmp r4, #10 ; 判断是否到10 ble loop ; 没到10就继续循环 pop {r4, lr} ; 3. 恢复r4和lr的原始值 bx lr ; 返回关键步骤解释保存push {r4, lr}把r4和lr压入栈lr也保存是因为循环里没有嵌套调用但养成习惯更安全使用随意修改r4做计数器不影响外部恢复pop {r4, lr}把栈里的值弹回r4和lr还原到调用前的状态返回bx lr正常返回。违反规则的后果反面例子如果不用push/pop保存r4sum_10_asm: mov r4, #1 ; 直接修改r4未保存 loop: add r0, r0, r4 add r4, r4, #1 cmp r4, #10 ble loop bx lr→ C 层的 main 函数如果之前用r4存了重要数据比如循环变量会被这个汇编函数覆盖导致 main 函数逻辑混乱程序大概率崩溃。调用约定部分总结结合前文代码r0-r3 是 “临时寄存器”咱们的加法 / 乘法函数直接用r0/r1传参、r0存返回值符合 “被调用者无需保存” 的规则这也是代码能正确运行的核心r4-r11 是 “保护寄存器”只有用到时才需要保存 / 恢复咱们的简单函数没用到所以不用处理调用约定的本质是 C 和汇编之间的 “协议”双方都遵守这个协议才能保证参数、返回值、寄存器状态的一致性程序才不会出错。简单记r0-r3随便用r4-r11用了就要 “先存后还”返回值必须放r0。总结lr 的核心作用调用汇编函数前CPU 会把「C 层接下来要执行的代码地址」比如 printf1、printf2存到lr里相当于给汇编函数留了一个 “回家的门牌号”这个地址是自动存的不需要你写代码干预。bx lr 的核心作用汇编函数执行完计算add/mul后bx lr就是 “按门牌号找回家的路”—— 跳回lr里存的地址回到 C 层继续执行图中F和M两个节点就是bx lr的执行效果是整个 “调用 - 返回” 链路的关键闭环。寄存器映射C 层的参数会自动映射到 ARM 寄存器x→r0、y→r1汇编计算后的结果存在 r0 里回到 C 层后会自动赋值给 sum/product 变量这是 ARM EABI 调用约定的具体体现。bx lr 说明结合 C 和 汇编代码bx lr的核心作用可以总结为 2 点lr是「C 层调用汇编函数时的 “回家地址”」存的是 C 代码下一行的地址bx lr就是「汇编函数执行完后按这个 “回家地址” 跳回 C 层」让整个程序从调用处继续执行不会中断或跑飞。bx lr就是汇编函数对 C 层说“我算完了加法 / 乘法结果在 r0 里你接着从 printf 那行继续执行吧”。