汇编语言基本概念简介

Table of Contents

→ 摘自 汇编语言程序指南

汇编语言是一种最低级、最古老、不具有移植性的编程语言,它能够直接访问计算机硬件,所以执行效率极高,占用资源极少,一般用于嵌入式设备、驱动程序、实时应用、核心算法等。

虚拟机的概念在理解软件与硬件之间的关系时非常重要。

汇编语言是一种什么程序设计语言

本文主要介绍与运行 Windows 32 位和 64 位系统的 Intel 和 AMD 处理器相兼容的微处理器编程,文中使用了 Microsoft 宏汇编器(MASM)。

MS Visual Studio 的大多数版本都包含 MASM 。

在运行 MS Windows 的 x86 系统中,其他一些有名的汇编器包括:TASM(Turbo 汇编器),NASM(Netwide 汇编器)和 MASM32(MASM 的一种变体)。

在 Linux 上有 GAS(GNU 汇编器)和 NASM 两种汇编器。

汇编语言是最古老的编程语言,在所有语言中,它与原生机器语言最为接近。它能直接访问计算机硬件,要求用户了解计算机架构和操作系统。

通过学习汇编语言,有助于了解计算机体系结构、机器语言和底层编程的基本原理。

什么是汇编器和链接器?

汇编器 (assembler)是一种工具程序,用于将汇编语言源程序转换为机器语言。 链接器 (linker)也是一种工具程序,它把汇编器生成的单个文件组合为一个可执行程序。还有一个相关工具,称为 调试器 (debugger),使程序员可以在程序运行时,单步执行程序并检查寄存器和内存状态。

汇编语言与机器语言有什么关系?

机器语言 (machine language)是一种数字语言,专门设计成能被计算机处理器(CPU)理解。所有 x86 处理器都理解共同的机器语言。

汇编语言(assembly language)包含用短助记符如 ADD、MOV、SUB 和 CALL 书写的语句。汇编语言与机器语言是一对一(one-to-one) 的关系: 每一条汇编语言指令对应一条机器语言指令。

C++ 和 Java 与汇编语言有什么关系?

高级语言如 Python 、C++ 和 Java 与汇编语言的关系是一对多(one-to-many)。比如,C++ 的一条语句就会扩展为多条汇编指令或机器指令。

来看个例子吧,将 C++ 代码转化为与之等价的汇编语言,如下:

1: int Y;
2: int X = (Y + 4) * 3;

↓ ↓ ↓ 这种转换需要多条语句,因为每条汇编语句只对应一条机器指令:

1: mov eax,Y                       ; Y 送入 EAX 寄存器
2: add eax,4                       ; EAX 寄存器内容加 4
3: mov ebx,3                       ; 3 送入 EBX 寄存器
4: imul ebx                        ; EAX 与 EBX 相乘
5: mov x,eax                       ; EAX 的值送入 X
寄存器(register)是 CPU 中被命名的存储位置,用于保存操作的中间结果。

汇编语言可移植吗?

一种语言,如果它的源码程序能够在各种各样的计算机系统中进行编译和运行,那么这各语言被称为 可移植的 (portable)。例如,一个 C++ 程序,除非需要特别引用某种操作系统的库函数,否则它就几乎可以在任何一台计算机上编译和运行。Java 语言的一大特点就是,其编译好的程序几乎能在所有计算机系统中运行。

汇编语言不是可移植的 ,因为它是为特定处理器系列设计的。

对于一些广为人知的处理器系列如 Motorola 68x00、x86、SUN Sparc、Vax 和 IBM-370 ,汇编语言指令会直接与该计算机体系结构相匹配,或者在执行时用一种被称为微代码解释器(microcode interpreter)的处理器内置程序来进行转换。

为什么要学习汇编语言?

第一,写嵌入式(embedded)程序,是指一些存放在专用设备中小容量存储器内的短程序,如电话、汽车燃油和点火系统、空调控制系统、安全系统、数据采集仪器、显卡、声卡、硬盘驱动器、调制解调器和打印机。由于汇编语言占用内存少,因此它是编写嵌入式程序的理想工具。

第二,处理仿真和硬件监控的实时应用程序要求精确定时和响应。高级语言不会让程序员对编译器生成的机器代码进行精确控制,而汇编语言则允许程序员精确指定程序的可执行代码。

第三,电脑游戏需求软件在减少代码大小和加快执行速度方面进行高度优化。就针对一个目标系统编写能够充分利用其硬件特性的代码而言,游戏程序员经常选择汇编语言,因为汇编语言允许直接访问计算机硬件,可以对代码进行手工优化以提高速度。

第四,汇编语言有助于形成对计算机硬件、操作系统和应用程序之间交互的全面理解。使用汇编语言,可以运用并检验计算机体系结构和操作系统中的理论知识。

第五,一些高级语言对其数据表示进行了抽象,这使得它们在执行底层任务时显得有些不便,如位控制。在这各情况下,程序员可以调用使用汇编语言写的子程序来完成任务。

第六,硬件制造商为其销售的设备创建设备驱动程序。设备驱动程序(device driver)是一种程序,它把通用操作系统指令转换为对硬件细节的具体引用。比如,打印机制造商就为他们销售的每一种型号都创建了一种不同的 MS-Windows 设备驱动程序。通常,这些设备驱动程序包含了大量的汇编语言代码。

汇编语言有规则吗?

大多数汇编语言规则都是以目标处理器及其机器语言的物理局限性为基础的。比如,CPU 要求两个指令操作数的大小相同,与 C++ 或 Java 相比,汇编语言的规则较少,因为,前者是用语法规则来减少意外的逻辑错误,而这是以限制底层数据访问为代价的。

汇编语言程序员可以很容易地绕过高级语言的限制性特征。例如,Java 就不允许访问特定的内存地址,程序员可以使用 JNI(Java Native Interface)类来调用 C 函数绕过这处限制,可结果会使得程序不容易维护。

反之,汇编语言可以访问所有的内存地址。但这种自由的代价也很高:汇编语言程序员需要花费大量的时间进行调试。

Table 1: 汇编语言和高级语言对各种应用类型的适应性
应用类型 高级类型 汇编语言
商业或科学应用程序,为单一的中型或大型平台编写 规范结构使其易于组织和维护大量代码 最小规范结构,因此必须由具有不同程度经验的程序员来维护结构。这导致对已有代码的维护困难。
硬件设备驱动程序 语言不一定提供对硬件的直接访问。即使提供了,可能也需要难以控制的编码技术,这导致维护困难。 对硬件的访问直接且简单。当程序较短且文档良好时易于维护
为多个平台(不同的操作系统)编写的商业或科学应用程序 通常可移植。在每个目标操作系统上,源程序只做少量修改就能重新编译 需要为每个平台单独重新编写代码,每个汇编器都使用不同的语法。维护困难
需要直接访问硬件的嵌入式系统和电脑游戏 可能生成很大的可执行文件,以至于超出设备的内存容量 理想,因为可执行代码小,运动速度快

虚拟机是什么

虚拟机概念(virtual machine machine)是一种说明计算机硬件和软件关系的有效方法。要说明这个概念,先从计算机的最基本的功能开始,即执行程序。

计算机通常可以用其原生机器语言编写的程序,这种语言中的每一条指令都简单到可以用相对少量的电子电路来执行。为了简便,称这种语言为 L0。

由于 L0 极其详细,并且只由数字组成,因此,使用其编写程序就非常困难。如果能够构造一种较易使用的新语言 L1,那么就可以用 L1 编写程序。

有两种实现方法:

  • 解释(Interpretation):运行 L1 程序时,它的每一条指令都由一个用 L0 语言编写的程序进行译码和执行。L1 程序可以立即开始运行,但是在执行之前,必须对每条指令进行译码。
  • 翻译(Translation):由一个专门设计的 L0 程序将整个 L1 程序转换为 L0 程序。然后,得到的 L0 程序就可以直接在计算机硬件上执行。

为什么要引入虚拟机的概念?

与只使用语言描述相比,把每一层都想象成有一台假设的计算机或者虚拟机会更容易一些。通俗地说,虚拟机可以定义为一个软件程序,用来模拟一些其他的物理或虚拟计算机的功能。

每一个虚拟机既可以用硬件构成也可以用软件构成。程序员可以为虚拟机 VM1 编写程序,如果能把 VM1 当作真实计算机予以实现,那么,程序就能够直接在硬件上执行。否则,用 VM1 写出的程序就被翻译/解释为 VM0 程序,并在机器 VM0 上执行。

机器 VM1 与 VM0 之间的差异不能太大,否则,翻译或解释花费的时间就会非常多。如果 VM1 语言对程序员来说还不够友好到足以用于应用程序的开发呢?

很简单,可以为此设计另一个更加易于理解的虚拟机 VM2。这个过程能够不断重复,直到虚拟机 VMn 足够支持功能强大、使用方便的语言。

Java 编程语言就是以虚拟机概念为基础的,Java 编译器把用 Java 语言编写的程序翻译为 Java 字节码(Java byte code)。后者是一种低级语言,能够在运行时由 Java 虚拟机(JVM)程序快速执行,并且 JVM 已经在许多不同的计算机系统上实现了,这使得 Java 程序相对而言独立于系统。

#. 特定的机器

与实际机器和语言相对,用 Level 2 表示 VM2,Level 1 表示 VM1,如图所示。

计算机数字逻辑硬件表示为 Level 1 机器,其上是 Level 2 ,称为指令集架构(ISA,Instruction Set Architecture)。通常,这是用户可以编程的第一个层次,尽管这种程序包含的是被称为机器语言的二进制数值。

指令集架构(Level 2)计算机芯片制造商在处理器内部设计一个指令集来实现基本操作,如传送、加法或乘法。这个指令集也被称为 机器语言 。每一个机器语言指令或者直接在机器硬件上执行,或者由嵌入到微处理器的程序来执行,该程序被称为微程序。在 ISA 层,编程语言提供了一个翻译层,来实现大规模软件开发。

汇编语言出现在 Level 3 ,使用短助记符,如 ADD、SUB 和 MOV ,易于转换到 ISA 层。汇编语言程序在执行之前要全部翻译(汇编)为机器语言。

高级语言(Level 4)出现在 Level 4,如 C、C++ 和 Java 。这些语言程序所包含的语句功能强大,并翻译为多条汇编语言指令,汇编语言代码则由编译器自动汇编为机器语言。比如,查看 C++ 编译器生成的列表文件输出,就可以看到这样的翻译。

汇编语言的数据表示

汇编语言程序员处理的是物理级数据,因此他们必须善于检查内存和寄存器。通常,二进制数被用于描述计算机内容,有时也使用十进制和十六进制数。

掌握数字格式,以便快速地进行数字格式转换。

每一种数制格式,都有一个基数(base),也就是可以分配给单一数字的最大符号数。下表的数制系统是硬件和软件手册中最常用的:

系统 基数 可能的数字
二进制 2 01
八进制 8 01234567
十进制 10 0123456789
十六进制 16 0123456789ABCDEF

*注:在展示计算机内存的内容和机器级指令时,使用十六进制是相当常见的。

二进制整数

计算机以电子电荷集合的形式在内存中保存指令和数据。用数字来表示这些内容就需要系统能够适应 开/关(on/off)真/假(true/false) 概念。

二进制(binary number)用 2 个数字作基础,其中每一个二进制数字(称为位,bit)不是 0 就是 1

位自右向左,从 0 开始顺序增量编号。左边的位 称为最高有效位(MSB, Most Significant Bit),右边的位称为最低有效位(LSB, Least Significant Bit)。一个 16 位的二进制数,其 MSB 和 LSB 如下图所示:

二进制整数可以是有符号的,也可以是无符号的。有符号整数又分为正数和负数,无符号整数认为正数,零也被看作是正数。

在书写较大的进制数时,有些人喜欢每 4 位或 8 位插入一个点号,以增加数字的易读性。比如, 1101.1110.0011.1000.000011001010.10101100

1. 无符号二进制整数

从 LSB 开始,无符号二进制整数中的每一个位代表的是 2 的减 1 次幂。如:

下表列出了从 20 到 215 的十进制值,如下:

2n 20 21 22 23 24 25 26 27
十进制 0 2 4 8 16 32 64 128
2n 28 29 210 211 212 213 214 215
十进制 256 512 1024 2048 4096 8192 16384 32768

2. 无符号二进制整数到十进制数的转换

对于一个包含 n 个数字的无符号二进制整数来说,加权位记数法(weighted positional notation)提供了一种简便的方法来计算其十进制值:

dec = ( Dn-1 x 2n-1 ) + ( Dn-2 x 2n-2 ) + … + ( D1 x 21 ) + ( D0 x 20 )

其中,D 表示一个二进制数字,如:
( 1 X 23 ) + ( 1 X 20 ) = 9

3. 无符号十进制整数到二进制数的转换

将无符号十进制整数转换为二进制,方法是不断将这个整数除以 2 ,并将每个余数记录为一个二进制数字。下表展示的是十进制数 37 转换为二进制数的步骤。

除法 37/2 18/2 9/2 4/2 2/2 1/2
18 9 4 2 1 0
余数 1 0 1 0 0 1

将表中的余数列的二进制位逆序连接,就得到了该整数的二进制值 100101 。由于计算机总是按照 8 的位数来组织二进制数字,因此在该二进制数的左边增加两个 0 ,形成 00100101

二进制加法运算

两个二进制整数相加时,是位对位处理的,从最低的一位(右边)开始,依序将每一对位进行加法运算。两个二进制数字相加,有四种结果,如下所示:

0 + 0 = 0 0 + 1 = 1
1 + 0 = 1 1 + 1 = 10

来看一个具体的例子吧,如下:

*注意,有些情况下,最高有效位会产生进位。这时,预留存储区的大小就显得很重要,否则可能会出错。如 1111 11110000 0001 等于 1 0000 0000 ,但是和数只能保存 8 位,那么它就等于 0000 0000 ,也就是计算结果的最低 8 位 ^_-||

字节

在 x86 计算机中,所有数据存储的基本单位都是字节(byte),一个字节有 8 位。

下表列出了无符号整数可能的取值范围,如下:

(无符号)类型 取值范围 按位计的存储大小
字节 0 到 28 - 1 8
0 到 216 - 1 16
双字 0 到 232 - 1 32
四字 0 到 264 - 1 64
八字 0 到 2128 - 1 128

十六进制整数

大的二进制数读起来很麻烦,因此十六进制数字就提供了一种简便的方式来表示二进制数据。 十六进制整数的 1 个数字就表示 4 位二进制位,两个十六进制数字就能表示一个字节

Table 2: 每个 4 位二进制序列转换为十进制和十六进制数值表
二进制 十进制 十六进制 二进制 十进制 十六进制
0000 0 0 1000 8 8
0001 1 1 1001 9 9
0010 2 2 1010 10 A
0011 3 3 1011 11 B
0100 4 4 1100 12 C
0101 5 5 1101 13 D
0110 6 6 1110 14 E
0111 7 7 1111 15 F

比如,下面二进制数 0001 0110 1010 0111 1001 0100 是如何与十六进制数 16A794 等价的。

1 6 A 7 9 4
0001 0110 1010 0111 1001 0100

1. 无符号数十六进制数到十进制的转换

dec = (Dn-1 X 16n-1)+ (Dn-2 x 16n-2) + … + (D1 X 161)+ (D0 x 160)

结合二进制的规律不难看出,一般情况下,可以通过公式把基数为 B 的任何 n 位整数转换为十进制数:

dec = (Dn-1 X Bn-1) + (Dn-2 X Bn-2) + … + (D1 X B1) + (D0 x B0)

2. 无符号十进制数到十六进制的转换

无符号十进制数转换为十六进制数的过程是,把这个十进制数反复除以 16 ,每次取余数作为一个十六进制数字。如,下表列出了十进制数 422 转换为十六进制 1A6 的步骤:

除法 余数
422/16 26 6
26/16 1 A
1/16 0 1

同样,如果要将十进制数转换为其他进制数,就在计算时把除数(16)换成相应的基数。

补码及进制转换

有符号的二进制数有正数和负数。在 x86 处理器中,MSB 表示的是符号位: 0 表示正数, 1 表示负数。

1. 补码表示

负整数用补码(two's-complement)表示时,使用的数字原理是:一个整数的补码是其加法逆元。(如果将一个数与其加法逆元相加,结果为 0 )

为什么需要补码呢?

补码表示法对处理器设计者来说很有用,因为有了它就不需要用两套独立的电路来处理加法和减法。例如,如果表达式为 A-B ,则处理器就可以很方便地将其转换为加法表达式: A+(-B) 。

将一个二进制整数按位取反(求补)再加 1 ,就形成了它的补码。以 8 位二进制数 0000 0001 为例,求其补码为 1111 1111 ,过程如下所示:

初始值 0000 0001
第一步:按位取反 1111 1110
第二步:将上一步得到的结果 1111 1110
加1 0000 0001
和值:补码表示 1111 1111

1111 1111-1 的补码, 补码操作是可逆的 (毕竟相加等 0 ),因此, 1111 1111 的补码就是 0000 0001

1.1 有符号二进制数到十进制的转换

用下面的算法计算一个有符号二进制整数的十进制数值:

  • 如果最高位是 1 ,则该数是补码。再次对其求补,得到其正数值。然后把这个数值看作是一个无符号二进制整数,并求它的十进制数值。
  • 如果最高位是 0 ,就将其视为无符号二进制整数,并转换为十进制数。

来看个例子,就理容易明白了。如,有符号二进制数 1111 0000 的最高有效位是 1 ,这意味着它是一个负数,首先要求它的补码,然后再将结果转换为十进制。过程如下所示:

初始值 1111 0000
第一步:按位取反 0000 1111
第二步:将上一步得到的结果 0000 1111
加 1 0000 0001
第三步:生成补码 0001 0000
第四步:转换为十进制 16

由于初始值( 1111 0000 )是负数,因此其十进制数值为 -16

1.2 有符号十进制数到二进制的转换

有符号十进制整数转换为二进制的步骤如下:

  • 把十进制整数的绝对值转换为二进制数;
  • 如果初始十进制数是负数,则在第一步的基础上,求该二进制数的补码。

比如,十进制数 -43 转换为二进制的过程为:

  1. 无符号数 43 的二进制表示为 0010 1011
  2. 由于初始值是负数,因此,求出 0010 1011 的补码 1101 0101 ,这就是十进制数 -43 的二进制表示。

1.3 有符号十进制数到十六进制的转换

有符号的十进制数转换为十六进制的步骤如下:

  • 把十进制整数的绝对值转换为十六进制数;
  • 如果初始值数是负数,则在第 1 步的基础上,求该十六进制数的补码。

1.4 有符号十六进制数到十进制的转换

有符号的十六进制整数转换为十进制的步骤如下:

  • 如果十六进制整数是负数,求其补码,否则保持该数不变;
  • 把第 1 步得到的整数转换为十进制。如果初始值是负数,则在该该十进制整数的前面加负号。

通过检查十六进制数的最高有效(最高)位,就可以知道该数是正数还是负数。如果最高位 ≥ 8 ,该数是负数;如果最高位 ≤ 7 ,该数是正数。比如,十六进制数 8A20 是负数,而 7FD9 是正数。

2. 最大值和最小值

n 位有符号整数只用 n-1 来表示该数的范围。下表列出了有符号单字节、字、双字、四字和八字的最大值与最小值。

类型 范围 存储位数 类型 范围 存储位数
有符号字节 -27 到 +27-1 8 有符号四字 -263 到 +263-1 64
有符号字 -215 到 +215-1 16 有符号八字 -2127 到 +2127-1 128
有符号双字 -231 到 +231-1 32      

二进制减法运算

执行二进制减法还有更简单的方法,即将被减去的符号位求补,然后将两数相加。这个方法要求用一个额外的位来保存数的符号。

来看一个例子,如计算 01101-00111 为例来试一下这个方法。首先,将 00111 按位取反 110001 ,得到 11001 然后,把两个二进制数值相加, 并忽略最高位的进位

01101                           # +13
11001                           # -7
----------
00110                           # +6

结果正是我们预期的 +6

字符在计算机中是如何表示的

如果计算机只存储二进制数据,那么它如何表示字符呢?计算机使用的是字符集,将字符映射为整数。

早期,字符集只用 8 位表示。即使是现在,在字符模式(如 MS-DOS)下运行时,IBM 兼容机使用的还是 ASCII (读为 askey)字符集。

ASCII 是美国标准信息交换码(American Standard Code for Information Interchange)的首字母缩写,在 ASCII 中,每个字符都被分配了一个独一无二的 7 位整数。

由于 ASCII 只用字节中的低 7 位,因此最高位在不同计算机上被用于创建其专有字符集。比如,IBM 兼容机就用数值 128~255 来表示图形符号和希腊字符。

ANSI 字符集

美国国家标准协会(ANSI)定义了 8 位字符集来表示多达 256 个字符。前 128 个字符对应标准美国键盘上的字母和符号。后 128 个字符表示特殊字符,诸如国际字母表、重音符号、货币符号和分数。

Microsoft Windows 早期版本使用 ANSI 字符集。

Unicode 标准

当前,计算机必须能表示计算机软件中世界上各种各样的语言。因此,Unicode 被创建出来,用于提供一种定义文字和符号的通用方法。

Unicode 定义了数字代码(称为代码点(code point)),定义了对象为文字、符号以及所有主要语言中使用的标点符号,包括欧洲字母文字、中东的从右到左写的文字和很多亚洲文字。代码点转换为可显示字符的格式有三种:

  • UTF-8 用于 HTML ,与 ASCII 有相同的字节数值;
  • UTF-16 用于节约使用内存与高效访问字符相互平衡的环境中;
Microsoft Windows 近期版本使用了 UTF-16 ,其中的每个字符都有一个 16 位的编码。
  • UTF-32 用于不考虑空间,但需要固定宽度字符的环境中,每个字符都有一个 32 位的编码。

ASCII 字符串

有一个或多个字符的序列被称为字符串(string)。更具体地说,一个 ASCII 字符串是保存在内存中的,包含了 ASCII 代码连续字节。比如,字符串 "ABC123" 的数字代码是 41h、42h、43h、31h、32h、33h

以空字节结束(null-terminated)的字符串是指,在字符串的结尾处有一个为 0 的字节。C 和 C++ 语言使用的是以空字节结束的字符串,一些 Windows 操作系统函数也要求字符串使用这种格式。

使用 ASCII 表

下图中列出了在 Windows 控制台模式下运行时使用的 ASCII 码。

在查找字符的十六进制 ASCII 码时,先沿着表格最上面一行,再找到包含要转换字符的列即可。表格第二行是该十六进制数值的最高位;左起第二列是最低位。

例如,要查找字母 a 的 ASCII 码,先找到包含该字母的列,在这一列第二行找到第一个十六进制数字 6 。然后,找到包含 a 的行的左起第二列,其数字为 1 。因此, a 的 ASCII 码是十六进制数 61

ASCII 控制符

0~31 的字符代码被称为 ASCII 控制字符。若程序用这些代码编写标准输出(比如 C++ 中),控制字符就会执行预先定义的动作。下表列出了该范围内最常用的字符:

ASCII 码(十进制) 说明 ASCII 码(十进制) 说明
8 回退符(向左移动一列) 12 换页符(移动到下一个打印页)
9 水平制表符(向前跳过 n 列) 13 回车符(移动到最左边的输出列)
10 换行符(移动到下一个输出行) 27 换码符

数字数据表示术语

用精确的术语描述 内存中和显示屏上 的数字及字符是非常重要的

比如,在内存中用单字节保存十进制数 65 ,形式为 0100 0001调试程序 可能会将该字节显示为 41 ,这个数字的十六进制形式。

如果这个字节复制到显存中,则显示屏上可能显示字母 A ,因为在 ASCII 码中, 0100 0001 代表的是字母 A 。由于数字的解释可以依赖于它的上下文,因此,下面为每个数据表示类型分配一个特定的名称,以便将来的讨论更加清晰:

格式 数值 格式 数值
二进制数字字符串 "01000001" 十六进制数字字符串 "41"
十进制数字字符串 "65" 八进制数字字符串 "101"

汇编语言布尔表达式

布尔代数(boolean algebra)定义了一组操作,其值为真(true)或假(false),它的发明者是十九世纪中叶的数学家乔治·布尔(George Boole)。

在数字计算机发明的早期,人们发现布尔代数可以用来描述数字电路的设计。同时,在计算机程序中,布尔表达式被用来表示逻辑操作。

一个布尔表达式包括一个布尔运算符以及一个或多个操作数,每个布尔表达式都意味着一个为真或假的值。以下为运算符集合:

  • 非(NOT):标记为 ¬~'
  • 与(AND):标记为 ^·
  • 或(OR):标记为 v+

其中,NOT 是一元运算符,其他运算符都是二元的,布尔表达式的操作数也可以是布尔表达式。

NOT

NOT 运算符将布尔值取反。用数学符号书写为 ¬X ,其中,X 是一个变量(或表达式),其值为真(T)或假(F)。下表列出了对变量 X 进行 NOT 运算后所有可能的输岀。 左边为输入,右边(阴影部分)为输出:

X ¬X
F T
T F

真值表中,0 表示假,1 表示真。

AND

布尔运算符 AND 需要两个操作数,用符号表示为 X ^ Y 。下表列出了对变量 X 和 Y 进行 AND 运算后,所有可能的输出(阴影部分):

X Y X^Y
F F F
F T F
T F F
T T T

当两个输入都是真时,输出才为真。这与 C++ 和 Java 的复合布尔表达式中的逻辑 AND 是相对应的。

汇编语言中 AND 运算符是按位操作的,如上图所示,X 中的每一位都与 Y 中的相应位进行 AND 运算。

OR

布尔运算符 OR 需要两个操作数,用符号表示为 XvY 。下表列出了对变量 X 和 Y 进行 OR 运算后,所有可能的输出:

当两个输入都是假时,输出才为假。这个真值表与 C++ 和 Java 的复合布尔表达式中的逻辑 OR 对应。

OR 运算符也是按位操作。在下例中,X 的每一位与 Y 的对应位进行 OR 运算,结果为 1111 1100

运算符优先级

运算符优先级原则(operator precedence rule)用于指示在多运算符表达式中,先执行哪个运算。在包含多运算符的布尔表达式中,优先级是非常重要的。

如下表所示,NOT 运算符具有最高优先级,然后是 AND 和 OR 运算符,当然我们可以使用括号来强制指定表达式的求值顺序。

布尔函数真值表

布尔函数(Boolean function)接收布尔输入,生成布尔输出。所有的布尔函数都可以构造一个真值表来展示全部可能的输入和输出。比如下面这个示例 (Y^S)v(X^¬S)

X Y S Y^S ¬S X^¬S (Y^S)v(X^¬S)
F F F F T F F
F T F F T F F
T F F F T T T
T T F F T T T
F F T F F F F
F T T T F F T
T F T F F F F
T T T T F F T

其实上述示例描述了一个多嘴选择器(multiplexer),一种数字组件,利用一个选择位(S)在两个输出(X 和 Y)中选择一个。如果 S 为假,函数输出(Z)就和 X 相同;如果 S 为真,函数输出就和 Y 相同。

Date: 2020-09-06 Sun 19:16

Author: Jack Liu

Created: 2020-09-11 Fri 14:40

Validate