ARM汇编中使用=运算符与Literal Pool文字池加载数据的区别是什么
两种加载方式的核心区别
本质上你看到的LDR Rd, =label是ARM汇编器提供的语法糖,本身不是ARM硬件原生支持的指令,它的底层实现其实就是自动生成文字池条目,和你手动写文字池的逻辑本质是相通的,两者的差异主要在以下几点:
- 实现逻辑不同
汇编器处理LDR R1, =number时,会自动在当前代码段附近的文字池里分配一个4字节的空间存储number的地址,然后把这条伪指令替换为PC相对寻址的真实LDR指令:LDR R1, [PC, #到自动生成的地址条目的偏移量]。而手动定义文字池的方案是你完全控制地址条目的命名、存储位置,汇编器不会做额外的自动处理。 - 灵活性和容错性不同
用=运算符的方案汇编器会做额外的优化:如果number的地址值刚好符合ARM MOV指令的立即数编码规则(比如是小于256的常数,或者常数循环右移偶数位可以得到),汇编器会直接把这条伪指令优化成MOV R1, #number,不需要访问内存,运行效率更高。而手动写的文字池方案只会走PC相对寻址加载的逻辑,不会有自动优化。
另外如果代码很长,手动定义的address_of_number距离加载指令超出了ARM LDR PC相对寻址的偏移范围(32位ARM模式下是±4KB,Thumb模式更小),会直接编译报错,需要你手动调整文字池的位置。用=运算符的话汇编器会自动在合适的位置插入文字池,避免超出偏移范围。 - 可维护性不同
当需要加载多个地址常量时,用=运算符不需要手动给每个地址都写对应的文字池条目,代码更简洁,也能避免手动定义条目标错地址的问题。
为什么还要手动使用文字池方案
虽然=运算符更易用,但以下场景还是需要手动定义文字池:
- 需要精确控制代码段的内存布局:比如编写Bootloader、内核等底层代码,或者位置无关代码(PIC)时,需要严格控制每一段数据的存储地址,不能让汇编器随意插入自动生成的文字池破坏布局,手动定义文字池可以实现完全可控。
- 兼容老旧汇编器:部分非常老旧的ARM汇编器不支持
LDR Rd, =label这种伪指令语法,只能通过手动定义文字池实现地址加载。 - 复用地址常量:如果有多处代码都需要加载同一个地址常量,手动定义的带标签的文字池条目可以被多处复用,避免汇编器自动生成多个重复的地址条目,节省代码空间。
额外的理解纠正
你之前认为=运算符和C语言的*类似是错误的:LDR R1, =number的作用是把number对应的内存地址加载到R1,等价于C语言的R1 = &number;;而你写的第二条LDR R1, [R1]才是对地址解引用取里面的值,等价于C语言的R1 = *R1;,对应C的*运算符。
内容的提问来源于stack exchange,提问作者Hamza Qureshi
相关产品推荐
相关产品推荐

