数据表示.docx
- 文档编号:23164819
- 上传时间:2023-05-15
- 格式:DOCX
- 页数:68
- 大小:344.67KB
数据表示.docx
《数据表示.docx》由会员分享,可在线阅读,更多相关《数据表示.docx(68页珍藏版)》请在冰豆网上搜索。
数据表示
第2章数据表示
2.1本章概述
在学习汇编语言的时候,许多初学者遇到的主要障碍都是二进制和十六进制数字系统的用法。
虽然十六进制数(或者hex1)与通常所用的数的确存在一些差异,但是它们的优点远远大于缺点。
因此,理解这些数字系统是很重要的,因为它们简化了许多复杂的问题,包括位操作、有符号数的表示、字符代码以及压缩数据。
本章将讨论几个重要的概念,其中包括二进制和十六进制数字系统、二进制数据的结构(位、半字节、字节、字以及双字)、二进制的有符号和无符号数字系统、算术、逻辑、移位以及循环移位操作、位域以及压缩数据。
这些都是基础知识,本书剩下的部分将依赖于对这些概念的理解。
如果您已经在其他课程或者研究中熟悉了这些术语,那么在学习第3章之前至少应该复习一下这些内容。
如果您对它们还不熟悉,那么应该认真学习本章内容。
千万不要跳过任何基础知识。
本章除了介绍基础知识以外,还引入了一些新的HLA语句和HLA的标准库例程。
2.2数字系统
现代的计算机系统大多数都不用十进制数字系统来表示数值。
它们一般都采用二进制或者二进制补码(two’scomplement)数字系统。
要理解计算机算术运算的限制,就必须理解计算机如何表示数据。
2.2.1回顾十进制系统
由于人们使用十进制数字系统的时间已经很长了,以致于都认为它是理所当然的。
当看到数字“123”时,您不会去考虑数值123;而是在大脑中考虑该数值代表了多少项。
实际上,数字123表示的是:
或者
100+20+3
在位置数字系统当中,小数点左边的每一位数字都代表一个数值,它是0~9之间的数字乘以10的递增次方幂。
小数点右边的数字都代表0~9之间的数字乘以10的递减负次幂。
例如,数值123.456可表示为:
或者
100+20+3+0.4+0.05+0.006
2.2.2二进制数字系统
现代的计算系统大多都采用二进制逻辑进行操作。
计算机则使用两个电平(通常是0v和+2.4~5v)来表示数值。
有了这两个电平,我们就正好可以表示两个不同的数值。
它们可以是任意两个不同的数,但一般都表示0和1。
这两个数同时对应于二进制数字系统中所使用的两个数字。
由于80x86所使用的逻辑电平与二进制数字系统中所用的两个数字对应,所以PC采用二进制数字系统就不足为奇了。
二进制数字系统与十进制系统的原理相同,但有两点差别:
二进制只使用数字0和数字1(而不是0~9),二进制使用2的幂而不是10的幂。
因此,将二进制数转换为十进制数非常容易。
二进制串中的每个“1”,都要加上2n,其中n是从0开始排序的二进制数字的位置数。
例如,二进制数值110010102表示:
1*27+1*26+0*25+0*24+1*23+0*22+1*21+0*20
=
128+64+8+2
=
20210
将十进制数转换为二进制数要稍微复杂一些。
必须要找到那些加到一起时等于十进制结果的2的幂数。
一种有效的办法就是从2的某个较大的次数递减到20。
考虑下面的十进制数1359:
210=1024;211=2048。
所以1024是小于1359的2次幂当中最大的数。
从1359中减去1024,从左边以数字1开始表示二进制数。
二进制数=“1”;十进制结果为1359-1024=335。
紧接着2的9次幂(29=512)大于上面得到的结果,所以在二进制串的末尾加上一个“0”。
二进制=“10”;十进制结果仍然是335。
2的8次幂是256(28)。
从335中减去它并在二进制数的末尾添加一个“1”。
二进制=“101”;十进制结果为79。
128(27)大于79,所以在二进制串的末尾附加一个“0”。
二进制=“1010”;十进制结果仍然为79;
2的6次幂(26=64)小于79,所以减去79并在二进制串的末尾附加一个“1”。
二进制=“10101”;十进制结果为15;
15小于2的5次幂(25=32),所以只是简单地在二进制串的末尾附加一个“0”。
二进制=“101010”;十进制结果仍然为15;
16(24)仍然大于余数,所以在二进制串的末尾附加一个“0”。
二进制=“1010100”;十进制结果为15;
23(8)小于15,所以在二进制串的末尾添加一个“1”。
二进制=“10101001”;十进制结果为7;
22小于7,所以从7中减去4,并在二进制串的末尾附加一个“1”。
二进制=“101010011”;十进制结果为3;
21小于3,所以在二进制串的末尾附加一个“1”,并从十进制数中减去2。
二进制=“1010100111”;十进制结果现在为1;
最后,十进制结果为1,它是20,所以在二进制串的末尾处附加最后一个“1”。
最终的二进制结果为“10101001111”;
如果真的要将十进制数手工转换为二进制数,那么上面的算法可能并不是最容易掌握的。
还有一种更简单的方法叫做“偶/奇-被2除”算法。
该算法的步骤如下所示:
如果是偶数,就得到一个0;如果为奇数就得到一个1。
用2来除这个数并舍弃小数部分或者余数。
如果商为0,算法就完成了。
如果商不是0而是奇数,就在现有的二进制串之前插入一个“1”;如果该数为偶数,那么就在二进制串之前附加一个“0”。
返回步骤2,再重复进行。
幸运的是,很少需要将十进制数直接转换为二进制,所以在现实中,这些算法都不是十分重要。
虽然在高级语言中,二进制数并不重要,但它们在汇编语言中却处处可见(甚至于都不必进行十进制与二进制之间的转换)。
所以您应该会觉得轻松一点。
2.2.3二进制格式
单纯凭感觉来说,每个二进制数都包含无数个数字(或者是位(bit),它比二进制数字更短)。
例如,我们可以用下面任何一种方式来表示数字5:
101000001010000000000101~000000000000101
二进制数前面可以加任意多个前导0,而不会改变它的值。
我们遵守一个协定,如果在数值中出现了前导0,就将它们都忽略掉。
例如,
表示数值5,但是由于80x86一般都对8位数进行操作,我们会发现用0将所有的二进制数扩展为4位或者8位的若干倍,处理起来会容易得多。
因此根据这个协定,我们将把数字5表示为01012或者000001012。
在美国,为了能使一些比较大的数更容易读,大多数人都将每三位用逗号隔开。
例如,1,023,435,208就比1023435208读起来和理解起来更容易。
在本书中我们将对二进制采用相似的协定,我们将每四个二进制位分为一组并用下划线来分隔。
例如,我们将二进制数1010111110110010写成1010_1111_1011_0010。
我们通常将几个数压缩为一个二进制数。
80x86mov指令的一种形式就是使用二进制编码10110rrrdddddddd将三项压缩为16位:
一个五位的操作码(1_0110),一个三位的寄存器域(rrr)和一个8位的立即值(dddd_dddd)。
为了方便起见,我们将为每一个位置分配一个数值。
我们将采用下面的方式对每一位进行编号:
(1)二进制数的最右一位为第0位。
(2)每向左一位就给一个后继的位号。
8位二进制数使用位0~7:
16位二进制数使用位0~15:
32位二进制数使用位0~31,等等。
位0是低位(L.O.)位(有人把这一位称为最低位)。
最左边的位一般叫做高位(H.O.)位(或者最高位)。
我们将用位号来引用中间的各个位。
2.3十六进制数字系统
二进制系统中存在的一大问题就是冗余。
数值202需要八个二进制数字表示。
如果采用十进制表示,三个数字就足够了。
因此,采用十进制表示的数就要比二进制系统紧凑得多。
这一事实并没有被设计二进制的工程师遗忘。
当要处理一个较大的数值时,二进制数就变得非常不适用了。
遗憾的是,计算机是使用二进制进行思考的,所以在大多数时间里,使用二进制数字系统会更加方便一些。
虽然我们可以在十进制和二进制之间进行转换,但是这种转换并不简单。
十六进制(基数为16)数字系统解决了二进制系统中固有的很多问题。
十六进制数具有我们一直在寻找的两个特征:
它们非常紧凑,并且将它们转换为二进制或者反过来转换都非常简单。
因此,大多数计算机系统工程师都采用十六进制数字系统。
由于十六进制数的基数为16,所以十六进制小数点左边的每位数字表示该数值与16的整数次幂的乘积。
例如,123416等于
或者
4096+512+48+4=
每个十六进制数字都可以代表0~1510这16个数字之间的某一个。
由于只存在10个十进制数字,所以我们需要增加六个额外的数字来代表1010~1510这几个数。
我们并没有为这些数创建新的符号,而是使用字母A~F来表示。
下面全部都是有效的十六进制数:
由于我们经常需要向计算机系统输入十六进制数,所以需要采用不同的机制来表示十六进制数。
在大多数计算机上,我们都不能使用下标来表示相关数值的基数。
我们将采用如下的约定:
所有的十六进制数都以“$”字符开头,例如,$123A4。
所有的二进制数都以一个百分号(“%”)开头。
十进制数没有前缀。
如果从上下文可以很清楚地知道基数,本书就省掉“$”或者“%”符号。
下面是一些有效的十六进制数的示例:
$1234$DEAD$BEEF$AFB$FEED$DEAF
正如您所看到的一样,十六进制数非常紧凑,可读性较好。
另外,还很容易进行十六进制与二进制之间的转换。
考虑如下所示的表2-1。
表2-1二进制/十六进制转换
二进制
十六进制
%0000
$0
%0001
$1
%0010
$2
%0011
$3
%0100
$4
%0101
$5
%0110
$6
%0111
$7
%1000
$8
%1001
$9
%1010
$A
%1011
$B
%1100
$C
%1101
$D
%1110
$E
%1111
$F
表2-1提供了十六进制数与二进制数相互转换时所需的全部信息。
要将某个十六进制数转换为二进制数,只需要简单地将该数中的每一位十六进制数字替换为4位相关联的二进制数。
例如,要将$ABCD转换为二进制数,就只需要参照表2-1对每一位十六进制数字进行转换:
A
B
C
D
十六进制
1010
1011
1100
1101
二进制
将二进制数转换为十六进制格式也一样容易。
第一步就是将二进制数补零,使得该数的位数变为4的倍数。
例如,给定一个二进制数1011001010,第一步就是在该数的左边添加两位使得它包含12位数字。
要转换的二进制数就是001011001010。
下一步是将二进制数每四位分成一组,例如,0010_1100_1010。
最后,在表2-1中查这些二进制数,并用相应的十六进制数字来替换。
也就是,$2CA。
将这一过程的难度与十进制与二进制之间的转换或者十进制与十六进制之间的转换进行对比!
由于十六进制与二进制之间的转换需要反复操作,所以最好花上一点时间牢记表2-1。
即使有一个可以完成这种转换的计算器,您也会发现,在进行二进制与十六进制之间的转换时,手工转换会更快、更方便一些。
2.4数据结构
单纯从数学上来说,一个数值可以取任意多位。
而从另一方面来说,计算机一般都只能处理特定位数的值。
一般有1位(bit)一组的、4位一组的(称为半字节,nibble)、8位一组的(字节,byte)、16位一组的(字,word)、32位一组的(双字,doublewords或dwords)、64位一组的(四字,quadwords或qword)、128位一组的(长字,longwords或lwords)等等。
长度并不是任意的。
之所以用这些特定的值,是有充分理由的。
这部分将讲述在Intel80x86芯片中通常用到的位组。
2.4.1位
在二进制计算机上,数据的最小单元是一位。
由于一位只能够表示两个不同的数值(一般是0或1),所以您可能认为用一位只能表示很少量的数据项。
事实并非如此!
用一位可以表示无限多的数据项。
用一位可以表示两个不同的数据项。
例如,0或者1、真或者假、开或者关、男或者女、对或者错。
但是,千万不要局限于二进制数据类型(也就是说,那些只有两个不同数值的对象)。
可以用一位来表示数字723与1,245。
或者表示数值6,254与5。
也可以用一位来表示红色与蓝色。
甚至还可以用一位来表示两个不相关的对象。
例如,可以用一位来表示红色与数3,256。
用一位可以表示任意两个不同的数值。
但是一位只能表示两个不同的数值。
不同的位可以表示不同的对象。
例如,可以用第一位表示数值0与1,而其相邻位表示真与假。
那么该如何来分辨这些位呢?
当然,答案是您不能分辨。
但是这却阐明了计算机数据结构背后的所有思想:
数据就是您所定义的对象。
如果用一位来表示布尔值(true/false),那么这一位(所定义的)就表示true或者false。
要让一位具有真正的意义,那么就必须保持前后一致。
如果在某一点上,用一位来表示true或者false,那么在后面就不应该用它来表示红或者蓝。
由于试图要表示的内容大多数都需要多于两位,一位数值并不是使用得最多的数据类型。
但是,由于其他数据类型都由多位构成的组所组成,所以多位的组将在程序中扮演重要角色。
当然,有几种数据类型需要两个不同的数值,所以看起来这些位显得更重要。
但是,您很快就会看到单独的位很难操作,所以我们通常都会用其他数据类型来表示具有两种状态的数值。
2.4.2半字节
半字节是4位一组的数据类型。
它并不是特别令人感兴趣,但其中的两种表示法很有用:
BCD(binarycodeddecimal,二进制编码的十进制数)数2和十六进制数。
它用4位来表示一个BCD或者十六进制数字。
用半个字节,我们可以表示小于16的不同数值,因为4位串有16种不同的组合:
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
对于十六进制数而言,数值0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F都用4位来表示。
BCD码只用到了10个不同的数字(0、1、2、3、4、5、6、7、8、9),它也需要4位(因为用3位只能表示8个不同的数值)。
事实上,任意16个不同的数值都可以用一个半字节来表示,但是十六进制和BCD数是我们可以用半字节来表示的主要数据项。
2.4.3字节
毫无疑问,80x86微处理器所使用的最重要的数据结构是字节。
一个字节由8位组成,它是80x86微处理器上最小的可编址数据单元。
80x86的主存和I/O地址都是字节地址。
这就意味着80x86程序能够独立访问的最小单元是八位数值。
要访问更小的数需要读入包含该数据的字节,并屏蔽掉不需要的位。
字节中的位通常都从0~7进行编号,如图2-1所示。
图2-1位编号
位0是低位或者称为最低位,位7是字节的高位或者称为最高位。
我们将根据编号来引用它们。
注意一个字节正好包含两个半字节(见图2-2)。
图2-2一个字节中的两个半字节
位0~3构成了低位半字节,位4~7组成了高位半字节。
因为一个字节正好包含两个半字节。
字节数值需要两个十六进制数字。
因为一个字节包含8位,所以它可以表示28,或者说是256个不同的数值。
一般来说,都使用一个字节来表示0~255内的数值、–128~+127内的有符号数(参阅本章稍后对有符号数的讨论)、ASCII/IBM字符代码以及其他一些不需要超过256个不同数值的专用数据类型。
很多数据类型的数值都少于256个,所以8位通常已经足够了。
因为80x86是字节编址的机器,对于整个字节的操作,它比按位或者半字节编址的机器效率更高。
因此,大多数程序员都用一个字节来表示那些不需要超过256个数值的数据类型,即使少于8位也足够了。
例如,我们通常用000000012与000000002来分别表示布尔值true与false。
对于一个字节来说,最重要的用途在于保存字符数值。
无论是键盘上敲入的字符,屏幕上显示的字符,还是打印机打印的字符都具有数值。
为了让它能与其他数值相通,PC一般都使用ASCII字符集中的一个变码。
对于扩展字符代码,包括欧洲字符、图形符号、希腊字母和数学符号,PC一般都使用一个字节中余下的128个可能的数值。
因为字节是80x86存储空间中最小的存储单元,字节也正好是HLA程序中可以创建的最小变量;正如在第2章中所介绍的一样,可以用数据类型int8声明一个8位的有符号整型变量。
因为int8对象是有符号的,所以可以用int8类型的变量表示范围–128~+127中的数值。
int8类型的变量中应该只存储有符号的数值;如果想要创建一个任意字节的变量,就应该使用字节数据类型,如下面所示:
static
byteVar:
byte;
字节数据类型是一个部分untyped数据类型。
惟一与字节对象相关的类型信息就是它的长度(一个字节)。
可以将任意一个8位数值(较小的有符号整数、较小的无符号整数、字符等)存储为字节变量。
这就由您来跟踪存入字节变量的对象类型。
2.4.4字
字是一个16位的组。
我们将如图2-3中所示那样对一个字中的位从0~15进行编号。
和字节的编号一样,位0是低位。
对于字来讲,位15是高位。
当要引用字中的其他位时,我们将使用它们的位置编号。
图2-3字中的位编号
注意一个字正好包含两个字节。
位0~7组成了低位字节,位8~15组成了高位字节(见图2-4)。
图2-4一个字中的两个字节
一个字也可以如图2-5那样进一步分解为4个半字节。
半字节0是字中的低位半字节,半字节3是字中的高位半字节。
我们将简单地称另两个半字节为“半字节1”和“半字节2”。
图2-5字中的半字节
16位可以表示216(65,536)个数值。
这些数值可以处于范围0~65,535中,或者像一般情况下那样,是–32,768到+32,767之间的有符号数值,或者是不超过65,536的任意其他类型的数值。
字的三个主要用途分别是“短”有符号整数、“短”无符号整数以及Unicode字符。
字可以表示0~65,535或者-32,768到+32,767范围内的整数值。
无符号数用与字中的位相应的二进制数来表示。
有符号数使用2的补码形式来表示数值。
由于字可以表示多达65,536个不同的字符,Unicode字符允许在计算机程序中出现非罗马字符集。
和ASCII一样,Unicode也是一种国际标准,允许计算机处理亚洲、希腊以及俄国字符这样的非罗马字符。
与字节相同,在HLA程序中也可以创建字变量。
当然,在第1章中您已经看到了如何使用int16数据类型来创建16位的有符号整型变量。
下面是一个使用字数据类型来创建字变量的例子:
static
w:
word;
2.4.5双字
顾名思义,双字由两个字组成。
因此,双字如图2-6所示有32位长。
图2-6双字中的位编号
双字可以被分成一个高位的字和一个低位的字,4个不同的字节或者8个不同的半字节(见图2-7)。
图2-7双字中的半字节、字节和字
双字(dword)可以表示各种不同的内容。
一般双字都用来表示32位整型数(允许表示0~4,294,967,295内的无符号数或者-2,147,483,648到+2,147,483,647之间内的有符号数)。
32位浮点数也可以用双字来表示。
双字的另一个常见用法便是存储指针。
在第1章中,您已经看到了如何用int32数据类型来创建32位有符号整型变量。
同样也可以用双字数据类型来创建一个任意的双字变量,如下面的例子所示:
static
d:
dword;
2.4.6四字与长字
显然,我们可以继续定义更长的类型。
但是,80x86只支持特定的字长,所以没有理由再继续定义更长的类型。
虽然字节、字以及双字是80x86程序中最普遍的字长,四字(64位)也是非常重要的,因为有一种浮点数据类型需要64位。
同样,80x86处理器的MMX指令集也可以对64位的数据进行操作。
同样,长字(128位)也很重要,因为后来的80x86处理器上的SSE指令集可以操作128位的数据。
HLA允许用qword和lword类型来声明64位和128位的数值,如下所示:
static
q:
qword;
l:
lword;
注意,也可以用如下所示的HLA声明来定义64位和128位整型数:
static
i64:
int64;
i128:
int128;
但是请注意,不能用像mov、add以及sub这样的标准指令来直接操作64位和128位的整型数,因为标准的80x86整数寄存器每次只处理32位。
在本书后面,您将看到怎样操作这些扩展精度(extendedprecision)的数据类型。
2.5二进制数与十六进制数的算术运算
对二进制和十六进制数值我们可以执行几种操作。
例如,我们可以执行加法、减法、乘法、除法以及其他一些算术运算。
虽然您不必在这方面成为一名专家,但在一些紧急情况下应该能够用一张纸和一支笔手工完成这些运算。
已经说了您应该能够手工完成这些运算,而正确执行这类操作的方法便是用计算器来做。
市场上有几种这样的计算器,如下所示。
十六进制计算器的一些制造商(2002年):
卡西欧(Casio)
惠普公司(Hewlett-Packard)
夏普公司(Sharp)
德克萨斯仪器公司(TexasInstruments)
除此之外,其他的计算器制造商也能生产这样的计算器。
其中惠普生产的是最好的。
但是他们的产品比其他厂家的更贵。
夏普和卡西欧的产品售价都低于50美元。
如果您打算什么样的汇编程序都做,那么拥有一款这样的计算器是很关键的。
要想明白您为什么应该花钱买计算器,请考虑下面的算术问题:
$9
+$1
----
您可能认为这个问题的答案为$10。
但并不正确!
正确答案是10,即“$A”,而不是16($10)。
减法存在与此相似的问题:
$10
-$1
------
虽然真正的答案是“$F”,但您可能会认为答案为“$9”。
记住,这个程序问的是“16与1的差?
”当然,答案是15,即“$F”。
即使上面的两个问题没有干扰到您,但是当在考虑其他问题时,情急之下您的大脑还是会转回到十进制模式,就会产生不正确的结果。
这个例子说明:
如果必须手工用十六进制数进行算术运算,那您要么就抓紧时间认真做,要么就先将操作数转换为十进制,再对十进制数进行操作,然后再将它们转换回十六进制。
2.6关于数字及其表示法
许多人都对数字及其表示法产生了混淆。
初学汇编语言的学生经常会提出这样一个问题:
“EAX寄存器中有一个二进制数;怎样才能将它转换为EAX寄存器中的十六进制数呢?
”答案是:
“不用。
”虽然我们在前面不断地强调存储器或者寄存器中的数是以二进制表示的,但是最好将存储在它们当中的数看作是抽象数量(abstractnumericquantity)。
像128、$80或者%1000_0000这样的符号并不是不同的数;它们只是同一个抽象数量的不同表示法,即我们所说的“一百二十八”。
在计算机中,数字是不考虑其表示方法的;惟一涉及到表示法的就是在以人们可读的格式输入或者输出该数值的时候。
数量的可读格式是字符串形式。
要用人们可读的格式来打印128就必须将数值128转换为1后面跟着2,2后跟着8的三字符序列。
这就给出了数的十进制表示。
如果愿意,您可以将128转换为三字符序列“$80”。
数是相同的,但是我们将它转换成了不同的字符序列,因为(据推测)我们想用十六进制方式来表示数而不是十进制。
同样,如果我们想用二进制方式来表示数,那么就必须将该数值转换为一个1后面跟着七个0的串。
默认情
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据 表示