ARM编程进阶之一-ARM汇编伪指令
b)、编译器是如何得出InitStack所代表的地址是0x64?
编译器知道MOV R0, LR这条指令相对于整个程序的第1条指令的偏移量为0x64;同时又知道这个程序将来在内存中的运行地址为0x0,所以编译器在编译的时候(不是程序运行的时候)就可以确定InitStack所代表的地址为0x64+0x0=0x64。那么编译器又是如何知道“程序将来在内存中的运行地址”的呢?其实,这个“程序将来在内存中的运行地址”,我通常称它为“程序的期望运行地址”,简称“运行地址”,以后我也将这样称呼它。它其实是在编译程序前由程序员告知编译器的。
c)、如果将来程序并没有运行在它的运行地址处,很显然这个程序就会出问题。如何解决?
出问题的原因,显然是由于ldr伪指令使用的绝对地址。所以,解决的办法就是使用相对地址,进行相对寻址。这就要用到我们下面要介绍的另一条伪指令adr
2、adr
ADR伪指令的作用与LDR伪指令的作用相同,都是将标号所代表的地址赋予寄存器,不过2者的实现机制是完全不同的:ldr采用绝对地址,adr采用相对地址。
ADR伪指令将基于PC相对偏移的地址值读取到寄存器中。在汇编源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能。
很显然,由于将ADR R0, Delay替换为ADD r0, pc, #0x3c,而它是以当前指令的地址(pc的值)进行相对地址计算的。所以即使将来程序没有实际运行在运行地址处,也不会有问题。
当然,这里还有2个问题:
a)、既然adr和ldr完成类似的功能,adr又能避免绝对地址的问题,还要ldr伪指令有何用?
这主要是因为,adr伪指令要求标号与adr伪指令必须在同一个段中(段的概念参见“ARM汇编伪操作”一文),而ldr伪指令则没有这样的要求。
b)、add r0, pc, #0x3c中的常数0x3c是放在机器指令12bit中的立即数,这个立即数有可能不能被12bit表示出来。此时编译会产生错误。如果出现这样的情况,又应该如何办?
使用下面要讲的伪指令adrl
3、adrl
在汇编源程序时,ADRL伪指令被编译器替换成两条合适的指令。其本质是:将偏移量这个立即数(可能不能被12bit表示出来)拆分为2个可以被12bit表示的立即数,然后用2条add(或sub)指令来替换adrl伪指令。
当然你会问,如果那个立即数非常特殊,无论如何也拆分不成2个可以被12bit表示的立即数(也就是说需要拆分为3个甚至更多的数),那又应该如何办?关于这个问题,我在这里不予回答,不过你要记住一句话,如果机器智能到啥都能做的话,你作为程序员就失业了!哈哈!
4、nop
NOP是no operation的意思,就是CPU不做任何事的意思。这里千万要明白,CPU一旦上电就将永不停歇地运行,绝不可能有一条指令能另CPU什么都不做。所以,该伪指令在汇编时将会被代替成“MOV R0,R0”指令。
NOP主要用于短延时操作,关于这一点,这里我要多说几句。与应用程序不同,汇编程序通常要用于控制硬件,例如,你需要使用汇编程序要求某个硬件执行某个操作(例如:初始化nandflash),并要在该操作完成后才能进行后面的操作,而硬件从接到命令到完成该操作需要一段时间(该时间很短,但对CPU而言却较长,通常需要几个指令周期)。此时,程序员就必须在发出命令的指令和后续操作之间做延时,通常的做法就是加入几个NOP伪操作。
附:ldr指令与ldr伪指令的4种形式(这4种形式,极其容易让初学者困惑,所以在此集中列出)
ldr r0, [r1] | 指令,将内存存放的内容加载到r0中 |
ldr r0, label | 指令,将标号label所代表的内存地址处存放的内容加载到r0中 |
ldr r0, =10000 | 伪指令,将常数10000赋予r0(采用ldr指令+文字池的方式实现) |
ldr r0, =lable | 伪指令,将标号label所代表的内存地址赋予r0 |
评论