计算机只执行机器代码,用字节序列编码低级的操作。
编译器(compiler)基于编程语言的规则、目标机器的指令集和操作系统的惯例,将编程语言生成汇编代码。汇编代码是机器代码的文本表示。
然后汇编器(assembler)和连接器生成机器代码。
用高级语言编写的程序可以在不同机器上编译和执行,而汇编代码则与特定机器有关。
1. 程序编码
机器级编程用抽象模型来隐藏实现的细节,主要有 2 种抽象:
- 由指令集体系结构(Instruction Set Architecture,ISA)来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式和每条指令对状态的影响。
- 机器级程序使用的内存地址是虚拟地址,看上去是一个字节数组。存储器系统的实现是将多个硬件存储器和操作系统软件相结合。
汇编代码非常接近机器代码,但它用可读性更好的文本格式表示。
处理器(CPU)状态:
- 程序计数器(PC):给出下一条要执行的指令在内存中的地址;
- 整数寄存器文件:存储地址(指针)或整数数据。
- 有的用来记录重要的程序状态;
- 有的用来保存临时数据,如过程参数、局部变量或函数返回值;
- 条件码寄存器:保存最近执行的算术或逻辑指令的状态信息;用来实现控制数据流中的条件变化,如 if 或 while;
- 向量寄存器:存放一个或多个整数或浮点数值;
编程语言中的任何数据类型,在机器代码中都用一组连续的字节来表示。
程序内存:
- 程序的可执行机器代码;
- 操作系统需要的信息;
- 用来管理过程调用和返回的运行时栈;
- 用户分配的内存块;
操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器内存中的物理地址。
一条机器指令只执行一个基本的操作,如将存放在寄存器中的两个数字相加,在存储器和寄存器之间传送数据。
编译器必须产生机器指令的序列,从而实现程序结构(如算术表达式求值或循环)。
2. 访问信息
一个中央处理单元(CPU)包含16个存储64位值的通用目的寄存器,用来存储整数数据和指针。
![0a65902095d9bfeb2b0fb61fd313affc.png](https://i-blog.csdnimg.cn/blog_migrate/918186896b394555c074ff861a0efa14.jpeg)
2.1. 操作数指示符
大多数指令有一个或多个操作数(operand),指示出执行一个操作中要用到的源数据值和放置结果的目的位置。
操作数分为 3 个类型:
- 立即数(immediate),表示常数值;
- 寄存器(register),表示某个寄存器的内容;
- 这里将寄存器集合看成是数组,用寄存器标识符来作为索引;
- 内存引用,根据计算出来的地址访问某个内存位置;
2.2. 压入和弹出栈数据
栈(stack)是一种数据结构,可以添加或删除值。
栈遵循后进先出(first in,last out)原则,通过push把数据压入栈中,pop弹出数据。
栈可以由数组实现,从数组的一段插入和删除值,这一端称为栈顶(stack top)。
将一个4字节的值压入栈,栈指针减8,然后值写入新的栈顶地址。
弹出该值时,寄存器读取该值,栈指针加8,但值任在原来的地址。
3. 算术和逻辑操作
操作指令有 4 种:
- 加载有效地址;
- 一元操作,1 个操作数;
- 二元操作,2 个操作数;
- 移位;
加载有效地址(load effective address)是从内存读取数据到寄存器,但实际上不从指定的位置读入数据,而是将有效地址写入操作数。
该指令位后面的内存引用产生指针,还可以简洁的描述普通的算术操作。
一元操作,只有一个操作数,既是源又是目的。这个操作数可以是一个寄存器,也可以是一个内存位置。如++,--。
二元操作,第二个操作数既是源也是目的,如赋值运算符:x-=y;操作数可以是寄存器或内存位置;
当第二个操作数为内存地址时,处理器必须从内存读出值,再把结果写入内存。
4. 过程
过程(process)是软件的一个重要抽象,是一种封装代码的方式。
函数用一组指定的参数和一个可选的返回值实现某种功能,在程序不同的地方调用该函数。
过程隐藏某个行为的具体实现,并提供清晰简洁的接口定义。
过程在不同的编程语言中有不同形式:
- 函数(function)
- 方法(method)
- 子例程(subroutine)
- 处理函数(handler)
假设过程 P 调用过程 Q,Q 执行后返回到 P,需要以下机制:
- 传递控制:
- 在进入程序Q时,程序计数器必须设置为Q代码的起始地址;
- 返回P时,程序计数器设置为P中调用Q后面那条指令的地址;
- 传递数据:P 向 Q 传递参数,Q 向 P 返回一个值;
- 分配和释放内存:
- 开始时,Q需要为局部变量分配空间;
- 返回前,Q必须释放这些存储空间;
4.1. 运行时栈
过程调用的关键是使用了栈数据结构的后进先出内存管理原则。
当过程P调用过程Q时,控制和数据信息被添加到栈尾,当返回P时,这些信息会被释放掉。
栈指针增加或减少,来分配或释放空间。