8086汇编杂记

来源:互联网 发布:淘宝详情页是什么 编辑:程序博客网 时间:2024/05/01 00:56
    8086CPU拥有20位宽的地址总线,可以达到1MB的寻址空间,但是寄存器都是16bit的,那么如何进行寻址呢?方法是分段。一个20位的地址被分成两部分:16bit+4bit,分别保存在所谓段(segment)寄存器和普通寄存器(或者立即数)中。当访问外部地址时,段地址和偏移地址将被硬件转转换成物理地址,从地址总线输出。从运算上来看,物理地址 = 段地址 * 16 + 偏移地址。因为16bit左移了4位,相当于乘了16。如果用16进制表示,一个完整的物理地址可以被表示成:XXXXH:XH的形式,其中H表示16进制。比如物理地址21F60H可以被表示成21F6H:0H。
    上面说的是错误的。
    8086的确采用段地址和偏移地址进行寻址,只不过偏移地址没有被限定为4bit,而是16bit。但是段地址和偏移地之形成物理地址的过程,依然是左移4位相加,即乘16相加。偏移地址可以寻址64KB,因此一个段最大覆盖64KB范围。再考虑到两个紧邻的段的段首之间最多容纳16个字节(比如2000H:0000H -> 20000H,而2001H:0000H -> 20010H,所以20010H - 20000H = 10H = 16),故段之间存在大量的相互覆盖。这意味着同一个物理地址可以被表示成很多种段:偏移的形式。比如:
    21F60H  =  21F6:0H = 21F5:10H = 21F4:20H = 21F3:30H = ...
    
    8086对栈的支持很奇怪——栈顶竟然从高地址向低地址移动,也就是说栈从高地址向低地址填充!但是对于16bit字,在存储器的存储却是简单的“低低高高”(低位在低地址,高位在高地址)。
    
    8086CPU中所谓段(Segement)其实可以看作一个“环”。我们可以通过设置CPU中的三个段寄存器:DS, CS, SS来将RAM分割出不同功能的段来,这些段有一定的独立性:当相应的偏移DP,IP,SP从0000逐渐增加到ffff再继续增加并返回0000后(16位运算,进位舍弃),段寄存器和偏移组成的物理地址其实是从一个段内循环的。每个“环”最大为64KB。
    微软的汇编器MASM中,汇编代码几乎可以看作是内存中内容的累积。譬如下面的代码:
    mov ax,bx
    db 1,2,3,4,5
    mov ss,ax
    dw 1,2,3,4
    add bx,ds:[bx]
    dw ah,bh
可以看作内存中存储了mov ax,bx指令,然后“堆砌”了5个字节,然后又存了mov ss,ax,然后又“堆砌”了4个字,然后存了add bx,ds:[bx],然后“堆砌”了2个字。汇编如此底层,以至于可以把它看作直接在往内存中填写数据!段(segment)可以用来对这些堆砌的数据进行简单的分割。这对于开辟内存空间和让增强代码可读性非常有利。譬如,一般程序可以分割为数据段,代码段和栈段。这里的段其实就是上面所说类似于环的那个东西。比如代码
    segmentName segment
    ....
    segmentName ends
在MASM看来就是再说:给我分配一个段,段地址给segmentName。代码里segmentName就等于那个段地址。而所谓“分配”段地址,其实就是“顺次”向下(向高地址处)寻找下一个段地址。比如,如果第一个段内存储了16字节数据,那么第二个段的段地址就是第一个段的段地址+1;再如,如果第一个段内存储了15字节数据,那么第二个段的段地址也是第一个段的段地址+1;如果第一个段内存储了17字节数据,那么第二个段的段地址就是第一个段的段地址+2。从这个角度上讲,一个汇编程序就是一堆不同大小,且连续分布的段的集合。如果我愿意,每个段中存储无论存储什么,数据,代码,栈,或3者的大杂烩,都可以!两个段连续指上一个段的尾部地址和下一个段的头部地址相差不超过15字节(连续的段首地址相差16字节)。

    jmp指令可以让程序转移到任何一个地方。jmp的功能很简单,但是分类却很繁多:jmp short s,jmp near ptr s,jmp far ptr s,jmp 16bitReg,jmp word ptr RAM,jmp dword ptr RAM。jmp指令在机器码级别上可以分类两类,一类将跳转目的地址以偏移量的方式写入指令中,另一种则赤裸裸的将转移目的的完整地址写入指令中。前者包括jmp short s,jmp near ptr s,其中short允许8位位移,而near则允许16位位移(ptr是做神马的???)。后者包括jmp far ptr s,jmp 16bitReg,jmp word ptr RAM,jmp dword ptr RAM,其区别,一是目的地址存储的地方不同,比如标号、寄存器、存储器;二是是否指明段地址,如果没有指明,则段地址为本段。

    补码与数在计算机中的表示
    考虑一个字节可以表示的数的数目。256个。如果是无符号整数的话,一个字节可以表示[0,255]。如果表示有符号整数的话,一个字节同样只能表示255个数,问题则在于,这255个数是哪些?规定用最高位表示符号,即1-负,0-正,那么正数的范围是:[0,127];负数的范围则是:[-127,0]。由此可见0有两种表达方式:0000 0000与1000 0000。因此一个字节只能表示255个无符号整数。
    问题出在如何用bit位表示负数上。
    第二种方案是反码。他将一个正数X(X属于[1-127])逐位取反,去表示对应的负数。但是:
    0             -         -0
    0000 0000     -         1111 1111
根据反码方案,0000 0000与1111 1111均表示0 ---- 有两个码字表示同一个数字 ---- 问题依然得不到解决。
    第三种方案则是补码。不知道补码的设计思想是什么,但它的确有效。补码的目标依然是无重复的表示负数。补码将[1,127]逐位取反,然后再+1,得到的序列作为对应负数的表达。可以看到,表示-0的1111 1111加一之后和表示+0的0000 0000完全一致(难道这就是补码的设计动机?)。补码有很多神奇的性质,正是这些性质让补码成为了计算机内数的表示形式:
    1) 最高位可以用来判定正负。0表示正数而1表示负数。正数都以0开头自不用说,[0,127]取反码直后,首位都为1。反码1xxx xxxx表示数字[-127,0]。其中只有1111 1111(0的反码)加1之后,首位变成了0(0000 0000是0的补码)。而数字[-127,-1]的反码加一变成补码之后,首位均保持为1。因此补码首位清晰的表示出了数字的符号。
    2) “取反加一”和“改变符号”之间的对应关系是“对称”的。比如100(0110 0100)“取反加一”得到了-100的补码(1001 1100)(补码就是这样规定的),而对-100的补码(1001 1100)“取反加一”就得到了100(0110 0100)。换个角度看,在补码的世界里,“取相反数”操作,就是“取反加一”操作。
    3) 补码可以表示数字-128。“加一”操作让表示0的两个序列(方案一中的0000 0000与1000 0000以及反码中的0000 0000与1111 1111)变成一个(补码中的0000 0000),但是也带来了一个特殊的序列,那就是1000 0000。
由1)可知,这是一个负数。但是事实上,[1,127]中,没有一个正数进行“取反加一”操作后会成为1000 0000。如果对“取反加一”进行反操作,会发现原值是是1000 0000(128)----这超出了补码对正数的表示范围,而且也不符合补码对正数的表示规则。实际上,补码1000 0000,进行“取反加一”后得到1000 0000(128),由2)知,补码1000 0000表示的应该是-128。
    4) 补码简化了运算。无论是正数相加,还是正数与负数相加,在补码的世界里都是两个8bit二进制数相加——最重要的是,最高位符号位直接参与运算即可。补码统一了符号不同带来的不同运算方式。比如:
     10  0000 1010        10  0000 1010
  + -20  1110 1100     +  -5  1111 1011
----------------------------------------
    -10  1111 0110         5  0000 0101
    正是补码的上述奇妙性质,才让补码成为数在计算机中的表达形式。
    从另一个角度来看,当CPU将两个字节相加,事实上可以看作同时进行了两个运算,一是两个无符号整数的加法,二是两个有符号整数的加法。4)说明,这两种加法在过程上是完全一致的。
0 0
原创粉丝点击