底层原理,  技术人生

ARM汇编基础:寻址

ARM汇编中常用的内存寻址方式包括寄存器间接寻址(indirect addressing)、偏移量寻址(offset (indexed) addressing)、预索引寻址(pre-indexed (pre-increment) addressing)和后索引寻址(post-indexed (post-increment) addressing)。

ARM版本:ARM V7 32位

ARM属于完全基于寄存器的指令集架构(ISA),在涉及数值运算或操作的指令中操作数不能直接包含内存地址,而一般先将操作数加载到寄存器中。本文所说的寻址,便是指从内存加载(或写入内存)数据到寄存器中的这一过程,一般使用LDR/STR指令。

需要注意的是,由于ARM的指令是定长32位(RISC指令集),而完整的内存地址同样是32位,因此无法将完整的内存地址编码进指令操作,从而也就无法实现“直接寻址”。然而LDR/STR指令支持类似于直接寻址的写法,如LDR R0, a ; a为使用DEFW定义的内存标签。实际上,在汇编器实际编译时会将这种写法转译编码为“寄存器间接寻址”的方式,从而解决上述问题。

任务:分别采用四种寻址方式实现一字符串输出程序,并比较不同方法的执行效率。

程序要求:有一特定字符串,起始地址储存在寄存器R1中,使用循环计算字符串中所有字符的算数值的总和,将结果存储在R0中。(注:算数总和指字符串中每个字符的ASCII表示值的和,如字符串”10″的和为49+48=97)

a) 寄存器间接寻址 indirect addressing
	MOV R0, #0	; R0 = total
again	LDRB R2, [R1]	; R2 = next byte
	CMP R2, #0
	ADDNE R1, R1, #1
	ADDNE R0, R0, R2
	BNE again

使用R1寄存器保存单个字符的内存地址,R2寄存器存储字符。每次循环需要使用ADD指令将R1+1,从而遍历整个字符串。累加的结果保存在R0中。为ADD和B指令添加条件判断,依据字符串末尾的’\0’决定是否结束遍历。

执行效率:5指令/循环 + 1初始指令

b) 偏移量寻址 offset (indexed) addressing
	MOV R3, #0	; R3 = index
	MOV R0, #0	; R0 = total
again	LDRB R2, [R1,R3]	; R2 = next byte
	CMP R2, #0
	ADDNE R3, R3, #1
	ADDNE R0, R0, R2
	BNE again

R1寄存器保存字符串的起始地址,使用R3寄存器作为偏移量,每次循环+1,读取字符时将R1+R3的值指向的内存地址中的数据加载到R2中。偏移量寻址不改变基址和索引(偏移量)寄存器的值,需要手动进行递增。使用该方法的优点是保留了字符串开头的地址信息,代价是多使用一个寄存器。

执行效率:5指令/循环 + 2初始指令

(b) = (a) + 额外初始化和使用R3寄存器

c) 预索引寻址 pre-indexed (pre-increment) addressing
	MOV R0, #0	; R0 = total
	SUB R1, R1, #1
again	LDRB R2, [R1, #1]!	; R2 = next byte
	CMP R2, #0
	ADDNE R0, R0, R2
	BNE again

指令 “LDRB R2, [R1, #1]!” 首先执行 R1 = R1 + 1,然后将R1指向的内存地址中的数据加载到R2中。这样便可以节省一个寄存器来额外存储索引值,也不需要额外指令为R1执行递增操作。由于R1一开始就会+1,因此需要在迭代前先做一次减法以从首字符开始迭代。

执行效率:4指令/循环 + 2初始指令

(c) = (a) – ADD (循环体中减少1指令)

优化技巧:将两条初始指令替换为 “LDR R0, [R1] ; R0 = first byte” 可以额外减少一条SUB指令和一轮迭代次数。

执行效率:4指令/循环 (迭代次数-1) + 1初始指令 (这么做将比(d)更快,但无法处理空字符串的情况)

d) 后索引寻址 post-indexed (post-increment) addressing
	MOV R0, #0	; R0 = total
again	LDRB R2, [R1],#1	; R2 = next byte
	CMP R2, #0
	ADDNE R0, R0, R2
	BNE again

指令 “LDRB R2, [R1],#1” 首先将R1寄存器指向的内存地址中的数据加载到R2中,然后执行 R1 = R1 + 1。使用后索引不再需要先对R1做减法运算,R1会在返回值后自动递增,进一步提高了效率。

执行效率:4指令/循环 + 1初始指令

(d) = (c) – 初始SUB

总结

事实上,在以上例子中,使用ADDNE替代ADD指令是没有必要的,因为在本例中这么做并不会影响最终R0的累加计算结果。(字符串以’\0’结尾,其值为0)

执行效率比较:(d) 优于 (c) 优于 (a) 优于 (b) (但(c)有进一步优化技巧)

A WindRunner. VoyagingOne

留言

您的邮箱地址不会被公开。 必填项已用 * 标注