用汇编言语编写核算两整数之和的程序(上)
先来看一道leetcode题,2235. 两整数相加(add-two-integers)。恐怕不管运用哪一种干流编程言语,甚至是从未接触过的新言语,处理这道题都不费吹灰之力吧。反倒是考虑题目中的陷阱远比学习新言语中“求两整数之和”的语法更花费时刻。并且这样的语法根本不必学习吧,除了num1 + num2
还能有其他写法吗?
不过,为了加深对核算机的了解,咱们特意自讨苦吃,看看如何用NASM这种汇编言语编写这个程序,并凭借一款名为SASM的软件分析该程序的运转情况。
汇编言语归于低级言语
编程言语大致能够分为低级言语和高档言语两大类。低级言语包括机器言语和汇编言语。运用低级言语书写的程序能够直接操作核算机硬件。
在机器言语中,任何指令和数据都要用二进制数表明。由于运用机器言语编程很不方便,人们发明了汇编言语。汇编言语运用英语单词的缩写来表明指令,使得程序员无须再回忆指令对应的二进制数字。
不过,用汇编言语编写的程序需要先转换成机器言语的程序才干由CPU解说履行。汇编言语的指令和机器言语的指令是一一对应的。
必备的硬件知识
运用汇编言语编程时必须了解一些硬件知识。关于核算两整数之和这个程序,咱们只需要了解一些有关CPU的寄存器和内存存储单元的知识就足够了。尽管这个程序最终会在屏幕上输出核算成果,但这是经过调用预设的指令(称作宏)完成的,并没有直接操作I/O。
寄存器
CPU内部有多个寄存器,每个寄存器都有一个仅有的姓名。例如,在Intel CPU中,寄存器的姓名是eax、ecx、edx、ebx等。咱们能够将这些寄存器视作变量,运用它们来履行运算。
eip寄存器是一个很关键的寄存器,其间存储的是正在履行的指令的地址(存储着指令的内存单元的地址)。每履行完一条指令,eip寄存器的值都会自动更新为下一条指令的地址。
内存
内存中的每个存储单元都有一个仅有的地址,存储单元之间经过地址加以区别。内存地址多用十六进制数表明。
汇编言语的语法只要一条
用汇编言语(这儿运用的是NASM)编写的核算两整数之和(这儿是核算1+2)的程序代码如下所示。
汇编言语其实是NASM、MASM、FASM等一类核算机言语的总称,本文选用了语法上较为简单的NASM汇编言语。
%include "io.inc"
section .data
A dd 1
B dd 2
ANS dd 0
section .text
global main
main:
mov eax, [A]
add eax, [B]
mov [ANS], eax
PRINT_DEC 4, ANS
xor eax, eax
ret
汇编言语的代码乍看之下非常晦涩,可实际上并非如此。由于汇编言语的语法基本上只要一条,即指令 指令的目标
。指令既能够没有目标,也能够带一个或两个目标。两个指令的目标之间要用逗号分隔。指令也称作操作码(opcode,operation code),即表明操作的代码,指令的目标也称作操作数(operand)。
例如,mov eax, [A]
这一行代码中的mov
是指令,eax
是该指令的第一个目标,[A]
是第二个目标。又如最终一行代码,ret
这个指令就没有目标。
在汇编言语中,操作数通常是CPU中的寄存器或内存中的存储单元,这是由于汇编言语正是用于描绘以下操作的编程言语:
- 对存储在CPU的寄存器中的数据进行核算
- 将存储在内存的存储单元中的数据读取到寄存器中
- 将核算成果存储在内存的存储单元里
- 将主机与外部设备之间输入/输出的数据存储在I/O的存储单元里
一行汇编言语的代码(句子)除了指令自身(操作码)和指令的目标(操作数),有时还包括标签(label)和注释(comment)。
标签是程序员为指令或数据赋予的称号,主要用于阐明指令或数据的含义。在上面的代码中,main
标签表明程序履行的起点,而A
、B
和ANS
也是标签,别离表明第一个加数、第二个加数和核算成果(answer)。稍后咱们将会看到,标签本质上便是内存中存储空间的地址。为了避免运用由杂乱无章的数字组成的内存地址,程序员往往运用标签指代存储空间。
注释是程序员为代码添加的文字阐明。在本文运用的名为NASM的汇编言语中,注释要写在分号;
之后。
逐行分析“核算 1+2”的代码
下面就来逐行分析代码清单中的代码。
%include "io.inc"
section .data
A dd 1
B dd 2
ANS dd 0
section .text
global main
main:
mov eax, [A]
add eax, [B]
mov [ANS], eax
PRINT_DEC 4, ANS
xor eax, eax
ret
能够看到,两个空即将这段代码分成了三部分。第一部分只要1行,%include "io.inc"
表明包括一个名为io.inc
的文件,这样咱们就能够调用其间的预设指令PRINT_DEC
,向屏幕输出核算成果了。
汇编言语的代码通常会分为几个段(section),最常见的段是代码段(.text section)和数据段(.data section),前者包括了程序中的指令,后者包括的是数据。
section .data
表明数据段的起点,其间包括三条“指令”,各条指令的作用如下:
-
A dd 1
:把整数1
存储到由4个接连的存储单元(4字节)构成的存储空间中,并为这块空间贴上一个叫作A
的标签,表明这是第一个加数。相当于高档言语中的A = 1
-
B dd 2
:把整数2
存储到由4个接连的存储单元(4字节)构成的存储空间中,并为这块空间贴上一个叫作B
的标签,表明这是第二个加数。相当于高档言语中的B = 2
-
ANS dd 0
:把整数0
(初始值)存储到由4个接连的存储单元(4字节)构成的存储空间中,并为这块空间贴上一个叫作ANS
的标签,表明这是核算成果。相当于高档言语中的ANS = 0
至此,数据段就完毕了,空行之后的section .text
表明接下来要进入代码段了。
代码段中的第一条指令是global main
,其间的main
是一个标签,下一行的main:
正是这个叫作 main
的标签自身,这是一个特别的标签,表明程序履行的起点。也便是说,CPU将从贴有main
标签的指令,即下一行的mov eax, [A]
开端解说履行程序。尽管main
和数据段中的 A
、B
和ANS
都是标签,但由于main
单独占了一行,所以习惯上要在结尾处加上冒号,以清晰表明这是一个标签,而不是一条叫作main
的指令。
前面的代码都是在为“核算 1+2”做准备,从mov eax, [A]
这一行开端,才真实开端进入核算环节。
mov eax, [A]
add eax, [B]
mov [ANS], eax
PRINT_DEC 4, ANS
mov
(move 的缩写)指令会将存储在A
标签中的数据复制到CPU的eax
寄存器中。这儿的[]
表明“存储在标签中的数据”,若不加[]
,这条指令就成了“将A
标签自身(本质上是内存地址)复制到eax
中”,这就不是咱们的目的了。[]
有点像高档言语中的解引证(如C言语中的eax = *A
)。
下一条指令是add eax, [B]
,这儿的add
望文生义,表明履行加法运算,参加加法运算的两个操作数别离是存储在eax
寄存器中的数据和存储在B
标签中的数据。该指令会把加法运算的成果存回到eax
寄存器中,相似高档言语中的eax = eax + *B
。
接下来又是mov
指令,这条指令会将存储在eax
寄存器中的核算成果存储到(复制到)ANS
标签中(贴有ANS
标签的存储单元中),相似高档言语中的*ANS = eax
。
“把A+B的成果存储到ANS中”,如此简单的运算看似一步就能完成,可到了汇编言语中竟然需要分三步才干完成。为了输出“好不容易”才核算出的成果,程序最终调用了预设的指令PRINT_DEC
来输出ANS
的值。由于ANS
这块存储空间占4字节,所以PRINT_DEC
的第一个操作数是4
。
装置汇编言语编程东西 SASM
了解了每行代码的含义后,咱们再来运用SASM验证一下这个程序的行为,看看程序输出的成果对不对。SASM是一款免费的汇编言语编程东西,自带调试功能,非常合适初学者用来学习汇编言语。SASM 可从以下页面获取。
dman95.github.io/SASM/englis…
SASM与干流IDE的运用方法非常相似,代码编写好以后,点击东西栏上的“构建并运转”(图标是绿色的三角形)按钮。假如代码中没有错误,就会在窗口底部的窗格中看到一行绿色的文字程序正常完成
,一起会在右侧的“输出”窗格中看到正确的核算成果3
,如下图所示。
至此,咱们终于得到了一个NASM汇编言语版本的“核算两整数之和”,严格说来这段程序只能核算1+2,而不是恣意的两整数之和。
汇编言语的程序需要先转换成机器言语的程序才干由CPU解说履行,并且汇编言语的指令和机器言语的指令是一一对应的。那“核算 1+2”这段代码对应着怎样的机器言语的代码呢?
另外,在高档言语中,核算两整数之和能够只用两个变量a += b
,但在汇编言语中,为什么不能写成add [A], [B]
呢?
接下来,咱们将利用SASM的调试功能探究这些问题。
用汇编言语编写核算两整数之和的程序(下)