深入理解计算机系统(CSAPP)复习笔记——第三章
日期:2023-11-12 21:50:26   来源:可信计算

  笔者哈工大大二在读,为整理计算机系统课程内容(准备考试),特此撰写复习笔记。笔记力求精简,实现内容的整合。

  笔记会不定期更新,参考内容有原书、老师的课件、网络上现有笔记,放在文末。

  考虑一个C程序,有两个文件p1.c和p2.c。我们用Unix命令行编译这些代码:

  假设有一个C语言代码文件mstore.c,在命令行上使用“-S”选项,就能看到C语言编译器产生的汇编代码:

  要查看机器代码文件的内容,有一类称为反汇编器的程序很有用。在Linux系统中,带“-d”命令行标志的程序OBJDUMP可以充当这个角色:

  一个x86-64的中央处理器单元(CPU)包含一组16个存储64位值的通用目的寄存器。这些寄存器用来存储整数数据和指针。

  生成1字节和2字节数字的指令会保持剩下的字节不变, 生成4字节数字的指令会把高位4个字节置为0。

  大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。操作数分为三种类型:

  最简单形式的数据传送指令——MOV类。这些指令把数据从源位置复制到目的位置,不做任何变化。

  MOV指令只会更新目的操作数制定的那些寄存器字节或内存位置。唯一的例外是movl指令以寄存器作为目的时,它会把该寄存器的高位4字节设置为0。

  常规的movq指令只能以表示为32位补码数字的立即数作为源操作数。movabsq指令能以任意64位立即数值作为源操作数,并且只能以寄存器作为目的。

  注意没有一条明确的指令把4字节源值零扩展到8字节目的。这样的数据传送可以用movl指令来实现。

  C语言中所谓的“指针”实际上的意思就是地址。间接引用指针就是将该指针放在一个寄存器中,然后在内存引用中使用这个寄存器。

  pushq指令的功能是把数据压到栈上,而popq指令时弹出数据。这些指令只有一个操作数——压入的数据源和弹出的数据目的。

  加载有效地址指令leaq其实就是movq指令的变形。它的指令形式是从内存读数据到寄存器,但实际上它根本就没有引用内存,该指令将有效地址写入目的操作数。

  一元操作只有一个操作数,这个操作数可以是一个寄存器,也可以是一个内存位置。

  二元操作的第二个操作数既是源又是目的。第一个操作数可以是立即数 、寄存器或是内存位置,第二个操作数可以是寄存器或是内存位置。当第二个操作数为内存地址时,处理器必须从内存读出值,执行操作,再把结果写回内存。

  移位操作,先给出移位量,第二项给出要移位的数。移位量可以是一个立即数,或者放在单字节寄存器%cl中。(只允许以这个特定的寄存器作为操作数)

  除法或取模操作由单操作数除法指令来提供。有符号数除法指令idivq将寄存器%rdx(高64位)和%rax(低64位)中的128位数作为被除数,除数作为指令的操作数给出。指令将商存在寄存器%rax中,将余数存在寄存器%rdx中。

  机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。

  CPU维护着一组单个位的条件码寄存器,他们描述了最近的算术或逻辑操作的属性。

  CMP和TEST指令只设置条件码而不改变任何其他寄存器。CMP和TEST指令分别根据两个操作数之差和两个操作数取与来设置条件码。注意它们的比较顺序。

  我们使用SET指令来实现第一种方法。这类指令的后缀表示不同的条件而不是操作数大小。

  一条SET指令的目的操作数是低位单字节寄存器元素之一,或是一个字节的内存位置,指令会将这个字节设置为0或1。为得到一个32位或64位结果,我们一定要对高位清零(例如利用MOVZ或MOVS指令)。

  跳转指令会导致执行切换到程序中的一个全新的位置,这些跳转的目的地通常用一个标号指明。

  jmp是无条件跳转。它可以是直接跳转,即跳转目标是作为指令的一部分编码的(标号,例如“.L1”);也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的(“*”后跟一个操作数提示符)。

  条件跳转指令根据条件码的某种组合,或跳转,或继续执行代码序列中下一条指令。条件跳转只能是直接跳转。

  跳转指令有几种不同的编码,最常用的都是PC相对的,会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。

  实现条件操作的传统方法是利用控制的条件转移。当条件满足时,程序沿着一条执行路径执行,当条件不满足时,就走另一条路径。

  一种替代的策略是使用数据的条件转移。这种方法计算一个条件操作的两种结果,然后再根据条件是不是满足从中选取一个。只有在一些受限制的情况下这种策略才可行,但如果可行,就可以用一条简单的条件传送指令来实现它。

  条件传送指令有两个操作数:源寄存器或者内存地址S,和目的寄存器R。指令的结果取决于条件码的值。

  不是所有的条件表达式都可以用条件传送来编译。最重要的是,无论测试结果如何,我们给出的代码会对then-expr和else-expr都求值。如果这两个表达式中的任意一个可能会产生错误条件或副作用,就会导致非法的行为。(例如return (xp ? *xp : 0);)

  第一种翻译方法:跳转到中间,它执行一个无条件跳转跳到循环结尾处的测试,以此来执行初始的测试。

  第二种翻译方法:guarded-do,首先用条件分支,如果出事条线不成立就跳过循环,把代码变换为do-while循环。