x86-64架构下switch语句对应汇编跳转表代码的疑问解析
解析x86-64汇编中
addq %rcx, %rax的作用 对应的C源码
void switch_eg(long x, long n, long* dest) { long val = x; switch(n) { case 0: val *= 13; break; case 2: val += 10; case 3: val += 11; break; case 4: case 6: val += 11; break; default: val = 0; } *dest = val; }
对应的x86-64汇编代码
.LFB0: // x in %rdi, n in %rsi, *dest in %rdx cmpq $6, %rsi ja .L8 // if n > 6, switch->default ------------------------- leaq .L4(%rip), %rcx movslq (%rcx,%rsi,4), %rax addq %rcx, %rax jmp *%rax ------------------------- .L4: .long .L3-.L4 // case 0 .long .L8-.L4 // case 1 .long .L5-.L4 // case 2 .long .L6-.L4 // case 3 .long .L7-.L4 // case 4 .long .L8-.L4 // case 5 .long .L7-.L4 // case 6 .L3: leaq (%rdi,%rdi,2), %rax // %rax = 3 * val leaq (%rdi,%rax,4), %rdi // val = 4 * 3 * val + val = 13val jmp .L2 // break .L5: addq $10, %rdi // val += 10 .L6: addq $11, %rdi // val += 11 .L2: movq %rdi, (%rdx) // *dest = val ret // return .L7: addq $11, %rdi // val += 11 jmp .L2 // break .L8: movl $0, %edi // val = 0 jmp .L2 // break
用户疑问
我对上述汇编中标记段的代码存在疑问:
我理解leaq .L4(%rip), %rcx的作用是将跳转表数组的首地址存入%rcx寄存器;movslq (%rcx,%rsi,4), %rax是将跳转表中对应n的数组元素值存入%rax寄存器,该元素应为对应case操作的地址偏移。但我不清楚addq %rcx, %rax这句指令的具体含义,希望能得到专业的解释。
解答
这是因为跳转表.L4里存储的不是绝对地址,而是相对于跳转表首地址.L4的偏移量,我们需要把这个偏移量转换成实际的内存地址才能完成跳转。我来一步步拆解逻辑:
leaq .L4(%rip), %rcx:通过RIP相对寻址计算出跳转表.L4在内存中的绝对地址,存入%rcx。x86-64下编译器默认生成位置无关代码(PIC),这种方式能保证程序不管被加载到内存的哪个位置,都能正确找到跳转表的位置。movslq (%rcx,%rsi,4), %rax:从跳转表中取出第n个元素(每个元素是4字节的.long类型,所以用%rsi*4作为偏移量定位),再通过movslq把32位的有符号偏移量扩展为64位长整型存入%rax。你说得没错,这个元素就是对应case代码块相对于.L4的地址差值,比如.L3-.L4就是.L3地址减去.L4地址的结果。addq %rcx, %rax:把跳转表的绝对地址%rcx和偏移量%rax相加,得到的就是对应case代码块的绝对内存地址。举个例子,如果%rax里存的是.L3-.L4,加上%rcx(也就是.L4的地址),最终结果就是.L3的实际运行地址。
这种设计的核心目的是保证代码的位置无关性——不管程序被加载到内存的哪个区域,只要通过「表基地址+偏移量」的计算,就能得到正确的跳转目标,让代码可以在不同的内存地址空间正常运行。最后执行jmp *%rax,就能直接跳转到对应的case处理逻辑了。
内容的提问来源于stack exchange,提问作者solego
相关产品推荐
相关产品推荐

