ARM汇编在格式上有少数硬性要求,在排版上几乎没什么硬性要求,都不多,以下分别说明。
格式要求
ARM 汇编有一些格式上的硬性要求,这些规则由汇编器(如 GNU 的
gas
、ARM 官方的armasm
)强制执行,违反会导致编译错误。以下是核心硬性要求:1. 指令格式:操作码与操作数的顺序
- 必须遵循 “操作码 目标操作数,源操作数” 的顺序(与 x86 汇编相反)。
例如:ADD R0, R1, R2 ; 正确:R0 = R1 + R2(目标在前,源在后) ; 错误写法:ADD R1, R2, R0(顺序颠倒)
- 立即数前必须加
#
,寄存器前必须加R
(或r
,大小写不敏感):MOV R0, #10 ; 正确:R0 = 10 ; 错误写法:MOV 0, 10(缺少R和#)
2. 标签定义:必须以冒号
:
结尾
- 标签(用于跳转或数据引用)必须以
:
结尾,且单独占一行(或行首):loop: ; 正确:定义loop标签B loop ; 跳转至loop ; 错误写法:loop B loop(标签未加:)
3. 段定义:必须通过伪指令声明
- 代码、数据必须放在明确的段中,通过
.text
(代码段)、.data
(数据段)等伪指令声明:.text ; 正确:代码段开始 main:MOV R0, #0.data ; 正确:数据段开始 var: .word 100 ; 错误:无段声明直接写指令或数据
4. 注释格式:必须用特定符号开头
- GNU 汇编器(
gas
)中,注释以@
开头;ARM 汇编器(armasm
)支持;
或@
:MOV R0, #0 @ 正确:GNU风格注释(推荐) MOV R1, #1 ; 正确:ARM汇编器兼容的注释
5. 指令集模式匹配
- ARM 指令集(32 位)和 Thumb 指令集(16/32 位混合)不能混用,需通过伪指令声明模式:
.arm ; 声明ARM模式(32位指令) ADD R0, R1, R2 ; 32位ARM指令.thumb ; 声明Thumb模式(16/32位指令) ADD R0, R1 ; 16位Thumb指令(操作数限制更严格) ; 错误:在Thumb模式下使用ARM特有的32位指令
6. 对齐要求:数据和指令的地址对齐
- 数据类型需按大小对齐(如
.word
占 4 字节,需 4 字节对齐):.align 2 ; 强制4字节对齐(2^2=4) var: .word 0x12345678 ; 正确:地址为4的倍数 ; 错误:未对齐时,某些架构可能无法访问数据
- 指令对齐:Thumb 指令需 2 字节对齐,ARM 指令需 4 字节对齐(汇编器通常自动处理,但复杂场景需手动用
.align
控制)。7. 伪指令格式:必须以
.
开头
- 汇编器伪指令(如段定义、数据定义)必须以
.
开头:.global main ; 正确:声明全局符号 .word 100 ; 正确:定义4字节数据 ; 错误:global main(缺少.)
8. 寄存器使用规范
- 特殊寄存器(如
PC
、LR
、SP
)有固定用途,不能随意当作普通寄存器使用:MOV PC, LR ; 正确:用LR恢复PC实现返回 ; 错误:MOV LR, #10(随意修改LR会破坏返回地址)
总结
ARM 汇编的硬性要求主要体现在:指令格式、标签定义、段划分、注释符号、指令集模式、对齐规则 等方面。这些规则确保汇编器能正确解析代码,生成符合目标架构的机器码。不同汇编器(如
gas
和armasm
)可能有细微差异,但上述核心规则是通用的。
排版建议
ARM 汇编对排版(格式布局)没有像高级语言那样严格的语法约束,但良好的排版习惯能提高代码可读性,且部分汇编器对特定元素的位置有隐性要求。以下是需要注意的排版规则和建议:
一、硬性排版要求(违反会报错)
标签必须位于行首
标签(带:
的符号)必须放在一行的开头,不能缩进,否则汇编器会将其识别为指令而非标签:loop: ; 正确:标签在行首B loop ; 指令缩进(推荐); 错误示例:标签前有空格error_label: ; 汇编器会报错(无法识别为标签)
指令和伪指令不能与标签同行
标签必须单独占一行,不能跟在指令或伪指令后面data: .word 100 ; 错误:标签与伪指令同行; 正确写法: data:.word 100
操作数之间的逗号必须紧跟前一个操作数
指令的操作数之间用逗号分隔,逗号后可加空格,但逗号不能单独换行:ADD R0, R1, R2 ; 正确 ADD R0,R1,R2 ; 正确(无空格也可); 错误示例:逗号后换行 ADD R0, R1, R2 ; 汇编器会将第二行识别为独立指令,导致错误
二、推荐排版规范(提高可读性)
指令和伪指令缩进
标签不缩进,指令、伪指令和注释缩进(通常用 4 个空格或 1 个 Tab),使代码结构清晰:main:push {lr} ; 缩进指令mov r0, #0 ; 缩进指令.data ; 伪指令也建议缩进 var:.word 100
注释与代码分隔
注释尽量放在指令右侧,或单独占一行,与代码保持一定距离(如用多个空格分隔):; 推荐:注释与代码用空格分隔 MOV R0, #10 @ 设置返回值为10; 不推荐:注释紧贴代码,可读性差 MOV R0,#10@返回值
同类指令对齐
操作码、操作数、注释分别对齐,形成列结构,尤其适合批量处理的指令:MOV R0, #0 @ 初始化参数1 MOV R1, #100 @ 初始化参数2 MOV R2, #255 @ 初始化参数3
空行分隔逻辑块
用空行分隔不同功能的代码块(如初始化、循环、函数调用等),增强层次感:main:push {lr}; 初始化参数mov r0, #5mov r1, #3; 调用加法函数bl addpop {pc}
大小写统一
寄存器(R0
/r0
)、指令(ADD
/add
)、伪指令(.text
/.TEXT
)的大小写不影响汇编器解析,但建议统一风格(如大写指令、小写寄存器):; 推荐:风格统一 add r0, r1, r2 @ 小写指令和寄存器; 不推荐:大小写混乱 Add R0, r1, R2
三、汇编器的灵活性
- 多数 ARM 汇编器(如
gas
)对空格、换行不敏感,只要语法正确,即使排版混乱也能编译通过(但可读性极差)。- 唯一的例外是标签必须在行首,这是几乎所有汇编器都严格要求的排版规则。
总结
ARM 汇编的硬性排版要求极少,仅需遵守 “标签行首”“操作数逗号不换行” 等规则;但推荐的排版规范(缩进、对齐、空行等)能极大提升代码的可维护性,尤其在复杂程序中更为重要。良好的排版习惯是汇编开发的基本素养。
AAPCS(Procedure Call Standard for the ARM Architecture)是ARM 架构的过程调用标准,定义了 ARM 程序中函数调用时必须遵循的一套规则,确保不同语言(如 C 和汇编)、不同编译单元之间的函数调用能够正确交互。
AAPCS调用规范
简单说,AAPCS 的核心作用是:规定函数调用时参数如何传递、返回值如何存储、寄存器如何使用、栈如何维护,避免因调用规则不统一导致程序崩溃。
AAPCS 的核心内容
1. 参数传递规则
- 前 4 个参数:通过寄存器
r0-r3
传递(称为 “参数寄存器”)。
- 例如:
func(a, b, c, d)
中,a→r0
,b→r1
,c→r2
,d→r3
。- 第 5 个及以后的参数:从右向左依次压入栈(栈增长方向为低地址)。
- 例如:
func(a, b, c, d, e)
中,e
先压栈,再压入其他超出 4 个的参数。- 参数类型适配:
- 32 位数据(int、指针等)直接放入寄存器或栈。
- 64 位数据(long long、double)占用两个连续寄存器(如
r0-r1
)或栈空间。- 结构体等大型数据:通常通过指针传递(放入
r0
),或按规则拆分到寄存器 / 栈。2. 返回值存储规则
- 32 位返回值(int、float、指针等):存入
r0
。- 64 位返回值(long long、double):存入
r0-r1
(低 32 位在r0
,高 32 位在r1
)。- 大型返回值(如结构体):调用者预先在栈上分配空间,将空间地址通过
r0
传递给被调函数,被调函数将结果写入该地址。3. 寄存器使用规范
寄存器分为调用者保存寄存器和被调用者保存寄存器,职责明确:
- 调用者保存寄存器(
r0-r3
、r12
):
- 用途:传递参数、临时计算。
- 规则:被调函数可以随意修改,调用者若需保留这些寄存器的值,需在调用前手动压栈保存。
- 被调用者保存寄存器(
r4-r11
、r14
=LR):
- 用途:保存局部变量、函数上下文。
- 规则:被调函数若使用这些寄存器,必须在函数入口处压栈保存,退出前恢复(确保调用者的上下文不被破坏)。
- 特殊寄存器:
r13
(SP):栈指针,必须保持 4 字节或 8 字节对齐(依架构而定)。r15
(PC):程序计数器,不可手动修改(通过分支指令间接更新)。4. 栈的使用规则
- 栈对齐:函数调用前后,栈指针(SP)必须保持 8 字节对齐(确保 64 位数据访问正确)。
- 栈帧结构:通常包含以下部分(从高地址到低地址):
高地址 → 调用者的栈帧被调用者保存的寄存器(r4-r11等)局部变量临时数据/参数(第5个及以后的参数) 低地址 → SP(栈指针)
- 栈增长方向:向低地址增长(压栈时 SP 减小,出栈时 SP 增大)。
5. 其他重要规则
- Thumb 与 ARM 指令集兼容:混合使用时需确保调用跳转符合指令集切换规范(如用
BX LR
而非MOV PC, LR
)。- 位置无关代码(PIC):函数调用需支持动态地址重定位(通过相对寻址实现)。
- 浮点参数:通过 VFP(向量浮点单元)寄存器
s0-s15
传递,遵循类似整数寄存器的规则。为什么需要 AAPCS?
如果没有统一的调用标准,会导致:
- C 函数调用汇编函数时,参数传递方式不匹配(如 C 存
r0
,汇编读r1
)。- 寄存器被意外修改(如汇编函数修改了
r4
但未恢复,导致 C 函数的局部变量错乱)。- 栈对齐错误(导致硬件无法访问 64 位数据,触发异常)。
AAPCS 通过统一这些规则,确保不同语言、不同模块的代码能够无缝协作,是 ARM 架构下混合编程(如 C 与汇编互调)的基础。
实际开发中,编译器(如 GCC)会自动遵循 AAPCS 生成代码,手动编写汇编时则需要严格遵守这些规则,才能确保函数调用正确。