《汇编语言程序设计》学习笔记(3)三、C与汇编语言

时间:2022-03-09 12:45:59

3.1 80x86汇编与C语言-1

3.1.1 80x86汇编与C语言-1 - 汇编程序员眼中的系统结构

汇编程序员眼中的系统结构

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

如何从C代码生成汇编代码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

gcc -O2 -S code.c -m32 -fno-omit-frame-pointer

-O2 表示有一定的优化的 level ;
-S 表示要从 code.c 原始 c 程序,把它编译成一个.s汇编程序;
-m32 表示要生成32位代码。

汇编语言数据格式

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.1.2 80x86汇编与C语言-1 - 第一条汇编指令

第一条汇编指令实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

l 表示两个整数相加。

这条add指令操作数有两个 实际上就是x加y等于t因为是两个操作数 那么我们肯定知道就是说 肯定是8ebp eax相加 加起来之后的和放到eax里面去 也就是说这个指令的操作数是两个 目的寄存器是在右侧 那个 当然它既是目的又是源实际上 两个都是源 加完之后放到目的寄存器 目的寄存器就是右侧的第二个源 加起来就完了

数据传送指令(mov)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

(
立即数,实际上就是常整数。
)

数据传送指令支持的不同操作数类型组合

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

括号里面是什么呢 表示内存地址。(应该是指大括弧。)

简单的寻址模式

如果我们有个操作数是访问内存的话,那么内存地址怎么计算或者叫做怎么寻址?

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

  • 间接寻址

movl (%ecx),%eax中,(%ecx)就是把寄存器ecx里面的数值作为一个Memory的address,去访问Memory Address里面的数据,把这个数据拿出来,而不是Address拿出来,是把Memory Address所指明的那个位置里面的数据拿出来,把它作为操作数mov过去。这就叫做间接寻址。

  • 基址加偏移量的寻址

还有个叫做基址加偏移量的寻址 实际上跟那个差不多 无非就是说 我在括号外面加了一个常数 比方说这里面就是8 8括号百分号ebp 寄存器ebp里面的值拿出来加上8 加出来的和作为内存的地址 有了内存地址之后呢 把内存中的这个数取出来 取出来之后再挪过去 这个叫做基址加偏移量寻址

所以注意这种表示方式 括号里面百分号ecx 那么这样子的表示方式ecx value表示Address 如果外面再有一个常数 注意常数前面是没有dollar号 如果加上dollar号就是什么了 就表示它是一个常量不是地址 不要加这个 把两个数加起来之后变成内存地址 这就取出来 取出来的这个数就作为操作数之一挪过去

3.1.3 80x86汇编与C语言-1 - 寻址模式

寻址模式使用实例

swap.c:

void swap(int *xp, int *yp)
{
        int t0 = *xp;
        int t1 = *yp;
        *xp = t1;
        *yp = t0;
}

汇编得到的swap.s:

$ gcc -O2 -S swap.c -m32 -fno-omit-frame-pointer
$ cat swap.s
        .file   "swap.c"
        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .text
.LHOTB0:
        .p2align 4,,15
        .globl  swap
        .type   swap, @function
swap:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        pushl   %ebx
        .cfi_offset 3, -12
        movl    8(%ebp), %edx
        movl    12(%ebp), %eax
        movl    (%edx), %ecx
        movl    (%eax), %ebx
        movl    %ebx, (%edx)
        movl    %ecx, (%eax)
        popl    %ebx
        .cfi_restore 3
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   swap, .-swap
        .section        .text.unlikely
.LCOLDE0:
        .text
.LHOTE0:
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

实例分析

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

后续详细请看视频,我也没有听懂这里。

3.1.4 80x86汇编与C语言-1 - 地址计算指令与其它 - 1

变址寻址

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

寻址模式实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

地址计算指令

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

地址计算指令leal l表示后缀就是 我算的目的操作数是double word类型 那它实际上跟mov非常像 也是两个操作数 src和Destination 注意 src是地址计算表达式 什么叫做地址计算表达式 就像我们刚才说过了实际上就是变址寻址 一共四个要素 基址加上index乘上scale 再加上displacement 就是说src就是这种表达方式 只要是这种的合法表达方式 它就可以作为src

Destination一般来说就是个寄存器 就是说我把src的地址表达式值给算出来 算出来的地址赋给Destination

注意它跟mov很像 但是有本质上的不同 mov如果你的src是地址表达式的话 它会去访存 它把地址算出来之后 真真正正的去访存 访问Memory 把Memory中的数据取出来 再mov过去

leal很简单 我要算出的地址就是我所需要的数据 你把这个地址表达式写清楚 算完了 算完了之后这个地址本身 我是作为操作对象 挪到Destination里面去的 这是一个很大的区别 因为它是完成地址计算

所以说它的用途呢一大用途就是地址计算 但不访存 比方说在C里面我定义了数组x 一个什么什么的数组 我要计算它的地址 D等于一个取地址的运算x方括号i 这个很常见 屏幕上打出来的 这个很常见的 这种情况实际上就是用leal指令计算就完了 我还是把它的基址加上index 乘上sizeof就完了 对吧 这个算出来就是我所需要的地址 这个地址就是我的目标 我的目的 把这个目的就是把我要算出的地址值 赋过去赋给p就ok了 所以它可以用于地址计算 无需访存

另外一个是比较巧妙的 地址计算表达式 里面有4个要素 基址 index scale 再加上D这个常量 那么这种情况下呢 它可以完成x加上k乘以y 这一类型的整数计算 这里面的x和y可以是寄存器也就说是可变的 k是个常量 就是说如果你能把 一个整数计算表达成一个这样的形式的话 那么就很方便的可以利用leal来计算 这个比你用单独的 加减或者乘指令分开去运算效率要高要快 所以说我们可以发现 编译器大量的使用leal指令 除了完成地址计算之外 大量的使用leal指令 完成整数数值的计算

整数计算指令

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

算术右移跟逻辑右移不一样的地方在于 因为你的数据整个往右侧挪动了 高位得把别的数据补进来 补什么呢 逻辑右移就单纯的补0 算数右移就补被移动数据的最高位 符号位 补符号位 这是个区别

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.1.5 80x86汇编与C语言-1 - 地址计算指令与其它 - 2

将leal指令用于计算(实例1)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

实例2

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

sarl中,a代表arith算术,r代表right右移。 l 表示它是一个 double word 的运算

3.1.6 80x86汇编与C语言-1 - x86-64下的通寄存器与汇编指令—-初步

x86-32与x86-64的数据类型宽度

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64的通用寄存器

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

rsp 还是保留工作站点寄存器。

x86-32下的swap

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64下的…

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64下long int类型的swap

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86汇编的格式

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

练习题

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.2 80x86汇编与C语言-2

3.2.1 80x86汇编与C语言-2 - 条件码

Control Flow(控制流)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

汇编程序员眼中的体系结构(部分)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

条件码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

首先咱们解释一下什么是条件码 条件码共分成四位 你可以理解为呢 是四个一位的寄存器呗 每个里面呢就存储了这些标志位

第一个呢就是CF位 叫进位位 第二个呢叫符号位 第三个呢叫0 也就是说你的计算结果是不是0由它来标识 第四个呢就是溢出位

这个进位大家我们想想看 可用于检测无符号整数运算的溢出 回想一下如果是两个无符号整数相加的话 如果最高位向上产生了进位 那么就预示着 这时候就无符号整数运算就发生了溢出啦

那么如果这个相加 这个数出来是个负的 就是最高位是个1 当然这种情况下我们就把它作为 带符号处理啦 那么这个时候呢 这SF呢就设成1 否则就设成0 也就是说根据不同的结果来设置

我记得以前呢在课堂上跟大家讲过 就是 一个整数 你是带还是不带符号也好 在机器层面 它的表示是一样的 都是一个0101的一个串 01串 那么在机器看起来 你光从这个存储格式上面是看不出这个数 是带还是不带符号的 而且我们也讲过啦 因为补码的特性 你对它进行简单的加减运算 比如说加减指令 在机器层面来说 补码运算的加减和原码运算的加减 实际上是一套电路 实际上就一条指令 一类指令来实现 所以针对add而言呢 它做加法的话 实际上它是不区分 你是 带符号还是不带符号的整型

但是它们的区别就在这个地方 就是说 add指令它做完之后 或者说在它做的同时 它会判断两个进位标志 就是CF位和OF位 就是说 如果一方面就是把它作为 无符号整数的话 那么如果它溢出了 就会把CF置位 如果把这两个数作为带符号整数处理的话 那么如果它溢出了 就会把OF置位 也就是说硬件上面还是考虑的比较多的

我们以前问过大家一个问题 就是既然在硬件层面如果表示上在指令上 对带符号数和不带符号数 如果都没有区分的话 那么谁来区分这个事儿呢 当然 编译器来区分 编译器会知道你这个数是带符号还是不带符号 但编译器到底怎么区分呢 它也得依赖于硬件的某些指令 或者某些条件码嘛 所以它就依赖于这两个条件码 分别进行判断 如果编译器认为 你这两个数是无符号计算 它有溢出了 那它就通过CF位来判断 那否则呢就通过OF位来判断

3.2.2 80x86汇编与C语言-2 - 比较指令与测试指令

条件码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.2.3 80x86汇编与C语言-2 - 读取条件码指令-1

读取条件码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.2.4 80x86汇编与C语言-2 - 读取条件码指令-2

条件码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

那么读取条件码呢给大家还是一个 典型的比较简单的C语言的一个函数 咱么就看通过Gcc 把这个C函数怎么转换成什么样的一个汇编 在我们直观上就有个认识 就在C层面如果要读取 进行比大比小 两个数比大比小 实际上把这个数返回的话呢

在汇编层面是怎么实现的 那么再说一遍 SetX指令就是读取当前的条件码 或者条件码的一些组合 存入目的的字节寄存器 它只存入一个byte 余下的三个字节呢不会被修改 那么这个时候呢 你把一个寄存器 它的 可能最低的那个字节给改掉了

那么余下的那个高的三个字节怎么办呢 所以一般情况下我们会采用movezbl指令 对目的寄存器啊 进行高位的0扩展 就是顾名思义 我们以前课也讲过 就是最后就move 这时候叫move指令 z的话呢就是说0扩展 因为是b到l嘛 一个byte到一个doubleword 所以就把8位扩展到32位码 那怎么扩展呢就是z 叫zero 就0扩展

3.2.5 80x86汇编与C语言-2 - x86-64下的读取条件码指令

x86-64下读取条件码

读取条件码

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

我们后来翻了一下手册是这么个原因 应该看得清楚 什么意思呢

就是说在64位的这个体系架构下面 X86 64体系结构下面 如果进行了一个32位的操作 那32位的操作呢 产生了一个32位的result 那么就会自动的0扩展 扩展到高32位 也就是说在64位的架构下面这第一条指令 eax自个儿对自个儿做了个异或 做完之后它当然结果是0啦 这个结果0会自动的扩展到它这个 rax的高32位

那这个看上去呢 好像有一点点奇怪 就是说 至少我从指令上来看 我的目的寄存器 是eax 我完全是一个32比特的一个操作 那么为什么你要把我的这个最高的 就是rax 就相当于eax的高32位 给它自动请0 给它0扩展呢

当然这个讲起来的话 实际上跟处理器的流水线的相关性有关系 就跟流水线的相关性有关系 这么做呢实际上是为了减少 就是 流水线运行当中 处理器 啊 不同 就是前后不同指令之间的 数据依赖的关系 这咱们就不细说了

3.2.6 80x86汇编与C语言-2 - 跳转指令

跳转指令

条件码实际上大量的用于什么呢 就用于条件执行 就是说你前面有一系列的语句 比如说你add也好 Test也好 Compare也好 进行了一定的操作 尤其像Compare 两个数据比大比小 那比大比小之外呢 就是 如果数据大怎么着 数据小怎么着 这肯定有不同的if else之类的 这个不同路径去跑 所以这个时候就牵扯到 怎么着 牵扯到跳转指令了 尤其是条件跳转 所以条件码大量的实际上用在这个层面

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

jX j打头就是jump指令 后面呢就是一个后缀 就是说我这个jump依赖于什么条件 或者说什么条件码的组合 那么它这个意思就是说 依赖于当前的条件码 当然或者条件码的组合了 选择下一条执行语句 就是你是顺着跑呢 还是jump到另外一个地方去 叫做跳转指令 那首先就是第一条jmp 那这个没得说 就是我是无条件跳转 就1了 条件是1等于永远满足 那咱们没的说了

那么剩下那个咱们一看呢 当然后缀啊 跟Set差不多 刚才我们就是说sete啊 setne啊 这边是je jne之类的 那什么意思呢 就是说如果相关的条件满足 比如说je 条件码是ZF 这个位是1 也就是说刚才那个比较是个zero 称为结果0或者结果相等 那么这种情况下呢就跳转 那否则就顺序走

那jne呢就刚刚跟它反过来 那js jns也是类似 当然后面还有ja jb jg jl 我们说过啦 有一套是用于这个带符号整数比较的 还有一套呢是作为不带符号整数比较的 那这两套我们要区分开 这两套要区分开 当然你区分一次就够了 因为这个后缀跟Set是一模一样的 有的是用于带符号数的处理 有的是用于不带符号数的处理 那么大家要区分一下

条件跳转指令实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

cmpl cmpl指令 cmpl呢是eax跟edx相比较 实际上就是edx减去eax 那么再往上看 movl ebp 8到edx movl ebp 12到eax 实际上无非就是把两个参数都取进来了 把x y都取进来了 取进来之后呢就是 就是减一减 减完了之后呢就是我比较一下大小啦 那反正最终就是肯定是大的减小的 就如果小于等于的话呢就跑到这儿 就用eax减去edx 反之呢就用edx减去eax 然后当然记得结果值呢是放到eax里面去的 这个差值是放到eax里面去的 那实际上整体上就这么个逻辑

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.2.7 80x86汇编与C语言-2 - 条件移动指令

C语言:条件表达式

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64下…

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

一条cmovle这样的指令 那我们猜一猜它是干嘛的 其实也很明显 就是说你条件跳转没有了 代以代之一条什么指令呢 一条条件传送指令 cmov就conditional move 就条件移动 那么这里condition是什么呢 就看你最后那个c 它这个c就是le

le让我们想到什么啊 set那一套 jump那一套 后缀都是le啊 什么je啊 he之类的 这个都是一样的 它这个什么意思呢 就是这个条件也就是le满足 就将数据从src传至到dest 这个c啊跟Set后面那个后缀 跟jump后面那个后缀 这个c是一样的 这边意思就是什么呢 如果小于等于的话 就edi跟esi比一比 如果小于等于的话 我就把edx挪到eax里面去 否则就什么都不做

那么我们回过头来再看看这一条代码 实际上很清楚 就首先就是怎么着呢 就把x减y和y减x 两个值都算出来 然后呢我这边当然先 默认先放一个 在eax里面放一个结果了 因为我们最终的返回值是通过eax的 然后呢再compare一下 看一看前面我这个结果有没有放对 如果放得对了 也就是说这个le不满足 那这条指令就等于是个空指令 空指令就过去了 如果没放对 相当于这个le就满足了 就小于等于这个条件满足了 那我就把edx放到eax里面去 就把另外一个差值替换掉原来的 我们猜测的那一个 然后作为返回值给它返回 那么这样做的呢 一个明显的好处就是说 好像 我就使用一条条件移动指令 来替代了一条条件跳转指令 那为什么呢 一会儿我们再讲

x86-32下…

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3 80x86汇编与C语言-2 (续)

3.3.1 80x86汇编与c语言-2(续) - 条件移动指令的体系结构背景-1

微体系结构背景*

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.2 80x86汇编与c语言-2(续) - 条件移动指令的体系结构背景-2

微体系结构背景*

这个处理器啊 我可以同时读取多条指令进入这个流水线 就是 我一下子比方读取 不是读取一条指令 不是说一个circle读取一条指令 让它进流水线 是一个circle读多条指令 进流水线 那么在这种情况下的话呢 可能会多达几十条到上百条的指令 同时在流水线里面运行 同时在流水线里面运行 当然一方面来说这是个好事情 因为你的吞吐率提高了

但是对于一个 跳转指令 尤其是那种条件跳转指令 会迎来一个问题 这什么意思呢 大家想想看啊 条件跳转指令 它非常关键 它就决定 就是 我这条指令的下一条指令 是连着往下走呢还是跳到 我的一个目标地址去 但这样一来就有个问题了

比方说咱们以上面这段作为一个例子 就是说 因为我现在把程序的执行 分成多个流水段 也就是说 一条条件跳转指令进来之后呢 我不是立刻就能知道 这条条件跳转指令 是应该是要跳转还是不跳转 我可能得等它到了流水线的中间 或者某些甚至比较靠后的部位我才知道 那这样一来的话呢 你这条指令 紧接着以后的指令该怎么取 就成了一个问题了

对 就成一个问题 这是一个很大的一个问题 就是我就顺着走呢还是说我就认为它会跳 那这个时候就犹豫 因为我不知道 流水线本来是紧跟着一排排走的 条件跳转指令一进去 后面马上要去取址了 但现在怎么取是个问题 就流水线会带来这个问题 那么 这个时候呢就是说一个笨办法 就是说我就不取了 我一看它是条这个条件跳转指令我就不取 等着 等着它出结果 完后我再去跑 当然这样子的话呢就是太浪费了

当然有种做法就怎么着呢 我就 相当于我就搏一把 我就赌它是跳还是不跳 如果是我赌它不跳 那我就挨个取 那这样呢也会有问题 如果它跳转了呢 那么你取的这些指令全部作废 就cancel重新来过 那么 尤其在你深度流水线就多发射结构下 你一下子把这么多指令cancel掉 这个效率是很低的 这个效率很低 可能会有几十条指令 执行效率很低

所以这种情况下的话呢 就是说面临一个问题 就是条件跳转指令呢 有可能会引起一定的性能损失 即使你啊 如果等着它出结果 一直不动 那就是笨办法 那肯定是损失 那或者你就猜一下 那猜错了的话呢 也会有性能损失 所以呢就需要尽量消除 大概就这个意思啊 不知道我又没有说明白

条件跳转指令往往会引起一定的性能损失,因此需要尽量消除。

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

那么,怎么去消除它呢?就用条件转移指令。相当于用 1 条条件转移指令,来替换 1 条条件跳转指令,就是condition move那条指令。

回想下刚才的代码段,在那个里头,指令完全是顺序执行的,一直顺序执行,没有任何的branch。

所以相当于把这条条件跳转指令取消掉了,代之以 1 条条件转移指令。

但是条件转移指令也有很大的局限性。

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

(详细原因看视频)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.3 80x86汇编与c语言-2(续) - 循环的汇编语言表示-1

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

“Do-While”循环

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.4 80x86汇编与c语言-2(续) - 循环的汇编语言表示-2

“While” 循环(版本1)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

“While”循环(版本2,do-while模式)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

“For”循环

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

“For” –> “While” –>“Do-While”

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

补充

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.5 80x86汇编与c语言-2(续) - 循环的汇编语言表示-3

“While”循环-版本3(jump-to-middle)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Jump-to-Middle 实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

“For”–>“While”(Jump-to-Middle)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Jump-to-Middle模式

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.6 80x86汇编与c语言-2(续) - 循环表示的体系结构背景

微体系结构背景*

咱们还得讲讲看就微体系结构背景这一块 就是 我们就接着刚才说的那个往下说 条件跳转指令我们讲过啦 往往会引起一定的性能损失 所以呢要想办法消除它 那怎么消除呢 一个方法就刚才说过了 conditional move 但它的使用范围是有限 因为很多情况下实在是消除不了 消除不了 那消除不了怎么办呢 就是猜 我们刚才讲过了 跳还是不跳是个问题 那我们就猜一下 怎么猜呢 用Branch Prediction来猜 就是跳转的预测 跳转预测实际上这个东西 实际上也非常简单啦

我们看这个表 当然我们简单讲一讲 实际上它就引入了一个跳转的 Branch Prediction的一个table 一个表来进行预测 那怎么个预测法呢 就历史上 假设你 这条指令就是一个 就你这条指令 就是什么叫你这条指令呢 就是说 就这个地址里头 存放的就是一条branch指令 我只要 历史上只要跑过一次我就给它记下来了 记下来怎么记呢 就是我把它的这个

你看这是一个表嘛 表主要分成左右两大项 但当然实际上右边还有一小列 左边Lock up这一项是什么呢 这是你的这个跳转指令的一个pc地址 你通过pc地址来判断 你这条指令以前是不是曾经被我们执行过 条件跳转的话 左边这一列啊 实际上就放的是你这个 执行过的那些条件跳转指令的那个 它的pc以及它的地址 右侧是什么呢 就是你这条条件跳转指令 只要你历史上曾经执行过 那么它是跳还是不跳呢 如果你跳了 那么你跳的目标地址我就给你记下来 放在相关栏的右侧 然后最右边还有一小列 比较窄这一列 就说明什么呢 就是说 我这次是预测你是跳还是不跳

那怎么个预测思路呢 实际上非常非常简单 咱们讲讲原理啦 非常非常简单 就是说 我就根你的历史信息来完成预测啦 就历史上你跳了 上一次你跳了 比如说我只有一位来记录你上次跳还是不跳 那如果你上次跳了那就置1 那这次我还是猜你跳 你上次没有跳 那我就给它置0 你这次还是猜你不跳 就根据历史信息来 当然如果我猜错了那我就把它0转换成1了 然后下次再来的时候 那么发现它状态是1 那我就给它跳 实际上就这样子 当然你这个预测位数可以扩展 就一位的话可能精度不够 可以扩展 有些状态就在里面进行处理

总的来说呢就根据你的历史信息 来判断你这条指令 如果是branch指令的话 那是跳还是不跳 就根据历史信息来 历史上跳那就让它跳 历史上如果不跳那可能就是不跳 当然逻辑可能跟它不一样 就这个来法

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Branch Prediction

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.7 80x86汇编与c语言-2(续) - Switch的汇编语言表示-1

Switch 语句

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

我们说这怎么用跳转表来实现 switch case呢 实际上这个来法啊 其实我们之前首先有一个这么个概念啊 这是左侧相当于是switch case的一个 它的C的代码 然后呢对每个case的处理啊 是由一个指令段来进行处理 一个Block来进行处理的

那我们这么想啊 针对不同的x 实际上我们要针对这个x值 跳到不同的这个指令段的入口地址 那可以这么想象 C代码编译出来之后 就是变成一段一段的汇编代码嘛 汇编代码 它实际上在内存里面占据了一定的地址 它程序执行的时候 在内存里面肯定占据了一定的地址空间 那么它这个代码的起始地址 那就是这段代码的入口咯

那么我们就可以专门创建一个Jump Table 把你各个的 case下面的 处理代码的入口地址 搁到这个Jump Table里面去 这就形成了一个Jump Table表 然后大家可以想象 因为你这个 咱么说这个x这个取值啊 刚才说过了1 2 3 4没有 5 6 还是比较小而且比较连续的 所以的话呢 我们就可以根据这个x这个值 把x的值作为Jump Table的一个下标 相当于你这 table这个表嘛 表里面连续的放了几个入口地址嘛 然后你就可以把x作为这个 访问这个表的下标 把这个 下标所对应的这表项里面那个项读出来 这个读出来的这个项 就是你跳转的的目标地址呗 这个就跳转表的工作原理嘛

3.3.8 80x86汇编与c语言-2(续) - Switch的汇编语言表示-2

Switch 语句示例(x86-32)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

表结构

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.3.9 80x86汇编与c语言-2(续) - Switch的汇编语言表示-3

表项内容

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64 下的Switch语句

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Switch语句实例(case值很稀疏)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

举个switch语句的一个实例 这个case的值啊 很稀疏 也就是说从0 111 222 就间隔太大 一直到这个999然后就default 那这个呢就不太适合用条件跳转表啦

实际上条件跳转表适合用于什么呢就是说 你的取值比较密 就你111 这个数比较大但不怕 只要你111 112 113这样也行

你怕就怕什么啊 111 222 333 每次都加一百多个 那这样的话呢 你的跳转表就太大啦 占太大空间啦 实际上你有效的项没多少个 一般都是default 写了那么多项 实际上都是default 实际上这个不太合适 那么这样的情况下呢 那就没办法 就不适合用跳转表啦

x86-32下的实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

一般来说呢就是 以二叉树的组织结构呢挺明显的 就编译器还是比较聪明的 就是针对这种比较简单的switch case 它的取值比较稀疏 范围比较大的话呢 OK 我们通过二叉树方式 比较均衡的二叉树 这样的话呢就使得就是 我平均的判断次数能够最少 达到我的目的 就这么个来法

小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

条件码是什么东西 条件码实际就是处理器的状态的一部分 用于表示最近那条执行指令的某些 结果的一些状态 它的结果是0 非0 大于0小于0或者溢出 或者这溢出呢还可以分成就是说 是这个无符号数还是带符号数的溢出 各种条件码都给你设好了 那么相关的 怎么进行设置 读取 就set x里的一些指令

3.4 80x86汇编与C语言-3

3.4.1 80x86汇编与c语言-3 - 程序运行栈的基本操作-1

x86-32的程序栈

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

压栈操作

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

压栈是怎么来法呢 压栈指令只有一个操作数就是src src是一个合法的 你可以是一个寄存器 也可以是一个立即数 可以是一个合法的寻址 一个地址访问模式 这条指令的语义是从src取得操作数 操作数取出来之后 我把esp减4 减4是什么意思呢 就是压栈 esp指向栈顶 减了4它就往下走 往下走就是压栈的意思 栈顶往下走 像这属于压栈 然后呢 再把你取入那个操作数写入栈顶地址 写入esp所指向的内存地址 把它写进去 用户把esp减4 把数据写进去 相当于做了一个压栈操作 压栈push

出栈操作

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

pop实际上就是一个反操作 压进去再把它弹出来 实际上刚好相反 它就是读取栈顶数据 读取esp所指向的那个地址里头的数据 然后esp加4 加4是出栈嘛 把数据弹出来了呗 然后 把你读取的栈顶数据写入destination 写入目标操作数

3.4.2 80x86汇编与c语言-3 - 程序运行栈的基本操作-2

过程调用

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

这个有个过程调用有个叫call call什么意思呢 就是这边调用了call指令 call有点像个drop 我要跳到你这个目标地址 目标地址相当于你的操作数 在跳之前我要把返回地址压栈 这跟drop不一样 drop跳过之后我不用顾着返回 你函数调用会有个return 所以在跳之前要把我的返回地址压入栈 什么叫返回地址 就是你call指令后面的那条指令 这个地址给它压到栈里头 以便返回 比方说这边就给个实例 如果说call 后面实际上就给出了地址 相当于我要drop过去 drop之前我把后面的这条push指令的地址 把它压栈 这就是我的return address 那么相反的 你只要有call 那就有return return什么意思呢 也可以理解为跳转 跳转地址在哪里呢 跳转至当前栈顶 esp所指向的那个内存地址里边所存的地址 把它取出来就是我的目标对象 跳到那边去 就是这样子

过程调用实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.3 80x86汇编与c语言-3 - 过程调用与栈

基于栈的编程语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

连续过程调用示例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

栈帧(stack frame)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

过程调用时栈的变化

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

esp ebp永远遵循的规律 我要指向 当前正在运行的那个函数的它的栈帧的两头 所以它是往下走了一格 就这样子

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

ebp esp还是遵循刚才那个的规律 它要指向当前 正活跃正在被运行的那个过程的栈帧的两头

(这一小节建议观看视频中的动画,加强理解。)

3.4.4 80x86汇编与c语言-3 - 栈帧

x86-32/Linux 下的栈帧

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

栈帧当然怎么定义 基本上定义为就是说 把某个函数或者某个过程 调用它的子过程的一些参数 也算作是你的过程栈帧的一部分 但还有一种定义方式 我把你调用参数算成是你的子过程 那这样我们就可以往上看 就是说你当前栈的内容你可以看成 我有局部变量 有old ebp 有return address 还有父过程给我的参数 这也可以 只是个定义的问题

3.4.5 80x86汇编与c语言-3 - 一个实例

回顾下swap过程

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Setup #1

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Setup #2

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Setup #3

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Finish #1

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Finish #2

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Finish #3

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

swap Finish #4

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.6 80x86汇编与c语言-3 - 寄存器使用惯例

寄存器的使用惯例

两个过程之间会有个调用关系 我们以前就提到对于C程序而言 从main函数开始 一个函数调用另外一个过程 那么在这种情况下呢 大家从刚才例子里面已经看到了 这个通用寄存器的个数的限制 是非常有限的 32位就8个 64位是16个 那么在这种情况下呢 每个过程都可能使用 这些通用寄存器非常普遍的使用 那么在这种情况下呢 子过程如果使用了一定的通用寄存器 那么可能这些通用寄存器 也非常有可能是父过程使用过的 那么这两者之间 子过程在使用之前应该说保留这些 通用寄存器的值 在它退出的时候呢再进行恢复 这是应该这么做

但是具体怎么去做这个事呢 那一个笨办法 如果没有任何约定的话 那笨办法就是什么 就是一个过程对吧 就是把它所有使用的通用寄存器都保留一遍 那这样的是一种效率不高的方法 因为你所使用的通用寄存器 未必你的父过程就一定使用了

所以在这种前提下 大家要引入一个约定 实际上这是软件层面的一个约定 称之为寄存器的使用惯例 那怎么个来法呢就是说 寄存器要作为程序的临时存储 那么子过程父过程 或者我们称之为调用者 和被调用者它们之间各有各的职责 我们把这些通用寄存器分成两类 一部分是让调用者来负责 保存的 来保存和恢复了 当然 另一部分呢是由被调用者 进行保存与恢复

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.7 80x86汇编与c语言-3 - 一个递归调用的实例-1

x86-32/Linux 下的使用惯例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

递归调用

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

栈帧的建立(分配)过程

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.8 80x86汇编与c语言-3 - 一个递归调用的实例-2

递归体

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.9 80x86汇编与c语言-3 - 另一个递归调用的实例-1

带指针的“阶乘”过程

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

创建指针

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

它这边 对value做了一个取地址的操作 那么在这个情况下 编译器就没法 把val这个变量放到寄存器里头 那怎么办呢 只能放到栈帧里头 因为它是个局部变量 就放到这个函数的栈帧里头 因为它需要一个指针嘛

3.4.10 80x86汇编与c语言-3 - 另一个递归调用的实例-2

传递指针

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

我们说leal指令两个作用 本分工作是计算地址值 第二个就是 也能够比较好的参与一些数值的运算 它甚至比普通的加减法指令都要方便 这个地方当然当作它的本职工作

使用指针

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.4.11 80x86汇编与c语言-3 - x86-32过程调用小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.5 80x86汇编与C语言-3(续)

3.5.1 80x86汇编与c语言-3(续) - x86-64过程调用与运行栈-1

x86-64通用寄存器

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64寄存器使用惯例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

这实际上就是X86-64寄存器使用惯例 大家看一下好了 它里头有6个寄存器 是用作这个参数的传递 我们可以看到 这里边就有6个寄存器 那么return value还是默认放到rax里面去

还有一些就是说我们区分了一下 剩下这几个标黄的寄存器 叫做被调用者(Callee)负责保存与恢复

那么剩下这些 比如说像这种rax等 包括你的传参的 那么这种肯定是调用者保存以及恢复 rbp有点特殊我回头再讲 rsp实际上就是栈顶的寄存器 这还是硬件支持的一个栈顶寄存器

除此之外大家都是作为通用寄存器使用 当然各有各的分工了 但是唯一特殊的rsp rsp还是一个硬件支持的栈顶寄存器 就相当于call return这些指令默认地还是 操作这些寄存器

3.5.2 80x86汇编与c语言-3(续) - x86-64过程调用与运行栈-2

x86-64 寄存器

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64 下的swap过程-1

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.5.3 80x86汇编与c语言-3(续) - x86-64过程调用与运行栈-3

x86-64 下的swap过程-2

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64 下的swap过程-3

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

这里面呢没有分配栈帧 我们看看这个代码 首先就是说 你看它就把这个 这里面就相当于把这个a[i] 这个地址给它取出来 然后把a[i+1]这个地址给它取出来 然后就call swap 这个里头就是完全没有分配栈帧

这为什么可以这么做呢 实际上也简单因为在这里头 我们可以看到它所在过程本身 它首先第一它没有局部变量 而且它也没有对传递给它的参数 做什么运算操作 就直接两个呢 在相当于通过寄存器相关运算把地址算出来之 后 直接就调用它的子过程 在这种情况下呢 就不需要分配栈帧 就直接call就可以

x86-64 下的swap过程-4

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

这个时候我们看看它编译出来的代码 可能是这个样子了就jmp swap 没有用call指令直接用swap 这是可以的 为什么

大家想想看 因为jmp swap的时候呢 就直接相当于你就跳到swap函数的入口 然后swap里面执行完之后呢 swap本身有个return 它return到哪去呢 因为你的这个函数 没有对栈进行任何的修改 所以说当时那个rsp 应该还是指向 swap这个swap函数的返回地址 那反过来讲的话呢 当下面被调用的swap函数 它调return的时候 实际上它返回的是它的 父过程的 就是这个函数的返回地址 就是从一个从孙子过程 可以理解为 这是一个父过程 这是一个子过程 那么上面还有一个祖父过程 那么就是相当于直接从一个孙子过程 直接返回到祖父过程 这个我们完全可以画出layout 出来这个图就是很清楚

3.5.4 80x86汇编与c语言-3(续) - x86-64过程调用与运行栈-4

x86-64 的栈帧使用实例

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

栈操作

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.5.5 80x86汇编与c语言-3(续) - x86-64过程调用与运行栈-5

x86-64下的栈帧的一些不同操作特性

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64 过程调用小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

练习题

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6 80x86汇编与C语言-4

3.6.1 80x86汇编与c语言-4 - 数组的存储

基本数据类型

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

数组的内存存储

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

数组访问

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.2 80x86汇编与c语言-4 - 数组的访问-1

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

数组循环示例(x86-32)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.3 80x86汇编与c语言-4 - 数组的访问-2

指针循环示例(x86-32)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

嵌套数组

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

嵌套数组的这个例子 实际上说白了就是二维数字嘛 就是二维数字嘛 从它低维来看呢 就是说它里面每个元素 都是一个长度为5的int类型的数字 就是高维来看的话 所以就称为二维数字

二维数字我们在C里面也学到过对吧 二维数字存储呢就是说 它是按行来存储 第一行第二行第三行第四行这么来存 第0行第i行和第i加1行呢 因该是连续存储的 这个很清楚的 我们在C里面应该有这么一个概念 它是连续存储的 就是实际上随着指针的移动可以 访问完第一行之后也可以访问第二行了

3.6.4 80x86汇编与c语言-4 - 数组的访问-3

访问嵌套数组中的“行”

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

访问嵌套数组的单个元素

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.5 80x86汇编与c语言-4 - 数组的访问-4

Multi-Level Array

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Multi-Level数组 这一例子什么意思呢就说 我们定义了一个指针数组 就是它数据长度啊数组长度为3 这应该university 它数据长度 数组长度为3 它的每个元素实际上是个指针 每一个指针又指向了一个 长度为5的一个int类型的数组 实际上就变成这个样子 就2层 第一层是个数组 它里面的数组啊实际上是个指针是个地址 这个地址又指向 另外一个整数类型的这个数组

访问Multi-Level Array中的元素

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.6 80x86汇编与c语言-4 - 数组的访问-5

访问Multi-Level Array中的元素

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

与嵌套数组访问的不同

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.7 80x86汇编与c语言-4 - 二维数组示例-1

N X N Matrix Code

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

16 X 16 Matrix Access

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.8 80x86汇编与c语言-4 - 二维数组示例-2

n X n Matrix Access

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.6.9 80x86汇编与c语言-4 - 二维数组示例-3

Optimizing Fixed Array Access

(固定的size的数组的访问)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

Optimizing Variable Array Access

(可变长度数组的访问)

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

练习题

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7 80x86汇编与c语言-4(续)

3.7.1 80x86汇编与c语言-4(续) - (结构的存储-1)

结构

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

结构中元素的地址计算

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

续前

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7.2 80x86汇编与c语言-4(续) - (结构的存储-2)

数据存储位置对齐

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-32下不同元素的对齐要求

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

x86-64下不同元素的对齐要求

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7.3 80x86汇编与C语言-4(续) - (结构的存储-3)

结构的存储对齐要求

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7.4 80x86汇编与C语言-4(续) - (结构的存储-4)

结构自身的对齐要求

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

结构内元素不同的先后顺序…

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7.5 80x86汇编与C语言-4(续) - 结构数组的存储

结构数组

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

3.7.6 80x86汇编与C语言-4(续) - 数据结构的存储小结

结构的存储对齐要求小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

联合

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

小结

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

练习题

《汇编语言程序设计》学习笔记(3)三、C与汇编语言

参考文献:
1. 汇编语言程序设计 - 清华大学 - 学堂在线