3.6 lea 指令

lea(Load Effective Address,加载有效地址)是 x86-64 中一条独特且强大的指令。它计算内存引用的地址并将其存入目标寄存器,不访问内存

基本语法

leaq    源操作数(S), 目的操作数(D)    # D = &S

其中源操作数使用 的寻址格式,但指令只计算地址,不读取内存。

lea 与 mov 的关键区别

# 假设 %rdi = 0x1000,内存[0x1000] = 0x42
 
movq    (%rdi), %rax       # rax = 内存[0x1000] = 0x42  (读取内存)
leaq    (%rdi), %rax       # rax = 0x1000              (只计算地址)
特性leamov
访问内存❌ 不访问✅ 访问(当源操作数是内存引用时)
用途地址计算 / 算术运算数据传送
性能快(仅 ALU 运算)可能慢(含访存延迟)

核心用途

1. 计算指针地址

// C 语言示例
long *ptr = &array[i];
# %rdi = &array, %rsi = i
leaq    (%rdi, %rsi, 8), %rax    # rax = array + i*8 = &array[i]

2. 执行算术运算(无乘法指令)

leaq 可以高效完成形如 a + b × k + c 的运算,其中 k ∈ {1,2,4,8}

# 实现 t = x + y*8 + 3
leaq    3(%rdi, %rsi, 8), %rax   # rax = rdi + rsi*8 + 3
 
# 实现 t = x*4 + y*2
leaq    (%rdi, %rdi, 4), %rax    # rax = rdi + rdi*4 = rdi*5 (不是乘以 4!)
# 注意:需要组合使用
leaq    (%rdi, %rdi, 2), %rax    # rax = rdi * 3
addq    %rdi, %rax              # rax = rdi * 4  (或用左移 shlq $2, %rdi)

3. 快速乘法(乘数为小的 2 的幂 ±1)

# 乘以 3
leaq    (%rdi, %rdi, 2), %rax    # rax = rdi * 3
 
# 乘以 5
leaq    (%rdi, %rdi, 4), %rax    # rax = rdi * 5
 
# 乘以 9
leaq    (%rdi, %rdi, 8), %rax    # rax = rdi * 9
 
# 乘以 7 (需要两步)
leaq    (%rdi, %rdi, 8), %rax    # rax = rdi * 9
subq    %rdi, %rax              # rax = rdi * 9 - rdi = rdi * 7

4. 寄存器间加法(不修改条件码)

# 实现 rax = rdi + rsi (与 addq 类似,但不设置条件码)
leaq    (%rdi, %rsi), %rax       # rax = rdi + rsi
 
# 对比 addq
addq    %rsi, %rdi               # rdi = rdi + rsi (会设置条件码)

leaq 不修改 ,在某些场景下比 addq 更合适。

5. 实现 &(s->field)

struct S {
    int a;
    long b;
    double c;
};
 
// 获取成员 b 的指针
long *pb = &s->b;
# %rdi 指向 struct S
leaq    8(%rdi), %rax           # rax = rdi + 8 = &(s->b)

lea 的算术能力限制

leaq 能表达的运算受寻址格式限制:

运算lea 支持示例
x + cleaq c(%rdi), %rax
x × k仅 k=1,2,4,8leaq (, %rdi, k), %rax
x + yleaq (%rdi, %rsi), %rax
x + y×k仅 k=1,2,4,8leaq (%rdi, %rsi, k), %rax
x + y×k + c仅 k=1,2,4,8leaq c(%rdi, %rsi, k), %rax
x × 常数 (非 2 的幂)需组合指令

编译器使用 lea 的典型优化

// C 源代码
long calc(long a, long b, long c) {
    return a * 3 + b * 5 + c * 7;
}
# gcc -O2 生成的汇编
calc:
    leaq    (%rdi, %rdi, 2), %rax    # rax = a * 3
    leaq    (%rsi, %rsi, 4), %rdx    # rdx = b * 5
    addq    %rdx, %rax               # rax = a*3 + b*5
    leaq    (%rcx, %rcx, 8), %rdx    # rdx = c * 9
    subq    %rcx, %rdx               # rdx = c*9 - c = c*8 ??? 不是 c*7
    addq    %rdx, %rax               # rax = a*3 + b*5 + c*8 (编译器可能不同优化)
    ret

编译器会智能选择 lea + add/sub/shl 的组合来优化整数乘法。

lea 的典型模式总结

#           结果 = 基址 + 变址×比例 + 偏移
#                  ↓       ↓        ↓
leaq    offset(base, index, scale), dest
模式等价运算
leaq (%r1), %r2r2 = r1
leaq (%r1, %r2), %r3r3 = r1 + r2
leaq (, %r1, k), %r2r2 = r1 × k
leaq (%r1, %r2, k), %r3r3 = r1 + r2 × k
leaq k(%r1), %r2r2 = r1 + k
leaq k(%r1, %r2, s), %r3r3 = r1 + r2 × s + k

相关笔记

    • lea 使用的寻址格式
    • 与 lea 对比的算术指令
    • 数组地址计算使用 lea
    • lea 不修改条件码的特性

链接到