8086汇编语言(灵活定位内存)

来源:互联网 发布:sql 注入漏洞 关键字 编辑:程序博客网 时间:2024/06/07 04:00

推荐书籍:王爽《8086汇编语言》


  把最近学的两章总结一起写了,这两章涉及的都是一个重点内容:如何灵活的定位内存,重点就在“灵活”两个字。学会灵活的定位内存,能提高编程的效率。因为许多编程语言在编译的时候,都会将代码首先转化成汇编指令。

  现在我们已经知道,我们可以用ds存放数据所存放地址的段地址,然后用[0]或者[bx]的方法,定位一个内存地址。接下来我们将引入其他的一些寄存器,以及寄存器与寄存器、寄存器与常数的结合灵活定位内存。
  首先认识两个新的指令:and & or
  and指令为逻辑与指令,按位进行与运算,逻辑与运算就是当数据双方的二进制位都为真的时候结果才为真,否则结果为假。
  mov al,01100011B
  and al,00111011B
  执行后:(al)= 00100011B
  or指令为逻辑或指令,按位进行或运算,当数据双反的二进制位只要有一方为真,其结果都为真。
  mov al,01100011B
  or al,00111011B
  执行后:(al)=01111011B

  以字符形式给出的数据:在计算机中通常采用一种编码方案——ASCII码,英语是世界上最广泛的语言这点不得不承认,并且计算机什么的大多都是米国搞出来的,所以ASCII码表才那么普遍,因为里面包含了数字、所有的英文字母和一些控制符,想一下包含了所有的英文字母和数字,就能表示一句完整的话了,扯远了···
  在ASCII编码方案中,用61H表示‘a’,62H表示‘b’,所有的字符都有规定的数值,一种规定需人们遵守才有意义。
  一个文本编辑过程中,就包含着按照ASCII编码进行的编码和解码,在文本编辑过程中,我们按一下键盘的a键,就会在屏幕上看到“a”,详细过程:我们按下键盘a键,这个按键的信息被送入计算机,计算机用ASCII码的规则对其进行编码,将其转化为61H存储在内存的指定空间中;文本编辑软件从内存中取出61H,将其送到显卡上的显存中;工作在文本模式下的显卡,用ASCII码的规则解释显存中的内容,61H被当作字符“a”,显卡驱动显示器,将字符“a”的图像画在屏幕上。我们可以看到,显卡在处理文本信息的时候,是按照ASCII码的规则进行的。这也就是说,如果我们要在显示器上看到“a”,就要给显卡提供“a”的ASCII码,将61H写入显存中。
  说点题外的,有人会有疑惑,ASCII编码方案总共就256个字符,那么我们博大精深的汉字怎么办?当然拉,我们中国作为人口最多的国家,这么大的市场老外肯定不会放过,所以就有了Unicode编码方案,这套方案主要有中日韩的文字,ASCII为8位编码方案,而Unicode为16位编码方案,我们平时在显示器上看到汉字,主要是用了Unicode这套编码方案。

  在汇编程序中,我们用单引号‘’的方式指明数据是以字符的形式给出的,编译器将把它们转化为相应的ASCII码,如下面的程序:

  

  db 'unIX'相当于db 75H,6EH,49H,58H
  db 'foRK'相当于db 66H,6FH,52H,4BH
  mov al,'a'相当于mov al,61H
  mov bl,'b'相当于mov bl,62H
    
  现在考虑一个问题,我们如何将大写字母转化成小写,如何将小写字母转换成大写。
  首先我们需要知道一个规律:小写字母的ASCII值比大写字母的ASCII码值大20H
  例如:大写A 十六进制为41H 二进制为01000001 小写a 十六进制为61H 二进制为01100001
  我们能观察到小写字母的ASCII值的确比大写字母的ASCII值大20H(废话),但是我们又能观察到小写字母a的二进制第五位为1,大写字母A的二进制第五位为0,再联想到and & or运算符,貌似明白了些什么。
  是的,我们能运用这两个运算符,将一个数值的某个二进制位变成1或者0,假设我们这里还没学习汇编语言中判断指令(本来就没学),所以我们做一个更简单的例题,将‘BaSic’和‘iNfOrMaTiOn’这两个字符串,第一个全部改成大写,第二个全部改成小写。运用上面已知的规律,我们来写这个程序。
  首先分析,我们要将前面的字符串全部改成大写,我们必须将它们所有字符二进制的第五位变成0,想要将后面的字符串全部改变成小写,我们必须将它们所有的字符二进制的第五位变成1。无论该二进制位是1还是0,全部变成0;无论该二进制为1还是为0,全部变成1。程序如下:

  

  将这个程序进行一顿ML(别想歪,是masm和link),之后载入debug执行,执行完查看datasg段的数据。

  

  在前面我们用[bx]和[idata]的方法来定位一个内存单元(idata代表常数),现在我们可以结合起来利用[bx+idata]来定位内存。
  例如mov ax,[bx+200]
  含义:将ds:bx+200内存单元的内容送入ax中,这个内存单元长度为2个字节(一个字单元);
  也可以用这种写法:mov ax,200[bx] 或者 mov ax,[bx].200
  为什么要这样去定位一个内存单元呢?联想到我们这两章的重点:灵活,为的就是更灵活的定位内存单元,这种方法能在对数组进行处理的时候比较方便。
  有了这种方法我们能将上面的程序修改一下:

  

  现在我们想一下,如果我们的程序足够大、处理的数据足够多,对寄存器的需求也会随之变多。
  我们想将一个数据段的数据copy到该数据段的另一个位置,可是一个bx满足不了我们这个要求,因为需要索引一个源和一个目的,种种的不方便,使SI和DI诞生了。SI和DI的功能跟bx想近,只是SI和DI不能分两个8位寄存器使用,也就是没有di和dl也没有si和sl。
  看例题:

  

  试想一下若我们只有bx,那么就会变得很繁琐,注意mov ax,[si]进行的是字处理,将si的字型数据放入ax,也就是16位。上一个程序用的是al,是8位,而一个字符占8位,我们这里能一次copy16位也就是两个字节,提高了效率。
    
  有了si和di,现在我们又能组合出许多的定位方法,[bx+si]、[bx+di]、[bx+si+idata]、[bx+di+idata],有人注意到了,为什么没有[si+di],因为这两货不能放在一起用,不要问我,问老外去,这是他们的设计问题。
  利用[bx+si]、[bx+di],注意bx和si都是能够改变数据的,而利用[bx+idata]的话,idata是常数,是不能改变的,所以[bx+si]和[bx+di]适合对两个需要改变偏移地址的内存地址进行定位,[bx+si+idata]、[bx+di+idata]也一样有他的用途,重点是看你需要处理的是什么,该怎么用是自己决定的。
    
  现在考虑,若有一个程序需要实现两层循环,而寄存器只有一个cx用于存储循环次数,当执行内部循环的时候覆盖了外部的cx,那该咋办呢?如果我们用其他的寄存器来存,这是可以实现的,但是并不是我们推荐的,因为寄存器的数量是有限的,但若真的有一个情况,所有的寄存器都有各自的用法,那该怎么办?由此,我们推进一种新的知识,用栈存储暂时性的数据,赞赞赞。
  在进行外部循环的时候,我们可以将cx的值先压入栈,push cx,当内部循环完成之后,我们再进行pop cx,在这里我省略了例题,主要是因为太难打了,所以可以翻翻王爽大叔的闷骚之作P155页。
    
  当我们与朋友在街上漫步的时候,突然朋友一声“看!美女!”,我们首先需要考虑两个问题,美女在哪个方向,美女距离有多远。在我们汇编语言中,我们数据处理也有两个基本问题,处理的数据在什么地方,处理的数据有多长?处理的数据在哪里,我们可以用定位内存的方法来进行定位,处理的数据有多长?是字节型数据字型数据还是双字型数据。
  接下来我们又引入了一个寄存器,叫做dp,在8086CPU中,只有bx、si、di、dp这四个寄存器能用在[]中进行定位。在上面,我们已经知道了si和di是不能一起用的,这里我们需要知道dp和bx也是不能放在一起用的。若我们用[dp+idata]进行内存定位时没有详细标明段寄存器,默认的段寄存器是用ss。当然如果我们不用dp,用的是bx、di、si进行内存定位时,默认是在ds中。我们现在引入另一个寄存器,叫做es,它也是一个段寄存器,用法跟ds一样,你可以理解为“替补”,当ds不够用就用es。当然并不是说只能用ds、ss、es,其他的寄存器也行,只要进行显性的说明。
  例如:mov ax,ss:[bx+si] / mov ax,cs:[bx+si+8]
  
  这里是从鱼C工作室挖来的图,解释一下用[idata]称为直接寻址,[Idata]里为立即数;而用[bx]、[si]、[di]这类的称为寄存器直接寻址,这些寄存器因为是在CPU内部,所以速度比较快。而[bx+idata]这类的称为寄存器相对寻址,[bx+si]这类的称为机制变址寻址,[bx+si+idata]称为相对基址变址寻址。这些名词都不需要刻意去背,反正只要知道在哪些情况下我们该选择哪种定位方法就可以了,掌握了运用方法,碰上各种情况就要靠自己考虑咯。
  指令处理的数据有多长?在8086CPU下,可以处理两种尺寸的数据,一种是byte(字节),一种是word(字)。
  我们可以用寄存器名指明要处理的数据的尺寸:
  mov ax,1 进行的是字操作,因为ax为16位寄存器,能一次处理16位的数据
  mov bx,ds:[0] 同上
  inc cx 同上
  add ax,1000 同上
  当我们用mov al,1之类的,只要是8位寄存器,能一次处理8位数据,所以进行的就是字节操作。
  在没有寄存器的情况下,我们能用操作符X ptr 指明内存单元的长度,X在汇编指令中可以为byte或word。
  例如:mov word ptr ds:[0],1
  inc word ptr [bx]
  add word ptr [bx],2
  这就说明了是进行字型操作
  而mov byte ptr ds:[0],1
  inc byte ptr [bx]
   ···说明是进行字节型操作
  掌握这个的运用对我们来说是很重要的,因为在没有寄存器参与的情况下,CPU是无法得知所要访问的单元是字节型还是字单元。
    
  学到现在,我们已经能进行加减乘的运算,但是除运算该怎么办呢?别急,我们猴王请来了逗B——div
  这个是除法指令,需要注意一下问题:
  (1)除数:有8位和16位两种,存放在reg(寄存器)或者内存单元中;
  (2)被除数:默认放在AX或者DX和AX中,AX和DX合起来是32位,所以被除数可以为32位,但是不可以为8位。若被除数放在AX和DX中,AX存放低16位,DX存放高16位;
  (3)结果:如果除数为8位,则AL存储除法操作的商,AH存放出发操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
  格式:div 寄存器 / div 内存单元
  例如:div byte ptr ds:[0]
  这里我们可以了解除数为8位的字节型数据,所以其结果的商将放在AL,余数放在AH中;
  例如:div word ptr dx
  这里我们可以了解除数为16位的字型数据,所以其结果的商放在AX中,余数放在DX中。
    
  dd指令:我们之前学习过db和dw,db定义的是字节型数据,而dw定义的是字型数据,那么dd是什么呢?可能有些人已经知道了,是双精度的字型数据。
  db 1 占1字节,数据为01H
  dw 1 占2字节/1字,数据为0001H
  dd 1 占4字节/2字,数据为00000001H


  dup指令:天呐,有了db、dw、dd,dup又是什么怪胎?是的,dw是小三,dd是小四,dup就是小五。这让我想起一个故事,dup的爸爸生了四个孩纸,一个叫db一个叫dw一个叫dd,另一个叫什么?啪啪啪,此处应该有掌声。(丫的你在写文章能想到这些,膜拜!)
  继续我们的话题,考虑,如果我们需要定义100个db数据,那怎么办?难道要db 0,0,0,0,0,0,0,0····写个100遍?
  NO,dup就是因为这样而诞生的,拯救程序员于苦难中。
  dup的用法例如:db 3 dup (0) 定义了三个字节型数据,分别是0,0,0
  还可以酱紫:dw 100 dup (1,2,3) 定义了三个字型数据,分别是1,2,3,1,2,3,1,2,3,1,2,3········此处省略88个数字和87个逗号···


  实验7这里先不更新,写完直接copy代码过来,自行分析拉。
0 0
原创粉丝点击