ARM 汇编指令:STP\LDP

ARM 汇编指令:STP\LDP

本文来自于我关于 ARM 汇编指令系列文章。欢迎阅读、点评与交流~
1、汇编指令在不同架构中的联系与区别
2、ARM 汇编指令:MOV
3、ARM 汇编指令:LDR
4、ARM 汇编指令:STR
5、ARM 汇编指令:MRS 和 MSR
6、ARM 汇编指令:ORRS
7、ARM 汇编指令:BEQ
8、ARM 汇编指令:TST
9、ARM 汇编指令:B
10、ARM 汇编指令:BX
11、ARM 汇编指令:ERET
12、ARM 汇编指令:STP\LDP
13、ARM 汇编指令:UBFX

STP 和 LDP 是 ARMv8-A 架构中非常核心且高效的一对指令,用于同时存储/加载两个寄存器。它们对栈操作、函数调用约定和内存数据块操作至关重要。

核心概念

  • STPStore Pair。将两个寄存器的值存储到相邻的内存地址中。
  • LDPLoad Pair。从相邻的内存地址中加载数据到两个寄存器中。
  • 本质 :它们是单条指令,但完成两次内存访问,这比使用两条单独的 STR/LDR 指令更高效。
  • 主要用途
    1. 函数开场/收场(Prologue/Epilogue) :保存和恢复被调用者保存的寄存器(如 x29, x30 即 FP, LR),以及分配/释放栈空间。
    2. 在内存和寄存器之间移动大数据块
    3. 高效的结构体/数组元素存取

基本语法

复制代码
STP  Rt1, Rt2, [Rn, #offset]  // Store
LDP  Rt1, Rt2, [Rn, #offset]  // Load
  • Rt1, Rt2:要存储或加载的源/目标寄存器。通常是通用寄存器(X0-X30)或浮点/向量寄存器(D0-D31, Q0-Q31)。
  • Rn:基址寄存器,存放内存地址。
  • [Rn, #offset]:寻址模式。offset 是一个有符号的立即数,必须是 8 的倍数(因为一次操作 2 * 64-bit = 16 字节,要求地址对齐;而立即数单位是字节,只要第一个数是8字节对齐的,第二个也是对齐的)。

寻址模式(关键!)

寻址模式决定了如何计算内存地址以及如何更新基址寄存器 Rn。这是理解 STP/LDP 用法的核心。

1. 偏移模式 (Offset)

地址计算为 Rn + offset不更新 Rn

复制代码
STP X0, X1, [SP, #16]  // 将 X0, X1 存储到地址 SP+16 和 SP+24。SP 本身不变。
LDP X0, X1, [SP, #-16] // 从地址 SP-16 和 SP-8 加载到 X0, X1。SP 本身不变。
2. 前变基模式 (Pre-index)

地址计算为 Rn + offset然后 将计算后的地址写回 RnRn = Rn + offset)。
语法 :在偏移外加上 !

复制代码
STP X0, X1, [SP, #-16]! // 1. SP = SP - 16; 2. 将 X0, X1 存储到 [SP] 和 [SP+8]。
                         // 这是典型的【分配栈空间并保存寄存器】 。
3. 后变基模式 (Post-index)

使用 Rn 的原始值作为地址,然后 更新 RnRn = Rn + offset)。
语法 :偏移放在 ] 后面。

复制代码
LDP X0, X1, [SP], #16   // 1. 从 [SP] 和 [SP+8] 加载到 X0, X1; 2. SP = SP + 16。
                         // 这是典型的【恢复寄存器并释放栈空间】。

典型用例示例

用例 1:函数开场与收场(最常见)
armasm 复制代码
// 函数开场 (Prologue)
my_function:
    STP X29, X30, [SP, #-16]! // 将帧指针(X29)和返回地址(X30)压栈,同时 SP -= 16
    MOV X29, SP               // 设置新的帧指针

    // 函数体 ...

// 函数收场 (Epilogue)
    LDP X29, X30, [SP], #16   // 从栈中恢复 X29, X30,同时 SP += 16
    RET                       // 使用恢复的 X30 返回

解释

  • STP ... ! 在栈上分配了16字节空间,并保存了 X29, X30
  • LDP ..., [SP], # 恢复这两个寄存器,并回收栈空间。
  • 这对操作保证了栈的平衡。
用例 2:交换两个寄存器的值(无需临时寄存器)
armasm 复制代码
// 假设我们要交换 X0 和 X1 的值
STP X0, X1, [SP, #-16]! // 将 X0, X1 的值临时压栈
LDP X1, X0, [SP], #16   // 以相反的顺序加载回来,实现交换
用例 3:加载/存储结构体成员
c 复制代码
// 假设一个结构体: struct { long a; long b; } s;
// 地址在 X0 中,我们想加载 a 和 b 到 X1, X2
LDP X1, X2, [X0]        // X1 = s.a, X2 = s.b

// 或者想存储 X3, X4 到 s.a, s.b
STP X3, X4, [X0]
用例 4:批量内存拷贝(简化循环)
armasm 复制代码
// 从 X1 指向的内存拷贝 32 字节到 X0 指向的内存
LDP X2, X3, [X1]        // 加载前16字节
LDP X4, X5, [X1, #16]   // 加载后16字节(偏移模式)
STP X2, X3, [X0]        // 存储前16字节
STP X4, X5, [X0, #16]   // 存储后16字节

重要注意事项

  1. 寄存器顺序 :在内存中,Rt1 总是存储在较低地址,Rt2 在较高地址(Rt1 -> [address], Rt2 -> [address+8])。

  2. 对齐offset 必须是 8 的倍数。产生的内存地址通常也建议自然对齐(即地址是所传输数据总大小的倍数),非对齐访问可能降低性能或导致异常。

  3. 浮点/向量寄存器 :同样适用于 S, D, Q 寄存器。

    armasm 复制代码
    STP D0, D1, [X0]    // 存储两个双精度浮点数
    LDP Q0, Q1, [X1, #32]! // 加载两个128位向量,并更新基址
  4. 栈指针 (SP) :对 SP 的使用必须保持 16 字节对齐 ,这是 ARMv8 的硬性规定。STP/LDP 是维护这种对齐的首选指令。

  5. 符号扩展LDP 有带符号扩展的变体,如 LDPSW,用于加载两个 32 位字并将其符号扩展到 64 位寄存器。

总结

特性 STP LDP
全称 Store Pair Load Pair
作用 寄存器的值 -> 内存 内存的值 -> 寄存器
关键寻址 偏移 ([Rn, #off])、前变基 ([Rn, #off]!)、后变基 ([Rn], #off)
主要用途 保存寄存器、压栈、写数据块 恢复寄存器、出栈、读数据块
栈操作核心 STP X29, X30, [SP, #-16]! (分配空间并保存) LDP X29, X30, [SP], #16 (恢复并释放空间)

掌握 STP/LDP 及其寻址模式,是理解 ARMv8 汇编中栈管理和高效内存访问的关键。

相关推荐
管理大亨20 小时前
Linux vs Windows:五大维度深度对决
linux·arm开发·windows
Trouvaille ~21 小时前
【Linux】库制作与原理(三):动态链接与加载机制
linux·c语言·汇编·got·动静态库·动态链接·plt
XXYBMOOO1 天前
理解 C++ 中的字节序转换函数 `swapEndian`
开发语言·arm开发·c++
猫猫的小茶馆1 天前
【ARM】从零封装STM32标准库
汇编·arm开发·stm32·单片机·嵌入式硬件·架构
切糕师学AI1 天前
ARM 汇编指令:PUSH 和 POP
汇编·arm开发·assembly
切糕师学AI1 天前
ARM 汇编指令:ERET
汇编·arm开发
AI_56782 天前
从“3秒一帧”到“实时识别”——ARM平台OpenCV优化实战
arm开发·人工智能·opencv
切糕师学AI2 天前
ARM 汇编指令:BX
汇编·arm开发·assembly
ShiMetaPi2 天前
GM-3568JHF丨ARM+FPGA异构开发板系列教程:外设教程 05 蓝牙
arm开发·fpga开发·fpga·rk3568