用汇编言语编写核算两整数之和的程序(上)

先来看一道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标签表明程序履行的起点,而ABANS也是标签,别离表明第一个加数、第二个加数和核算成果(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和数据段中的 ABANS都是标签,但由于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的调试功能探究这些问题。

用汇编言语编写核算两整数之和的程序(下)