学习汇编记录Day4——程序结构

前面。了解了汇编语言里操作的寄存器,

如何定义变量,寄存器如何寻址,

常用的指令,和标志位。

了解了物理地址的概念,和地址与寄存器的关系。

终于到函数了,C语言的函数到了机器上是如何运行的,学完汇编才有一个本质的认识,

也可以为今后学习其他语言做基础,想不明白的就去看汇编。

先看问题

一,程序的基本组成

 概念:汇编语言源程序的组成部分有:模块、段、子程序等。一个模块对应一个目标文件,当开发较大型的应用程序时,

该程序可能由若干个目标文件或库结合而成的。

1,段

            微机系统的内存是分段管理的,为了与之对应,汇编语言程序也分若干个段来构成。8086cpu有4个段寄存器,

在该系统环境下运行的程序在某个时刻最多可访问四个段。

不论程序在某个时刻最多能访问多少个段,在编程序时,程序员都可以定义比该段数更多的段。在通常情况下,一个段的长度不能超过64K,在80386及其以后系统的保护方式下,段基地址是32位,段的最大长度可达4G。

 

1,如果段是数据段,则其长度是其所有变量所占字节数的总和;

2,如果段是代码段,则其长度是其所有指令所占字节数的总和;

 

在定义段是,每个段都有一个段名

格式 :            

段名              segment   

                      对齐类型   组合类型  类别

                       ......

                       ......

段名               ends

例子;

数据段

            data1  segment    

                       word1  dw   1, 9078h, ?

                       byte1  db    21, 'world'

                                  dd     12345678h

           data1  ends

代码段

          code1  segment

                     mov  ax,  data1

                     mov   ds, ax

                      .....

                    mov  ax, 4c00h

                    int     21h

         code1  ends

2,段寄存器的说明语句

        1)在汇编语言程序中可以定义多个段,每个段都要与一个段寄存器建立一种对应关系,建立这种对应关系的说明语句

如下;

assume     段寄存器名:段名[,段寄存器名:段名 , ……]

assume     cs:code1,  ds: data1    ;说明了cs对应于代码段code1, ds对应数据段data1.

在assume语句中,还可以用关键字nothing来说明某个段寄存器不与任何段相对应。

assume    es:nothing

在通常情况下,代码段的的一条语句就是用assume语句来说明段寄存器与段之间的对应关系。

在代码段的其他位置,还可以用另一个assume语句来改变前面assume语句所说明的对应关系,

这样,代码段中的指令就用最近的assume语句所建立的对应关系来确定指令中的有关信息。

 

data1    segment

             word1 dw 5678h

             byte1   db  "abcdefg"

data1 ends

 

data2  segment

            word2 dw  1234h

            word3 dw   9876h

data2  ends

 

data3  segment

           byte2  db     ?

data3   ends

 

code1  segment

           assume  cs:code1,  ds:data1, es:data2

           mov  ax, data1

           mov ds,  ax

          mov    ax, data2

          mov   es, ax

        ……

        assume  ds:data3, es:nothing

         mov  ax, data3

         mov ds,  ax

         mov bl, byte2

       ……

         int  21h

code1 ends

注意:代码段寄存器不能由程序员在源程序中对其赋值,其值是由操作系统在装入它进入系统运行时自动赋值的。

   2)堆栈段的说明

堆栈段时一个特殊的段,在程序中可以定义也可以不定义,除了要生成com型执行文件的源程序外,

一个完整的源程序一般最好定义堆栈段。如果在程序中不定义堆栈段,那么操作系统在装入该执行程序时将自动为其制定一个64k字节的堆栈段。

方法1:

stack1 segment

                           db  256 dup(?)

              top       label word

stack1 ends

由于堆栈时按地址从大到小的存储单元顺序来存放内容,所以,在堆栈存储单元说明语句之后,再说明一个栈顶别名,这样对栈顶寄存器sp的赋值就很方便。

方法2:

stack1 segment stack

                       db   256 dup(?)

stack1 ends

上述段定义说明了该段是堆栈段,系统会自动把寄存器ss和栈顶寄存器sp与该堆栈段之间建立相应的关系,并设置其初值,而不用再代码段对他们进行赋值。

3,程序的基本结构

伪指令END表示源程序到此为止,汇编程序对该语句之后的任何内容都不作处理,所以,通常情况下,伪指令END是源程序的最后一条语句。

伪指令END后面可附带一个在程序中已定义的标号,由该标号指明程序的启动位置。

如果源程序是一个独立的程序或主模块,那么,伪指令END后面一定要附带一个标号;如果源程序仅是一个普通模块,那么,其END后面就一定不能附带标号。

1)    顺序结构

顺序结构是最简单的程序结构,程序的执行顺序就是指令的编写顺序,所以,安排指令的先后次序就显得至关重要。另外,在编程序时,还要妥善保存已得到的处理结果,为后面的进一步处理直接提供前面的处理结果,从而避免不必要的重复操作。

编写程序段,完成下面公式的计算(其中:变量X和Y是32位有符号数,变量A,B和Z是16位有符号数)。

                                            A←(X-Y+24)/Z的商,B←(X-Y+24)/Z的余数

data1 segment 

           x dd ?

           y dd ?

           z dw ?

          a  dw ?

          b  dw ?

data1 ends

code1 segment

           ……

          mov ax, x

          mov dx, x+2 ;       用(dx:ax)来保存32bit变量x的数值

         sub ax, y

          sbb  dx, y+2

          add  ax, 24d

          adc  dx, 0

          idiv z

          mov a, ax

          mov b, dx

       ……

code1 ends

在编程序时,常常需要交换二变量之值。假设需要交换值的变量名为:var1和var2,临时增加的变量名为temp。常用的算法如下:        temp = var1,  var1 = var2, var2 = temp

xchg  指令

data1 segment

……

      word1  dw ?

      word2 dw ?

data1 ends

code1  segment

         ……

        mov  ax, word1

       xchg  ax, word2

       mov  word1, ax

code1 ends

 

2)分支结构

分支结构是一种非常重要的程序结构,也是实现程序功能选择所必要的程序结构。由于汇编语言需要书写转移指令来实现分支结构,而转移指令肯定会破坏程序的结构,所以,编写清晰的分支结构是掌握该结构的重点,也是用汇编语言编程的基本功。

在程序中,当需要进行逻辑分支时,可用每次分二支的方法来达到程序最终分多支的要求,也可是用地址表的方法来达到分多支的目的。

所以,在编写分支结构时,一般先处理简单的分支,再处理较复杂的分支。对多分支的情况,也可遵循“由易到难”的原则。因为简单的分支只需要较少的指令就能处理完,一旦处理完这种情况后,在后面的编程过程中就可集中考虑如何处理复杂的分支。

地址转移!!

 

2,用伪指令实现分支结构

格式1:

 .if  condition                 ;           条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相似,

                                                   也可用括号来组成复杂的条件表达式

           指令序列

.endif

格式2:

 .if condition

        指令序列

.else

      指令序列

.endif

格式3:

 .if  condition1

       指令序列

 .elseif conditon2

      指令序列

.endif

3)循环结构

在编写循环结构的程序片段时,我们可以多种方法来循环结构。

如:循环次数是已知的,可用LOOP指令来构造循环;

当循环次数是未知或不定的,则可用条件转移或无条件转移来构成循环结构。

2,用伪指令实现循环结构

.while conditon

           循环体的指令序列

.endw

如果条件表达式“condition”在循环开始时,就为“假”(false),那么,该循环体一次也不会被执行。

.repeat

         循环体的指令序列

.until conditon

.repeat

       循环体的指令序列

.untilcxz[condition]

REPEAT型循环在执行完循环体后,才判定逻辑表达式condition的值。若该表达式的值为真,则终止该循环,并将执行伪指令.UNTIL[CXZ]后面的指令,否则,将向上跳转到伪指令.REPEAT之后的指令,为继续执行其循环体作准备。

如果.UNTILCXZ后面没有写逻辑表达式,那么,由.REPEAT-.UNTILCXZ所构成的循环与用LOOP指令所过程的循环是一致的,它们都是以“CX=0”为循环终止条件。

如果.UNTILCXZ后面书写了逻辑表达式,那么,该逻辑表达式的形式只能是:“EXP1==EXP2”或“EXP1!=EXP2”。所以,这时由“.REPEAT-.UNTILCXZ condition”所构成的循环就与用LOOPNE/LOOPE指令所过程的循环是一致的,它们都是以“condition || CX=0”为循环终止条件。

和高级语言的REPEAT型的循环一样,.REPEAT-.UNTIL[CXZ]的循环体也会至少被执行一次。

.WHILE-.ENDW和.REPEAT-.UNTIL[CXZ]的循环体内还可再含有循环伪指令,这样就构成了循环结构的嵌套。

汇编程序在生产指令代码时会进行代码优化,以便尽可能得到最优化的指令序列。

4)辅助循环结构

1,终止循环伪指令         .break     /   .break  .if condition

2,循环继续伪指令          .continue  / .continue  .ifcondition

4,段的基本属性

1)对齐类型

2)组合类型

组合类型是告诉连接程序如何把不同模块中段名相同的段合并在一起,

NONE

表示当前段在逻辑上独立于其它模块,并有其自己的基地址。NONE是缺省的组合类型。

PUBLIC 表示当前段与其它模块中同段名的PUBLIC类型段组合成一个段。组合的先后次序取决于LINK程序中目标模块排列的次序。在组合时,后续段的起始地址要按其对齐类型进行定位,所以,同名段之间可能有间隔。
COMMON 表示当前段与其它模块中同名段重叠,也就是说,它们的起始地址相同。最终段的长度是同名段的最大长度。由于段覆盖,所以,前一同名段中的初始化数据被后续段的初始数据覆盖掉。
STACK 组合类型STACK表示当前段是堆栈栈,其组合情况与PUBLIC相同。
AT 数值表达式 该数值表达式是当前段所指定的绝对起始地址的段地址。

 

定义段组后,段组内各段所定义的标号和变量,除了与定义它们的段起始点相关外,还与段组的起始点相关。规定如

如果在ASSUME伪指令中说明段组与段寄存器相对应,那么,有关标号或变量的偏移量就相对于段组起点计算;

如果在ASSUME伪指令中说明段组内的某各段与段寄存器相对应,那么,有关标号或变量的偏移量就相对于该段的起点。

所以,在使用段组后,程序员要谨慎使用ASSUME伪指令,并保证段寄存器的值与段组或段相一致。

 

5,简化的段定义

在使用简化的段定义方式之前,必须使用存储模式说明伪指令来描述源程序所采用的存储模式。该伪指令说程序所使用的存储模式,汇编程序将用该存储模式生成相应的ASSUME和GROUP语句,同时也为其它的简化段创建等价的预定义。

          程序的存储模式说明伪指令的格式如下:

         .model  存储模式[,语言类型] [,操作系统类型] [,堆栈类型]

        例子;                .model small, c, os_dos, farstack

         可选的存储模式有:tiny, samll, compact, medium, large, huge, flat.

       语言类型: 

        操作系统类型:dos

        堆栈类型:堆栈类型的值主要影响伪指令.startup所生成的指令序列。该选项有二个可选值:NEARSTACK和FARSTACK。其中:NEARSTACK是该选项的缺省堆栈类型。

        

nearstack——堆栈段和数据段是同一段;

farstack——堆栈段和数据段是不同的段,且堆栈不在段组DGROUP中。

     

 
代码的位距 数据的位距 段的宽度 数据段和代码段能否合并
Code Distance Data Distance Segment Width Data & Code Combined?
  Tiny
  Small
 

Compact

  Medium
  Large
  Huge
  Flat
NEAR NEAR 16-bit Yes
NEAR NEAR 16-bit No
NEAR FAR 16-bit No
FAR NEAR 16-bit No
FAR FAR 16-bit No
FAR FAR 16-bit No
NEAR NEAR 32-bit Yes

 

small

所有的数据变量必须在一个数据段之内,所有的代码也必须在一个代码段之内。在这种模型下,数据段寄存器的内容保持不变,所有转移也都是段内转移。

该存储类型是独立汇编语言源程序常用的存储模型。

medium

所有的数据变量必须在一个数据段之内,但代码段可以有多个。在这种模型下,数据段寄存器的内容保持不变,转移可以是段间转移。

compact

数据段可以有多个,但代码段只能有一。

large

数据段和代码段都可以有多个,但一个数组的字节数不能超过64KB。

huge

数据段和代码段都可以有多个,一个数组的字节数也可以超过64KB。

flat

FLAT存储模式在创建执行文件时,将使该程序仅含一个包括程序数据和代码的32位段,并且只能在80386及其以后的计算机系统中运行。该程序的文件类型为EXE。

在使用该存储模式之前,必须先用伪指令.386、.486或其它伪指令来说明更高性能的CPU类型。也就是说:FLAT模式仅在386及其以后CPU模式下才能使用。

在该程序中,所有代码和数据位距的缺省值都是NEAR,子程序的类型也是NEAR,并且标识符@CodeSize,@DataSize和@Model的值分别为:0、0和7。

在FLAT存储模式下,程序将不使用段寄存器FS和GS。汇编程序在处理说明语句“.MODEL  FLAT”时,将自动生成下列段寄存器说明语句:

ASSUME  CS:FLAT, DS:FLAT, SS:FLAT, ES:FLAT, FS:ERROR, GS:ERROR

 

 

简化段段名的引用

当使用简化的段定义时,一般情况下,程序员可以不知道这些段的段名、段地址堆齐类型和组合类型等。但当把简化定义的段和标准定义的段混合使用时,就需要知道简化定义段的基本属性。

伪指令

缺省段名

对齐类型

组合类型

类别

段组名

.CODE

_TEXT

WORD

PUBLIC

'CODE'

 

.FARDATA

FAR_DATA

PARA

NONE

'FAR_DATA'

.FARDATA?

FAR_BSS

PARA

NONE

'FAR_BSS'

 

.STACK

STACK

PARA

STACK

'STACK'

DGROUP

.DATA

DATA

WORD

PUBLIC

'DATA'

DGROUP

.DATA?

BSS

WORD

PUBLIC

'BSS'

DGROUP

.CONST

CONST

WORD

PUBLIC

'CONST'

DGROUP

在其它存储模型下,由伪指令".CODE"说明的代码段段名在"_TEXT"之前还要加上其模块名(源程序名)。假设,某模块名为ABC,则其缺省的代码段段名就为ABC_TEXT。因此,在这种情况下,程序的模块名或源程序名不要以数字开头。

6,源程序的辅助说明伪指令

 

 

猜你喜欢

转载自blog.csdn.net/WeiLuckyStrike/article/details/89387060