通过调查本地代码的内容,可以了解程序最终是以何种形式来运行的。但是,如果直接打开本地代码来看的话,只能看到数值的罗列。如果直接使用这些数值来编写程序的话,还真是不太容易理解。因而就产生了这样一种想法,那就是在各本地代码中,附带上表示其功能的英语单词缩写。例如,在加法运算的本地代码中加上 add(addition 的缩写)、在比较运算的本地代码中加上 cmp(compare 的缩写)等。这些缩写称为助记符,使用助记符的编程语言称为汇编语言。这样,通过查看汇编语言编写的源代码,就可以了解程序的本质了。因为这和查看本地代码的源代码,是同一级别的。不过,即使是用汇编语言编写的源代码,最终也必须要转换成本地代码才能运行。负责转换工作的程序称为汇编器,转换这一处理本身称为汇编。在将源代码转换成本地代码这个功能方面,汇编器和编译器是同样的。用汇编语言编写的源代码,和本地代码是一一对应的。因而,本地代码也可以反过来转换成汇编语言的源代码。持有该功能的逆变换程序称为反汇编程序,逆变换这一处理本身称为反汇编。
除了将本地代码进行反汇编这一方法外,通过其他方式也可以获取汇编语言的源代码。大部分 C 语言编译器,都可以把利用 C 语言编写的源代码转换成汇编语言的源代码,而不是本地代码。利用该功能,就可以对 C 语言的源代码和汇编语言的源代码进行比较研究。
汇编语言的源代码,是由转换成本地代码的指令和针对汇编器的伪指令构成的。伪指令负责把程序的构造及汇编的方法指示给汇编器(转换程序)。不过伪指令本身是无法汇编转换成本地代码的。由构成程序的命令和数据的集合体加上一个名字而得到的,称为段定义。段定义的英文表达 segment 具有“区域”的意思。在程序中,段定义指的是命令和数据等程序的集合体的意思。一个程序由多个段定义构成。伪指令 proc 和 endp 围起来的部分,表示的是过程(procedure)的范围。在汇编语言中,这种相当于 C 语言的函数的形式称为过程。
在汇编语言中,1 行表示对 CPU 的一个指令。汇编语言指令的语法结构是操作码 + 操作数(也存在只有操作码没有操作数的指令)。操作码表示的是指令动作,操作数表示的是指令对象。操作码和操作数罗列在一起的语法,就是一个英文的指令文本。操作码是动词,操作数相当于宾语。能够使用何种形式的操作码,是由 CPU 的种类决定的。本地代码加载到内存后才能运行。内存中存储着构成本地代码的指令和数据。程序运行时,CPU 会从内存中把指令和数据读出,然后再将其存储在 CPU 内部的寄存器中进行处理。寄存器是 CPU 中的存储区域。不过,寄存器并不仅仅具有存储指令和数据的功能,也有运算功能。
指令中最常使用的是对寄存器和内存进行数据存储的 mov 指令。mov 指令的两个操作数,分别用来指定数据的存储地和读出源。操作数中可以指定寄存器、常数、标签(附加在地址前),以及用方括号([])围起来的这些内容。如果指定了没有用方括号围起来的内容,就表示对该值进行处理;如果指定了用方括号围起来的内容,方括号中的值则会被解释为内存地址,然后就会对该内存地址对应的值进行读写操作。
程序运行时,会在内存上申请分配一个称为栈的数据空间。栈(stack)有“干草堆积如山”的意思。就如该名称所表示的那样,数据在存储时是从内存的下层(大的地址编号)逐渐往上层(小的地址编号)累积,读出时则是按照从上往下的顺利进行的。栈是存储临时数据的区域,它的特点是通过 push 指令和 pop 指令进行数据的存储和读出。往栈中存储数据称为“入栈”,从栈中读出数据称为“出栈”。push 指令和 pop 指令中只有一个操作数。
函数的参数是通过栈来传递,返回值是通过寄存器来返回的。
C 语言中,在函数外部定义的变量称为全局变量,在函数内部定义的变量称为局部变量。全局变量可以引用源代码的任意部分,而局部变量只能在定义该变量的函数内进行引用。