在进行本章的解说之前,咱们先说明解说的机器言语类型。上一篇汇编言语和机器言语咱们讲过,机器言语是直接面向处理器(Processor:CPU)的程序设计言语,可是每一种这样的微处理器(CPU)由于硬件设计和内部结构的不同,所以每一种微处理器都有自己的机器指令集,也便是机器言语。而汇编言语是便于回忆的机器言语。本系列博客将会介绍两种相关的机器言语:Intel IA32 和 x86-64。前者是当今大多数计算机的主导言语,而后者是在 64 位机器上运转的扩展,咱们先从 Intel IA32开端。

1、机器级代码

  前面咱们就说过,计算机体系使用了多种不同的笼统,利用更简单的笼统模型来躲藏完成的细节。对于机器级编程来说,有两种笼统特别重要:

  ①、第一种是将机器级程序的格式和行为界说为指令集体系结构(Instruction set architecture ,ISA),它界说了处理器状况、指令的格式,以及每条指令对状况的影响。大多数 ISA,包括 Intel IA32 和 x86-64,将程序的行为描绘成如同每条指令是按次序履行的,即一条指令结束后,下一条指令开端。处理器的硬件远比描绘的精细杂乱,它们并发的履行许多指令,可是能够采纳措施保证全体行为与 ISA 指定的次序履行完全一致。

  ②、第二种是机器程序使用的存储器地址是虚拟地址,供给的存储器模型看上去是一个非常大的字节数组。存储器体系的实际完成是将多个硬件存储器和操作体系软件组合起来。

  在整个编译进程中,编译器会完成大部分工作,将把用 C 言语供给的相对比较笼统的履行模型表明的程序转化成处理器履行的根本指令,也便是汇编言语,汇编言语在被汇编器转化成机器言语,然后计算机去履行。汇编言语也便是具有更好的可读性的机器言语,所以能够了解汇编代码以及它与原始 C 代码的关系,是了解计算机怎么履行程序的关键步骤。

  咱们在写 C 程序时,处理器的状况都是躲藏的,即咱们编码不必去直接操作处理器。可是在汇编言语中,如下的几个处理器状况是可见的:

  一、程序计数器(在 IA32 中一般称为 PC,用 %eip 表明):指示将要履行的下一条指令在存储器中的地址。

  二、整数寄存器文件:包括8个命名的位置,能够存储一些地址或许整数的数据。有的用来记录某些重要的程序状况,有的则用来保存临时数据。

  三、条件码寄存器:保存最近履行的管用或逻辑指令的状况信息,它们用来完成控制或数据流中的条件变化,比方用来完成 if 和 while 语句。

  四、浮点寄存器:存储浮点数。

  留意:C 言语供给的模型能够在存储器中声明和分配各种数据类型的目标。可是实际上机器代码则只是简单的将存储器看成是一个很大的、按字节寻址的数组。

  汇编代码不区别有符号或许无符号整数,不区别各种类型的指针。甚至不区别指针和整数。

2、程序存储器

  程序存储器包括程序的可履行机器代码,操作体系需求的一些信息,用来办理进程调用和返回的运转时栈,以及用户分配的存储器块。

  程序存储器用虚拟地址来寻址,在任意给定的时刻,只认为有限的一部分虚拟地址是合法的。操作体系则担任办理虚拟地址空间,将虚拟地址翻译成实际处理器存储器(processor memory)中的物理地址。

3、程序示例

  如下这是一段 C 程序代码 hello.c:

#include <stdio.h>
int main()
{
    return sum(1,3);
}
int accum = 0;
int sum(int x,int y)
{
    int t= x+y;
    accum += t;
    return t;  
}

  然后履行如下指令生成汇编程序

gcc -O1 -S hello.c

  -O1是优化选项,少优化->多优化:

  O0 –>> O1 –>> O2 –>> O3

  -O0表明没有优化,-O1为缺省值,-O3优化等级最高

  生成的汇编程序 hello.s

        .file   "hello.c"
        .text
.globl sum
        .type   sum, @function  //界说全局函数sum
sum:
.LFB12:
        .cfi_startproc
        leal    (%rsi,%rdi), %eax //把寄存器%rsi和寄存器%rdi的值的地址装入eax中,即&(rsi+rdi)=eax
        addl    %eax, accum(%rip) //把寄存器%eax和寄存器%rip的值相加,并存放到 %rip中
        ret
        .cfi_endproc
.LFE12:
        .size   sum, .-sum
.globl main   //主函数main
        .type   main, @function
main:
.LFB11:
        .cfi_startproc
        movl    $3, %esi //将数据3复制到%esi寄存器
        movl    $1, %edi
        movl    $0, %eax
        call    sum  //将 sum 指令的地址压入到栈中,也便是下一条指令履行调用 sum 函数
        rep
        ret
        .cfi_endproc
.LFE11:
        .size   main, .-main
.globl accum  //界说全局变量accum
        .bss
        .align 4
        .type   accum, @object
        .size   accum, 4
accum:
        .zero   4
        .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-18)"
        .section        .note.GNU-stack,"",@progbits

  留意:一切以 ‘.’ 开头的行都是辅导汇编器和链接器的指令,咱们一般能够疏忽这些行。

  现在这些汇编指令咱们能够不必完全了解,后面会具体进行解说。

4、数据格式

  由于计算机是由16位体系结构扩展为32位体系结构的,Intel 用术语 “字”(word) 表明16位数据类型,因此 32 位表明 “双字”(double words),64 位数称为“四字”(quad words).

  前面的汇编代码咱们能够看到一切的汇编指令都带有字母 l,比方movl、addl、subl、pushl等等,这个l的后缀其实便是表明的数据格式,表明咱们操作的是32位的数值。

  下面咱们看一下 C 言语根本数据类型对应的 IA32 表明:

深入理解计算机系统(3.2)------程序编码以及数据格式
  上面的图示很好了解,比方mov指令,它是一个数据传送的指令,那么movb就代表传送一个字节的数据,movw就代表传送两个字节的数据,而movl就代表传送四个字节的数据。需求留意的是,long long int在IA32架构中是不支持这种数据格式的。并且汇编代码使用后缀 “l” 来表明 4 字节整数和8字节双精度浮点数,这不会发生歧义,由于浮点数使用的是一组完全不同的指令和寄存器。