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

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

先来看一道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汇编语言。

asm 复制代码
%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"的代码

下面就来逐行分析代码清单中的代码。

asm 复制代码
%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]这一行开始,才真正开始进入计算环节。

asm 复制代码
    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的调试功能探索这些问题。

相关推荐
Crossoads15 小时前
【汇编语言】[BX]和loop指令(四)—— 汇编语言中的段前缀与内存保护:原理与应用解析
android·java·开发语言·数据库·机器学习·汇编语言
CYRUS_STUDIO3 天前
Android下的系统调用 (syscall),内联汇编syscall
android·linux·汇编语言
我不是程序猿儿3 天前
【数据结构】汇编语言和机器语言的‘数据结构‘
开发语言·数据结构·汇编语言·机器语言
CYRUS_STUDIO4 天前
Android 下内联汇编,Android Studio 汇编开发
android·汇编语言
Crossoads15 天前
【汇编语言】第一个程序(三)—— 深度剖析汇编程序的执行流程:编辑、编译、连接与运行
android·java·开发语言·数据库·网络协议·机器学习·汇编语言
xuan哈哈哈17 天前
汇编语言
网络安全·汇编语言
Crossoads19 天前
【汇编语言】第三章----寄存器(内存访问)(八)—— 栈顶越界的问题
java·开发语言·数据库·网络协议·tcp/ip·机器学习·汇编语言
Crossoads24 天前
【汇编语言】寄存器(内存访问)(七)—— CPU提供的栈机制
android·java·tcp/ip·机器学习·cpu··汇编语言
Crossoads1 个月前
【汇编语言】寄存器(内存访问)(二)—— DS和[address]
android·java·tcp/ip·机器学习·汇编语言
Crossoads1 个月前
【汇编语言】寄存器(CPU工作原理)(六)—— 修改CS,IP的指令以及代码段
android·开发语言·网络协议·tcp/ip·机器学习·汇编语言