ARM指令ADR和LDR浅析
LDR R0, _START ;指将_START标记的内存位置的值载入到R0。
但是,ARM汇编器又为LDR赋予另一个伪指令含义:用于地址读取。这完全是两种不同的应用,但都是用LDR表示,所以很容易混淆。“用于地址读取”是指将用于表示“地址”的值写入到寄存器中,该寄存器中存储的就是地址,这非常类似于指针。LDR作为伪指令的用法是:在标号或立即数前面加上等号表示地址。例如:
LDR R0, =_START ;表示将_START标号所在的地址写入R0
LDR R0, =0x12345678 ;表示将0x12345678这个地址值写入R0
因为LDR本身既是指令又是伪指令,而比较难于理解的是伪指令LDR,所以在此先声明,本文以下只讨论LDR伪指令,所称的LDR也只是作为伪指令使用的LDR,不要与LDR指令混淆。
当LDR作为伪指令使用时,就又引出了另一个近亲,ADR伪指令。
ADR和LDR都是用于读取地址的伪指令,都是把一个地址写入到一个寄存器中,都是类似于指针的作用,被写入的寄存器中的值将被用于寻址内存。
但ADR只能使用标号,不能使用立即数,也没有等号,它的典型用法是:
ADR R0, _START ;表示将_START标号所在的地址写入R0
但它们还有更本质的区别,ADR是读取相对地址,LDR是读取绝对地址。有人会觉得这又有什么关系呢,反正就是地址的不同表示方法呗。虽然是这样说,但是这里面关系可大了。代码的存储位置都是在连接的时候由连接文件指定的,而且在运行时还可能会被到不同的位置。如果把一段代码里每个涉及存储地址的地方都用相对首地址的偏移量来表示,例如_START+0x800,那么这段代码无论将来被到哪里,都能运行无误。但是如果每个地址都是写死的绝对地址,例如 0x30008000,那么必须将这段代码到内存的该地址位置才能正确运行。所以,看起来相对地址比绝对地址更灵活。事实也确实如此,ADR因为更专一,所以编译出来的代码比LDR常常更有效率。
但是,好用是要有代价的,ADR的地址读取范围有限且很小,LDR则可以实现任意地址值的读取。为什么会这样?还要从ADR和LDR之所以被称为伪指令谈起。伪指令就是因为它们并不是实际执行的指令,而是汇编器要在汇编阶段将它们替换成可执行的ARM指令。那么实际执行的是什么指令呢?
LDR后面的地址范围如果未超出MOV的范围,会被汇编器替换成MOV指令,如果超出了MOV范围,则汇编器要为它再开个内存位置,叫内存池,专门存储这个值,因为内存能存储32位的值,从内存池中把这个值取出来给寄存器,就实现了任意地址值的加载。内存池中的数在运行中不能变,写的是几就是几,所以内存池是LDR的优势,同时也是它的劣势。再说一下为什么MOV会有范围限制,因为ARM指令是定长的,只能是32位,MOV指令字本身要占去几位,剩余的位数才能给有效数据使用,所以能存储在MOV指令码里的数据肯定达不到32位,要想取32位数就只能用内存池。ARM指令说明里面有MOV详细的换算过程,不详述。
ADR伪指令可以在运行时读取相对位置。但是有范围限制,因为ADR要使用相对地址就要在运行中找出当前地址位置,对ADR伪指令,汇编器是通过使用加减法指令来替换的。遇到ADR,编译器会用一条ADD或SUB当前PC寄存器和一个常数来实现ADR伪指令的功能,这样就实现了运行时相对当前地址的读取。若不能用一条指令替换,则产生错误,编译失败。而这里因为ADD和SUB又遇到了与MOV同样的范围问题,所以ADR不可能像LDR从内存池取数那样实现任意32位地址的读取。确切的说,ADR的地址读取范围是:
当地址值是字节对齐时,其取指范围为: -255 ~ 255B;
当地址值是字对齐时,其取指范围为: -1020 ~ 1020B;
如果要实现大一点的跳转,可以使用ADRL伪指令,在汇编器编译源程序时,ADRL伪指令被替换成两条合适的指令。若不能用两条指令实现,则产生错误,编译失败。
当地址值是字节对齐时,其取指范围为: -64K~64K;
当地址值是字对齐时,其取指范围为: -256K~256K;
以此类推,虽然ARM汇编器并没有提供能编译成三条合适指令的ADR伪指令,但是想象一下,如果能编译成三条,ADR读取的地址范围就会更大了。但那样效率又降低了,所以还是不划算,ARM果断放弃,真有那种需要,那就自己换算地址去写吧。汇编虽然很低层,还不足够低层。
ARM指令ADRLD 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)