GAS汇编器cmp指令为何不遵循source, dest语法?还有哪些类似情况?
AT&T语法(GAS)与Intel语法的核心差异
GNU汇编器(GAS)采用的AT&T语法和主流Intel语法在指令格式上存在不少容易踩坑的差异,除了你提到的减法类指令的表现,以下是最常见的几类:
1. 操作数顺序(最易混淆)
这是两类语法的核心区别:
- AT&T遵循 源在前,目的在后:比如
subq %rbx, %rax执行%rax = %rax - %rbx - Intel遵循 目的在前,源在后:对应指令为
sub rax, rbx,语义完全相同
关于cmp指令:它的本质是计算「目的操作数 - 源操作数」并更新标志位,不修改任何操作数。所以:
- AT&T:
cmpq %rbx, %rax→ 计算%rax - %rbx并设置标志位 - Intel:
cmp rax, rbx→ 计算rax - rbx并设置标志位
你提供的验证代码也能佐证这个逻辑:
.globl _start # How many data elements we have .section .text _start: movq $3, %rax movq $4, %rbx cmpq %rax, %rbx # 计算 %rbx - %rax = 4-3=1,标志位标记结果为正且无进位 jb rdiis1 # 无符号小于则跳转(4不小于3,不触发) ja rdiis2 # 无符号大于则跳转(4>3,触发跳转) rdiis1: movq $1, %rdi jmp end rdiis2: movq $2, %rdi # We're done - end: movq $60, %rax syscall
编译运行后执行echo $?返回2,完全符合%rbx > %rax时的分支逻辑。
2. 操作数前缀规则
- 寄存器:AT&T必须给寄存器加
%前缀,比如%rax、%rbx;Intel直接写寄存器名 - 立即数:AT&T必须给立即数加
$前缀,比如movq $3, %rax;Intel直接写数值,比如mov rax, 3 - 符号常量:AT&T引用符号常量也要加
$,比如movq $my_var, %rax;Intel直接写mov rax, my_var
3. 内存寻址格式
两者的内存寻址语法差异明显:
- AT&T格式:
disp(base, index, scale),比如movq 8(%rbp, %rdi, 4), %rax指向内存地址%rbp + %rdi*4 + 8 - Intel格式:
[base + index*scale + disp],对应写法为mov rax, [rbp + rdi*4 + 8]
额外注意:AT&T中movq my_var, %rax是把my_var的地址加载到rax;如果要加载my_var的实际值,需要用movq (%rip), %rax(PC相对寻址)或加*前缀;而Intel中mov rax, my_var直接加载值,取地址需加offset关键字。
4. 指令宽度后缀
AT&T通过后缀明确指定操作数宽度:
b(8位字节)、w(16位字)、l(32位长字)、q(64位四字),比如movb $0x1, %al、addl %ebx, %eax- Intel无需后缀,可通过操作数宽度自动推断,或显式指定(比如
mov byte ptr [rax], 0x1)
5. 条件跳转指令命名
AT&T的条件跳转指令基于标志位状态命名,和Intel的对应关系需要注意:
- AT&T的
jb(jump if below,无符号小于)对应Intel的jb/jnae - AT&T的
ja(jump if above,无符号大于)对应Intel的ja/jnbe
本质标志位判断逻辑一致,但新手容易混淆命名对应关系。
6. 段寄存器前缀写法
- AT&T:段前缀写在操作数前,格式为
%cs:,比如movq %cs:my_var, %rax - Intel:段前缀写在内存操作数前,格式为
cs:,比如mov rax, cs:[my_var]
内容的提问来源于stack exchange,提问作者ntos
相关产品推荐
相关产品推荐

