汇编
程序编码
在编译时指定'-Og'选项让GCC产生符合原始程序结构的机器代码
机器级代码
对C语言隐藏, 但对汇编代码可见的:
- 程序计数器
- 整数寄存器文件
- 条件码寄存器
- 向量寄存器
输出c源码的机器表示
gcc -Og -S mstore.c机器代码与反汇编的特性:
- x86-64的指令长度从1-15字节不等 
- 指令设计的格式, 从某个给定位置, 能将字节唯一解码成机器指令 - _哈夫曼编码_ 
- 反汇编无需访问源代码 
- 反汇编与gcc的命名规则有些许差别 - _比如movq的q在反汇编中会被省略_ 
关于格式的注解
.file    "mstore.c"    .text    .globl    mulstore    .type    mulstore, @functionmulstore:.LFB0:    .cfi_startproc    pushq    %rbx    .cfi_def_cfa_offset 16    .cfi_offset 3, -16    movq    %rdx, %rbx    call    mult2    movq    %rax, (%rbx)    popq    %rbx    .cfi_def_cfa_offset 8    ret    .cfi_endproc.LFE0:    .size    mulstore, .-mulstore    .ident    "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"    .section    .note.GNU-stack,"",@progbits以上是gcc完整生成的.s文件
所有. 开头的是伪指令, 可以忽略
ATT汇编代码格式
.file    "mstore.c"    .text    .globl    mulstore    .type    mulstore, @functionmulstore:.LFB0:    .cfi_startproc    pushq    %rbx    .cfi_def_cfa_offset 16    .cfi_offset 3, -16    movq    %rdx, %rbx    call    mult2    movq    %rax, (%rbx)    popq    %rbx    .cfi_def_cfa_offset 8    ret    .cfi_endproc.LFE0:    .size    mulstore, .-mulstore    .ident    "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"    .section    .note.GNU-stack,"",@progbits数据格式
- 8位: 字节(b)
- 16位: 字(w)
- 32位: 双字(l)
- 64位: 四字(q)
访问信息
x86-64的CPU包含一组16个存储64位的通用目的寄存器
63                     31         15          8          0%rax                   %eax       %ax         %ah         %al 返回值%rbx                   %ebx       %bx         %bh         %bl 被调用者保存%rcx                   %ecx       %cx         %ch         %cl 第4个参数%rdx                   %edx       %dx         %dh         %dl 第3个参数%rsi                   %esi       %si                     %sil 第2个参数%rdi                   %edi       %di                     %dil 第1个参数%rbp                   %ebp       %bp                     %bpl 被调用者保存%rsp                   %esp       %sp                     %spl 栈指针%r8                    %r8d       %r8w                    %r8b 第5个参数%r9                    %r9d       %r9w                    %r9b 第6个参数%r10                   %r10d      %r10w                   %r10b 调用者保存%r11                   %r11d      %r11w                   %r11b 调用者保存%r12                   %r12d      %r12w                   %r12b 被调用者保存%r13                   %r13d      %r13w                   %r13b 被调用者保存%r14                   %r14d      %r14w                   %r14b 被调用者保存%r15                   %r15d      %r15w                   %r15b 被调用者保存每个寄存器都可以作为8位、16位、32位、64位来访问
- %rax %rbx %rcx %rdx 是通用目的64位寄存器,用于存储操作数和计算结果 
- %rsi %rdi 是源索引寄存器和目的索引寄存器,通常用于字符串和数据传输 
- %rbp %rsp 是基址指针寄存器和栈指针寄存器,用于处理函数调用和栈操作 
- %r8 %r9 %r10 %r11 %r12 %r13 %r14 %r15 是额外的通用目的寄存器,用于扩展操作数和寄存器的数量 
- 16位操作可以访问2位字节 
- 32位操作可以访问4位字节... 
操作数指示符
- 立即数: 代表常数 - $后面接c语言表示法的整数 
- 寄存器: 表示寄存器里的内容 - $r_a$用来表示寄存器a 用$R[r_a]$表示里面的内容 
- 内存引用: 指定内存地址里的内容 M[地址] 

数据传送指令
| 指令 | 效果 | 描述 | 
|---|---|---|
| MOVE S,D | D←S | 传送 | 
| movb | 传送字节 | |
| movw | 传送字 | |
| movl | 传送双字 | |
| movq | 传送四字 | |
| movabsq I,R | R←I | 传送绝对的四字 | 
零扩展:用于将较窄的整数类型(如8位或16位整数)扩展为较宽的整数类型(如32位或64位整数),原始数据的低位(等于或小于原始数据位数的位)保持不变,而高位被填充为零
符号扩展:用于将较窄的整数类型(如8位或16位整数)扩展为较宽的整数类型(如32位或64位整数),原始数据的低位(等于或小于原始数据位数的位)保持不变,而高位被填充为原始数据的符号位
| 指令 | 效果 | 描述 | 
|---|---|---|
| MOVEZ S,R | R←零扩展(S) | 以零扩展进行传送 | 
| movzbw | 将做了零扩展的字节传送到字 | |
| movzbl | 将做了零扩展的字节传送到双字 | |
| movzwl | 将做了零扩展的字传送到双字 | |
| movzbq | 将做了零扩展的字节传送到四字 | |
| movzwq | 将做了零扩展的字传送到四字 | |
| MOVS S,R | R←符号扩展(S) | 以符号扩展进行传送 | 
| movsbw | 将做了符号扩展的字节传送到字 | |
| movsbl | 将做了符号扩展的字节传送到双字 | |
| movswl | 将做了符号扩展的字传送到双字 | |
| movsbq | 将做了符号扩展的字节传送到四字 | |
| movswq | 将做了符号扩展的字传送到四字 | |
| movslq | 将做了符号扩展的整形传送到四字 | |
| cltq | %rax←符号扩展(%eax) | 把%eax符号扩展到%rax | 
压入栈和弹出栈数据
. 将四字压入栈pushq S . 将四字弹出栈popq D%rsp 是栈指针 %rax是返回值
算术和逻辑操作
| 指令 | 效果 | 描述 | 
|---|---|---|
| leaq S,D | D←&S | 加载有效地址 | 
| INC D | D←D+1 | 加1 | 
| DEC D | D←D-1 | 减1 | 
| NEG D | D←-D | 取反 | 
| NOT D | D←~D | 取反 | 
| ADD S,D | D←D+S | 加 | 
| SUB S,D | D←D-S | 减 | 
| IMUL S,D | D←D*S | 乘 | 
| XOR S,D | D←D^S | 异或 | 
| OR S,D | D←D | 或 | 
| AND S,D | D←D&S | 与 | 
| SAL S,D | D←D<<S | 左移 | 
| SHL S,D | D←D<<S | 左移 = SAL | 
| SAR S,D | $D←D>>_AS$ | 算术右移 | 
| SHR S,D | $D←D>>_LS$ | 逻辑右移 | 
加载有效地址
. x= y+x*4leaq    (%rdi,%rsi,4), %rax一元和二元操作
. 从%edi中减去%esisubl    %esi, %edi移位操作
. 将x左移四位salq    $4, %rax特殊的算术操作
| 指令 | 效果 | 描述 | 
|---|---|---|
| imulq S | R[%rdx]: R[%rax]←S * R[%rax] | 有符号全乘法 | 
| mulq S | R[%rdx]: R[%rax]←S * R[%rax] | 无符号全乘法 | 
| clto | R[%rdx]: R[%rax]← 符号扩展(R[%rax]) | 转换为八字 | 
| idivq S | R[%rdx]←R[%rdx]: R[%rax] mod/÷ S | 有符号除法 | 
| divq S | R[%rdx]←R[%rdx]: R[%rax] mod/÷ S | 无符号除法 | 
控制
条件码
cmp 指令在被执行时,会首先比较两个变量的大小,并根据比较结果,动态调整 CPU 上 FLAGS 寄存器中的相应位
test 指令的执行方式与 cmp 类似,只不过它会对传入的两个操作数做隐式的“与”操作,而非减法操作
| 标志位名称 | 位 | 全称 | 什么情况下置位(即变更为值1) | 
|---|---|---|---|
| CF | 0 | Carry | 指令执行引起了进位或借位 | 
| PF | 2 | Parity | 指令执行结果的最低有效字节中值为1的位个数为偶数 | 
| ZF | 6 | Zero | 指令执行结果为0 | 
| SF | 7 | Sign | 指令执行结果的最高有效位为1 | 
| OF | 11 | Overtlow | 当操作致被当做有符号数时,指令的执行产生了溢出 | 
| 指令 | 基于 | 描述 | 
|---|---|---|
| CMP S2,S1 | S1-S2 | 比较 | 
| cmpb | 比较 byte | |
| cmpw | 比较 word | |
| cmpl | 比较 double word | |
| TEST S2,S1 | S1&S2 | 测试 | 
| testb | 测试 byte | |
| testw | 测试 word | |
| testl | 测试 double word | 
这些指令不修改寄存器的值,只设置条件码
cmp     DWORD PTR [rbp-4], 1        jne     .L2        mov     eax, 101        jmp     .L3.L2:        mov     eax, 10.L3:        pop     rbp        ret对应于
if (num == 1) {    return 101;}return 10;循环、选择等操作,都是通过 cmp + jmp 来实现的
读取条件码
| 指令 | 别名 | 效果 | 设置条件 | 
|---|---|---|---|
| sete D | setz | D←ZF | 相等或为0 | 
| setne D | setnz | D←!ZF | 不相等或非0 | 
| sets D | D←SF | 负数 | |
| setns D | D←!SF | 非负数 | |
| setg D | setnle | D←~(SF ^ OF) & ~ZF | 有符号大于 | 
| setge D | setnl | D←~(SF ^ OF) | 有符号大于或等于 | 
| setl D | setnge | D←SF ^ OF | 有符号小于 | 
| setle D | setng | `D←(SF ^ OF) | ZF` | 
| seta D | setnbe | D←~CF & ~ZF | 无符号大于 | 
| setae D | setnb | D←~CF | 无符号大于或等于 | 
| setb D | setnae | D←CF | 无符号小于 | 
| setbe D | setna | `D←CF | ZF` | 
跳转指令
| 指令 | 别名 | 跳转条件 | 描述 | 
|---|---|---|---|
| jmp LABEL | 1 | 直接跳转 | |
| jmp *Operand | 1 | 间接跳转 | |
| je LABEL | jz | ZF | 相等或为0 | 
| jne LABEL | jnz | ZF | 不相等或不为0 | 
| js LABEL | SF | 负数 | |
| jns LABEL | ~SF | 非负数 | |
| jg LABEL | jnle | ~(SF ^ OF) & ~ZF | 有符号大于 | 
| jge LABEL | jnl | ~(SF ^ OF) | 有符号大于或等于 | 
| jl LABEL | jnge | SF ^ OF | 有符号小于 | 
| jle LABEL | jng | (SF ^ OF) | ZF | 
| ja LABEL | jnbe | ~CF & ~ZF | 无符号大于 | 
| jae LABEL | jnb | ~CF | 无符号大于或等于 | 
| jb LABEL | jnae | CF | 无符号小于 | 
| jbe LABEL | jna | `CF | ZF` | 
用条件控制实现分支控制
cmpq    %rsi, %rdi        jg      .L4        movq    %rdi, %rax        subq    %rsi, %rax        ret.L4:        leaq    (%rdi,%rsi), %rax        ret对应的c代码:
if (x > y){    return x+y;}else{    return x-y;}用条件传送实现条件分支
分支预测
| 指令 | 别名 | 传送条件 | 描述 | 
|---|---|---|---|
| cmov S,R | cmovz | ZF | 相等或为0 | 
| cmovne S,R | cmovnz | ~ZF | 不相等或非0 | 
| cmovs S,R | SF | 负数 | |
| cmovns S,R | ~SF | 非负数 | |
| cmovg S,R | cmovnle | ~(SF ^ OF) & ~ZF | 有符号大于 | 
| cmovge S,R | cmovnl | ~(SF ^ OF) | 有符号大于或等于 | 
| cmovl S,R | cmovnge | SF ^ OF | 有符号小于 | 
| cmovle S,R | cmovng | `(SF ^ OF) | ZF` | 
| cmova S,R | cmovnbe | ~CF & ~ZF | 无符号大于 | 
| cmovae S,R | cmovnb | ~CF | 无符号大于或等于 | 
| cmovb S,R | cmovnae | CF | 无符号小于 | 
| cmovbe S,R | cmovna | `CF | ZF` | 
循环
- do-while 
- while - guarded-do
 
- for 
switch语句
跳转表:一种用空间换时间的条件匹配策略,这种优化手段通过将每个case标签生成一个唯一的标号,然后创建一个跳转表,其中每个条目对应一个case标签,再通过 jmp 指令,对输入值进行计算,以计算出跳转表的实际索引,然后跳转过去
jmp qword ptr [8*rdi +.LJTIO_0].LJT10_0:  .quad .LBBO 4  ...函数调用
- 传递控制
- 传递数据
- 分配和释放内存
运行时栈
使用栈帧,调用一个方法,就把该方法的变量表、返回地址等压入栈来实现,当从当前方法返回,把当前方法的栈帧弹掉,此时就返回上一个方法了,这点跟JVM的实现是一样的

如果被调用的函数内部没有对其他函数的调用,可以执行一项叫做函数内联的优化,内联带来的优化是,CPU 需要执行的指令数变少了,根据地址跳转的过程不需要了,压栈和出栈的过程也不用了,是一种空间换时间的策略
转移控制
保存当前程序地址,将程序计数器设置为新过程地址 返回时读取保存的地址,继续执行
在 x86-64 架构下,其会通过 call 指令将当前的程序地址压入栈中,并跳转到指定的函数地址,被调用的函数通过 ret 指令返回结果
int foo(int n) {    return n +1;}int main() {    foo(1);    return 1;}foo(int):        push    rbp        mov     rbp, rsp        mov     DWORD PTR [rbp-4], edi        mov     eax, DWORD PTR [rbp-4]        add     eax, 1        pop     rbp        retmain:        push    rbp        mov     rbp, rsp        mov     edi, 1        call    foo(int)        mov     eax, 1        pop     rbp        ret参数传递

返回值传递
- 函数调用产生整数类型的返回值,且小于等于 64 位时,通过寄存器 rax 进行传递
- 大于 64 位,小于等于 128 位时,则使用寄存器 rax 与 rdx 分别存储返回值的低 64 位与高 64 位
- 对于浮点数类型的返回值,默认使用 xmm0 与 xmm1 寄存器进行存储。而当返回值过大时,则会选择性使用 ymm 与 zmm 来替代 xmm 寄存器
栈上的局部存储
寄存器中的局部存储空间
- 被调用者保存寄存器
- 调用者保存寄存器
递归过程
尾递归优化
在一定条件下,编译器可以直接利用跳转指令取代函数调用指令。尾递归调用的一个重要条件是:递归调用语句必须作为函数返回前的最后一条语句
编译器会使用跳转指令(如je、jne、jle等)来替换函数调用时所使用的 call 指令,这样就
int f(int i, int sum) {    if (i == 0) {        return sum;    }    return (i - 1, sum * i);}f(int, int):        mov     eax, esi        test    edi, edi        je      .L1        imul    eax, edi.L1:        ret数组的分配和访问
基本原则
T A[N]指针运算
&D [ i ] [ j ] = X
D
L(Ci+j)
定长数组
变长数组
异质的数据结构
都是对地址进行偏移得到的
- 结构
- 联合
- 数据对齐
指针
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值
对于这样的一条语句
int *p = &n;其会通过 lea 指令找到 n 的地址,然后通过 mov 指令将 n 的值赋给 p
lea     rax, [rbp-12]mov     QWORD PTR [rbp-8], rax浮点代码
%ymm0 ~ %ymm15
浮点传送和转换操作
| 指令 | 源 | 目的 | 描述 | 
|---|---|---|---|
| vmovss | $M_{32}$ | X | 传送单精度数 | 
| vmovss | X | $M_{32}$ | 传送单精度数 | 
| vmovsd | $M_{64}$ | X | 传送双精度数 | 
| vmovsd | X | $M_{64}$ | 传送双精度数 | 
| vmovaps | X | X | 传送对齐的封装好的单精度数 | 
| vmovapd | X | X | 传送对齐的封装好的双精度数 | 
| vcvttss2si | $X/M_{32}$ | $R_{32}$ | 用截断的方法把单精度数转换成整数 | 
| vevttsd2si | $X/M_{64}$ | $R_{32}$ | 用截断的方法把双精度数转换成整数 | 
| vcvttss2siq | $X/M_{32}$ | $R_{64}$ | 用截断的方法把单精度数转换成四字整数 | 
| vcvttsd2siq | $X/M_{64}$ | $R_{64}$ | 用截断的方法把双精度数转换成四字整数 | 
| 指令 | 源1 | 源2 | 目的 | 描述 | 
|---|---|---|---|---|
| vcvtsi2ss | $M_{32}/R_{32}$ | X | X | 把整数转换成单精度数 | 
| vcvtsi2sd | $M_{32}/R_{32}$ | X | X | 把整数转换成双精度数 | 
| vcvtsi2ssq | $M_{64}/R_{64}$ | X | X | 把四字整数转换成单精度数 | 
| vcvtsi2sdq | $M_{64}/R_{64}$ | X | X | 把四字整数转换成双精度数 | 
过程中的浮点代码
使用XMM寄存器来传递浮点参数
浮点运算操作
| 单精度 | 双精度 | 效果 | 描述 | 
|---|---|---|---|
| vaddss | vaddsd | D←S2+S1 | 浮点数加 | 
| vsubss | vsubsd | D←S2-S1 | 浮点数减 | 
| vmulss | vmulsd | D←S2XS1 | 浮点数乘 | 
| vdivss | vdivsd | D←S2/S1 | 浮点数除 | 
| vmaxss | vmaxsd | D←max(S2,S1) | 浮点数最大值 | 
| vminss | vminsd | D←min(S2,S1) | 浮点数最小值 | 
| sgrtss | sqrtsd | $D←\sqrt{S1}$ | 浮点数平方根 | 
定义和使用浮点常数
浮点操作不能把立即数作为操作数
编译器必须为所有浮点常量初始化存储空间
在浮点代码中使用位级操作
| 单精度 | 双精度 | 效果 | 描述 | 
|---|---|---|---|
| vxorps | vorpd | D←S2·S1 | 位级异或(EXCLUSIVE-OR) | 
| vandps | andpd | D←S2&S1 | 位级与(AND) |